Makros: UI (Button-Objekt, Verwaltungspanel, Symbolleiste)

Zweiter von zwei Commits (Fundament in e670f13). Neues, frei platzierbares
Button-Objekt auf der Fläche, das per Klick ein zugewiesenes Makro
ausführt (Zuweisung im Inspektor oder per Rechtsklick direkt am Button).
Neues "▶ Makros"-Panel (Liste + Formular, dritter "▶ Ausführen"-Button pro
Zeile) für Anlegen/Bearbeiten/Löschen/Testlauf ohne Canvas-Button.

Beim Live-Testen gefunden und behoben: ADDTABLE/ADDSHEET navigieren als
Nebeneffekt zur eingefügten Tabelle/zum eingefügten Blatt (richtig für
einen Symbolleisten-Klick, überraschend für einen Makro-Lauf im
Hintergrund) — runMacro() sichert und stellt jetzt aktives Blatt/Objekt/
Auswahl nach dem Lauf wieder her. Nebenbei zwei bestehende
"impliziter-Else"-Fehler behoben, die mit einer vierten Objektart zu
echten Fehlerquellen geworden wären (io/json.ts normaliseObject,
CanvasObject.vue Render-Dispatch).

Vollständig live per Playwright End-zu-Ende verifiziert: Makro über das
echte Formular angelegt, per Listen-Button und per Button-Objekt
ausgeführt, Rechtsklick-Zuweisung geprüft, ein absichtlich fehlerhaftes
Makro zeigt einen Fehler-Toast statt abzustürzen — keine Konsolenfehler.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-09 10:07:56 +02:00
co-authored by Claude Sonnet 5
parent e670f13077
commit a59fda25b0
11 changed files with 759 additions and 30 deletions
+98 -1
View File
@@ -390,6 +390,73 @@ Beispiel mit.
ganze, vordefinierte Adresse; er kann nicht als Startpunkt einer neu
getippten `:`-Bereichsangabe kombiniert werden.
### Makros
Ein **USP gegenüber gewöhnlichen Tabellenkalkulationen**: Zellen, Zeilen/
Spalten, Tabellen und Blätter dokumentweit **per Skript adressieren und
verändern** — Struktur *und* Inhalt — statt nur per Formel zu lesen. Anders
als eine Formel läuft ein Makro **außerhalb der Neuberechnung**: es wird nie
aus einer Zelle heraus aufgerufen, sondern immer **explizit ausgelöst** —
über **„▶ Ausführen"** in der neuen **„▶ Makros"**-Symbolleisten-Ansicht
oder per Klick auf ein **Button-Objekt** auf der Fläche (einfügbar über das
Symbol neben „Text einfügen"; welches Makro der Klick auslöst, wird im
Inspektor **oder** per Rechtsklick auf den Button direkt gewählt). Diese
Trennung ist bewusst: eine Formel, die als Nebeneffekt andere Zellen
verändert, würde die Neuberechnungsreihenfolge undefiniert machen (genau
das vermeidet dieses Feature) — ein Makro ist stattdessen dieselbe Art
Mutation, die auch ein Klick in der UI auslösen würde, nur mit
`FOR`/`IF`/`RETURN`-Kontrollfluss verkettet (dieselbe Sprache wie „Mit
Schleife"-Funktionen, s. o.).
**Elf Schreib-Aktionen**, adressiert mit der gewohnten
`Blatt!Tabelle::Zelle`-Syntax:
| Aktion | Wirkung |
|---|---|
| `SETVALUE(Ziel; Wert)` | schreibt `Wert` in eine Zelle oder — als Ziel ein Bereich — denselben Wert in jede Zelle darin (wie Excels `Strg+Enter`). Beginnt der (ggf. per `&` zusammengesetzte) Text mit `=`, wird er wie getippt zur echten Formel. |
| `INSERTROW`/`DELETEROW`/`INSERTCOL`/`DELETECOL(TabellenRef; Index)` | dieselbe dokumentweite Formel-Referenz-Umschreibung wie das manuelle Einfügen/Löschen (s. o.) |
| `ADDTABLE(BlattRef; Name)`, `RENAMETABLE`/`DELETETABLE(TabellenRef)` | Tabellen anlegen/umbenennen/löschen |
| `ADDSHEET(Name)`, `RENAMESHEET`/`DELETESHEET(BlattRef)` | Blätter anlegen/umbenennen/löschen |
`TabellenRef`/`BlattRef` sind Text (`"Umsatz"` oder `"Blatt2!Kosten"`,
identisch zur Formel-Qualifizierung aufgelöst) — auch berechenbar, z. B.
`ADDTABLE("Blatt1"; "Kopie_" & COUNT(Umsatz!A:A))`. Nur `SETVALUE`s erstes
Argument ist ein echter Zell-/Bereichsbezug (Literal, Parameter,
Schleifenvariable); alle anderen Argumente sind normale Ausdrücke.
- **Jeder Zell-/Bereichsbezug muss die Tabelle nennen** (`Umsatz::A1`, nie
bloß `A1`) — anders als eine Zelle hat ein Makro keine „Heimat-Tabelle",
die ein unqualifizierter Bezug meinen könnte. Beim Speichern geprüft, mit
klarer Fehlermeldung.
- **`RETURN`** beendet die Ausführung vorzeitig (z. B. um eine Schleife
früh zu verlassen); ein `RETURN "text"` wird zur Erfolgsmeldung im Toast
statt des generischen „Makro ausgeführt.".
- **Ein ganzer Lauf zählt als ein Undo-Schritt** — läuft er synchron durch
(immer der Fall), fasst die bestehende, zeitbasierte Undo/Redo-Historie
ihn automatisch zu einem `Strg`/`Cmd`+`Z` zusammen, ohne eigene
Transaktionslogik.
- **Ein Fehler mitten im Lauf bricht sofort ab** (unbekannte Tabelle,
fehlerhafter Wert, …) und zeigt die Ursache als Fehler-Toast — bereits
ausgeführte Schritte bleiben stehen (kein automatisches Rollback,
bewusst; per Undo rückgängig machbar).
- **Eigenes, kleineres Schrittbudget** (1.000 statt der 10.000 einer
gewöhnlichen `FOR`-Schleife) — eine Makro-Schleife kann pro Durchlauf
eine volle Neuberechnung auslösen (bei einer Struktur-Aktion), ein zu
großzügiges Budget wäre dort ein echtes Einfrier-Risiko.
- **Kein Makro ruft ein anderes Makro auf** (keine solche Aktion existiert)
— eliminiert jedes Makro-triggert-Makro-Zyklus-Risiko von vornherein.
- Eine im Makro aufgerufene **benutzerdefinierte Funktion** funktioniert
normal (berechnet einen Wert), kann aber selbst nie schreiben — ihr
Funktionskörper wird immer ohne Schreib-Rechte geparst.
**Bewusst ohne** (Stand jetzt): Makros haben keine Parameter (ein Klick
übergibt keine Argumente); mehrere Struktur-Aktionen in einer Schleife
lösen weiterhin je eine eigene Neuberechnung aus statt gebündelt einmal am
Ende (das kleinere Schrittbudget hält das Restrisiko klein, spätere
Optimierung möglich); ein `TabellenRef`-String wird am *ersten* `!`
gesplittet (ein Tabellenname mit eigenem `!` darin ist so nicht
adressierbar).
### Zeilen & Spalten einfügen/löschen
Rechtsklick auf einen **Zeilen-** oder **Spaltenkopf** öffnet ein Kontextmenü
@@ -1083,11 +1150,40 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
Fehler ohne Nebenwirkung, unbekannter Tabellenname → `#REF`, v1→v2 beim
Import; CSV Übersicht-Export→Re-Import gleiche Zahlen, RFC-4180-Quoting,
`=1+2`→3, Locale-Zahlen.
- **Makros** — Grammatik (`parser.spec.ts`): `NAME(args)` an Anweisungs-
position nur mit `allowWrites` (nie in einem gewöhnlichen Funktionskörper),
`;`-Argumente inkl. übersprungener Slots, Zuweisung bleibt eindeutig
unterscheidbar. Interpreter (`macros.spec.ts`): `SETVALUE`-Zielauflösung
auf Literal/Bereich/Schleifenvariable, `RETURN` als Kurzschluss inkl.
String-Nachricht, Fall-Through → `null`, alle elf Aktionen gegen einen
Mock-`MacroWriteHost`, das kleinere Makro-Schrittbudget greift bei einer
strukturell-schweren Schleife, ein verschachtelter Aufruf einer
benutzerdefinierten Funktion in einem `SETVALUE`-Wert funktioniert und
kann selbst nicht schreiben. Store-Ebene (`document.spec.ts`): Validierung
(unqualifizierte Referenz abgelehnt, Namenskollision, reservierte
Aktionsnamen als lokale Variable abgelehnt), alle elf Aktionen live gegen
ein echtes Dokument inkl. dokumentweiter Referenz-Umschreibung bei
`INSERTROW`, **der wichtigste Test**: `SETVALUE` → `INSERTROW` → `SETVALUE`
in einem Lauf beweist, dass kein Host-Zugriff nach der strukturellen
Aktion auf einer veralteten Workbook-Referenz hängen bleibt, ein ganzer
synchroner Lauf ist genau ein Undo-Schritt, `DELETESHEET` verweigert das
letzte Blatt als echten Fehlschlag statt stillem No-op, `ADDTABLE` auf ein
*anderes* Blatt lässt die aktive Ansicht/Auswahl unangetastet (reiner
Seiteneffekt-Fix, live per Playwright gefunden), JSON-Round-Trip.
---
## Bekannte Grenzen
- **Makros — UI-Ebene** (`ButtonView.vue`, `MacrosModal.vue`, der Button-
Zweig in `CanvasObject.vue`/`SettingsPanel.vue`) rein UI-seitig
implementiert, ohne eigene Unit-Tests — wie bei Tabellen-Größenänderung/
Abhängigkeiten-Panel/Ctrl+A oben, nur live per Playwright verifiziert
(Makro anlegen, per Listen-Button und per Button-Objekt ausführen,
Rechtsklick-Zuweisung, Fehlerfall zeigt einen Fehler-Toast statt
abzustürzen). Die Makro-*Sprache* selbst (Grammatik, Interpreter,
Store-Aktionen) ist dagegen vollständig durch Engine-/Store-Tests
abgedeckt (s. „Tests" unten).
- **Zeilen/Spalten einfügen/löschen** (s. „Zeilen & Spalten einfügen/löschen"
oben) bewusst ohne: Mehrfachauswahl (immer genau eine Zeile/Spalte pro
Aufruf), automatisches Nachziehen von `CellFormat`-Regeln über die
@@ -1180,7 +1276,8 @@ src/
store/ # reaktiver Dokument-Store (v2 Canvas-Modell), Formatierung
io/ # csv / json (Eigenbau, inkl. v1→v2), xlsx (SheetJS, lazy)
components/ # AppToolbar, SettingsPanel, FormulaBar, SheetCanvas, CanvasObject,
# TableGrid, ChartView, TextBox, SheetTabs, StatusBar, TheLogo, …
# TableGrid, ChartView, TextBox, ButtonView, MacrosModal, SheetTabs,
# StatusBar, TheLogo, …
numolus-logo.svg # Marke: drei überlappende Pop-Art-Quadrate + Schriftzug
config/brand.ts # Herausgeber-Metadaten (wicom.li) + Social-Media-Liste
composables/ # Theme, Toasts