From 5f43c19878ac54c8081684c2ce1241d805302c17 Mon Sep 17 00:00:00 2001
From: Oliver Walter
Date: Sat, 6 Jun 2026 15:37:42 +0200
Subject: [PATCH] initial
---
.gitignore | 14 +
Cargo.toml | 119 ++++++++
LICENSE | 24 ++
README.md | 174 +++++++++++
documentation.md | 357 +++++++++++++++++++++++
end2end/.gitignore | 3 +
end2end/package.json | 15 +
end2end/playwright.config.ts | 105 +++++++
end2end/tests/example.spec.ts | 9 +
end2end/tsconfig.json | 109 +++++++
public/favicon.ico | Bin 0 -> 15406 bytes
src/app.rs | 43 +++
src/components/filter_bar.rs | 168 +++++++++++
src/components/mod.rs | 3 +
src/components/tool_form.rs | 366 +++++++++++++++++++++++
src/components/tool_list.rs | 533 ++++++++++++++++++++++++++++++++++
src/lib.rs | 12 +
src/main.rs | 46 +++
src/models/mod.rs | 1 +
src/models/tool.rs | 235 +++++++++++++++
src/server/api.rs | 123 ++++++++
src/server/export.rs | 72 +++++
src/server/mod.rs | 5 +
src/server/store.rs | 67 +++++
style/main.scss | 270 +++++++++++++++++
toollist_data.md | 8 +
26 files changed, 2881 insertions(+)
create mode 100644 .gitignore
create mode 100644 Cargo.toml
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 documentation.md
create mode 100644 end2end/.gitignore
create mode 100644 end2end/package.json
create mode 100644 end2end/playwright.config.ts
create mode 100644 end2end/tests/example.spec.ts
create mode 100644 end2end/tsconfig.json
create mode 100644 public/favicon.ico
create mode 100644 src/app.rs
create mode 100644 src/components/filter_bar.rs
create mode 100644 src/components/mod.rs
create mode 100644 src/components/tool_form.rs
create mode 100644 src/components/tool_list.rs
create mode 100644 src/lib.rs
create mode 100644 src/main.rs
create mode 100644 src/models/mod.rs
create mode 100644 src/models/tool.rs
create mode 100644 src/server/api.rs
create mode 100644 src/server/export.rs
create mode 100644 src/server/mod.rs
create mode 100644 src/server/store.rs
create mode 100644 style/main.scss
create mode 100644 toollist_data.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6985cf1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..fcaa11f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,119 @@
+[package]
+name = "toollist"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+leptos = { version = "0.8.0" }
+leptos_router = { version = "0.8.0" }
+leptos_meta = { version = "0.8.0" }
+serde = { version = "1", features = ["derive"] }
+axum = { version = "0.8.0", optional = true }
+console_error_panic_hook = { version = "0.1", optional = true }
+leptos_axum = { version = "0.8.0", optional = true }
+tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
+wasm-bindgen = { version = "=0.2.106" }
+wasm-bindgen-futures = { version = "0.4" }
+web-sys = { version = "0.3", features = [
+ "Event",
+ "EventTarget",
+ "File",
+ "FileList",
+ "FileReader",
+ "HtmlDialogElement",
+ "HtmlInputElement",
+ "Window",
+] }
+uuid = { version = "1", features = ["v4"], optional = true }
+chrono = { version = "0.4", optional = true }
+toml = { version = "0.8", optional = true }
+
+[features]
+hydrate = [
+ "leptos/hydrate",
+ "dep:console_error_panic_hook",
+]
+ssr = [
+ "dep:axum",
+ "dep:tokio",
+ "dep:leptos_axum",
+ "dep:uuid",
+ "dep:chrono",
+ "dep:toml",
+ "leptos/ssr",
+ "leptos_meta/ssr",
+ "leptos_router/ssr",
+]
+
+# Defines a size-optimized profile for the WASM bundle in release mode
+[profile.wasm-release]
+inherits = "release"
+opt-level = 'z'
+lto = true
+codegen-units = 1
+panic = "abort"
+
+[package.metadata.leptos]
+# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
+output-name = "toollist"
+
+# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
+site-root = "target/site"
+
+# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
+# Defaults to pkg
+site-pkg-dir = "pkg"
+
+# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //toollist.css
+style-file = "style/main.scss"
+# Assets source dir. All files found here will be copied and synchronized to site-root.
+# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
+#
+# Optional. Env: LEPTOS_ASSETS_DIR.
+assets-dir = "public"
+
+# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
+site-addr = "0.0.0.0:3000"
+
+# The port to use for automatic reload monitoring
+reload-port = 3001
+
+# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
+# [Windows] for non-WSL use "npx.cmd playwright test"
+# This binary name can be checked in Powershell with Get-Command npx
+end2end-cmd = "npx playwright test"
+end2end-dir = "end2end"
+
+# The browserlist query used for optimizing the CSS.
+browserquery = "defaults"
+
+# The environment Leptos will run in, usually either "DEV" or "PROD"
+env = "DEV"
+
+# The features to use when compiling the bin target
+#
+# Optional. Can be over-ridden with the command line parameter --bin-features
+bin-features = ["ssr"]
+
+# If the --no-default-features flag should be used when compiling the bin target
+#
+# Optional. Defaults to false.
+bin-default-features = false
+
+# The features to use when compiling the lib target
+#
+# Optional. Can be over-ridden with the command line parameter --lib-features
+lib-features = ["hydrate"]
+
+# If the --no-default-features flag should be used when compiling the lib target
+#
+# Optional. Defaults to false.
+lib-default-features = false
+
+# The profile to use for the lib target when compiling for release
+#
+# Optional. Defaults to "release".
+lib-profile-release = "wasm-release"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4718ce3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,174 @@
+# đ§ Toollist
+
+Verwaltung von Werkzeugen und Material fĂŒr ein Renovierungs- / Sanierungsprojekt.
+
+---
+
+## Tech Stack
+
+| Schicht | Technologie |
+|---|---|
+| Sprache | Rust |
+| Framework | [Leptos 0.8](https://leptos.dev) â Full-Stack SSR + Hydration |
+| Server | Axum 0.8 |
+| Datenspeicher | TOML-Datei (`data/tools.toml`) |
+| Styles | SCSS (`style/main.scss`) |
+| Build-Tool | [cargo-leptos](https://github.com/leptos-rs/cargo-leptos) |
+
+---
+
+## Entwicklung
+
+```bash
+# Entwicklungsserver mit Hot-Reload starten
+cargo leptos watch
+
+# Browser öffnen
+http://localhost:3000
+
+# Im Heimnetz erreichbar unter
+http://:3000
+```
+
+> **Hinweis:** `site-addr` ist auf `0.0.0.0:3000` gesetzt, die App ist also
+> direkt im lokalen Netz erreichbar â kein Login, keine Auth.
+
+### Voraussetzungen
+
+```bash
+cargo install cargo-leptos
+rustup target add wasm32-unknown-unknown
+```
+
+> **wasm-bindgen Version:** Die Cargo.toml pinnt `wasm-bindgen = "=0.2.106"`,
+> damit es mit dem in cargo-leptos 0.3.6 gebĂŒndelten CLI ĂŒbereinstimmt.
+> Falls cargo-leptos aktualisiert wird, ggf. beide Versionen anpassen.
+
+---
+
+## Projektstruktur
+
+```
+toollist/
+âââ Cargo.toml AbhĂ€ngigkeiten + cargo-leptos Konfiguration
+âââ data/
+â âââ tools.toml Datenspeicher (wird automatisch angelegt)
+âââ public/ Statische Dateien
+âââ style/
+â âââ main.scss Globales Stylesheet
+âââ src/
+ âââ main.rs Server-Einstiegspunkt (Axum)
+ âââ lib.rs Crate-Root, Hydration-Einstieg
+ âââ app.rs App-Komponente + Routing
+ âââ models/
+ â âââ tool.rs Datenmodell (Structs & Enums)
+ âââ server/
+ â âââ store.rs TOML lesen/schreiben [nur SSR]
+ â âââ api.rs Server Functions (HTTP-Endpunkte)
+ âââ components/
+ âââ tool_list.rs Hauptansicht â Liste, Filter, CSV-Import
+ âââ tool_form.rs Erstellen / Bearbeiten Formular
+ âââ filter_bar.rs Filterleiste (Suche, Status, PrioritĂ€t, Tag)
+```
+
+---
+
+## Datenmodell
+
+### `Tool` (zentrale EntitÀt)
+
+| Feld | Typ | Beschreibung |
+|---|---|---|
+| `id` | `String` (UUID) | Eindeutige ID, serverseitig generiert |
+| `name` | `String` | Name des Werkzeugs |
+| `anzahl` | `Anzahl` | Konkrete Zahl oder âMehrere" |
+| `beschaffung` | `Beschaffung` | Wie das Werkzeug beschafft wird |
+| `verwendung` | `Vec` | Liste von Verwendungszwecken |
+| `tags` | `Vec` | Stichwörter (z.B. `garten`, `schwer`) |
+| `status` | `Status` | `Offen` · `Organisiert` · `VorOrt` |
+| `prioritaet` | `Prioritaet` | `Hoch` · `Mittel` · `Niedrig` |
+| `notizen` | `Option` | Freitext |
+| `verantwortlich` | `Option` | Wer kĂŒmmert sich darum? |
+| `erstellt_am` | `String` (ISO-8601) | Erstellungszeitpunkt |
+
+### `Beschaffung`
+
+| Art | Zusatzfelder |
+|---|---|
+| `Unbekannt` | â |
+| `InBesitz` | â |
+| `Leihen` | `von: Option` |
+| `Mieten` | `wo: Option`, `preis: Option` |
+| `Kaufen` | `wo: Option`, `preis: Option` |
+
+### TOML-Format (Beispiel)
+
+```toml
+[[tools]]
+id = "3f2a1b..."
+name = "Schubkarren"
+status = "Offen"
+prioritaet = "Mittel"
+erstellt_am = "2025-01-01T10:00:00Z"
+verwendung = ["Schutt abtransportieren"]
+tags = ["transport", "garten"]
+
+[tools.anzahl]
+zahl = 2
+
+[tools.beschaffung]
+art = "InBesitz"
+```
+
+---
+
+## Server Functions (API)
+
+Alle Endpunkte unter `/api/` â automatisch von Leptos registriert.
+
+| Funktion | Beschreibung |
+|---|---|
+| `tools_laden()` | Alle Werkzeuge laden |
+| `tool_erstellen(input)` | Neues Werkzeug anlegen |
+| `tool_aktualisieren(id, input)` | Bestehendes Werkzeug aktualisieren |
+| `tool_loeschen(id)` | Werkzeug löschen |
+| `csv_importieren(inhalt)` | CSV-Text importieren (Name + Anzahl) |
+
+---
+
+## CSV-Import
+
+Einfaches Format â nur `name` und `anzahl` werden ausgewertet.
+Fehlende oder unbekannte Werte werden toleriert.
+
+```csv
+name,anzahl
+Schubkarren,2
+Betonmischer,1
+Nadelrolle,mehrere
+Besen,
+```
+
+Importierte EintrÀge erhalten Standardwerte:
+`Status = Offen`, `PrioritÀt = Mittel`, `Beschaffung = Unbekannt`.
+
+---
+
+## Features (Stand)
+
+- [x] Werkzeuge anlegen, bearbeiten, löschen
+- [x] Listenansicht (Tabelle)
+- [x] Filter: Status, PrioritÀt, Tag, Freitextsuche
+- [x] Dynamisches Formular (Felder je nach Beschaffungsart)
+- [x] CSV-Import (Name + Anzahl)
+- [x] Datenspeicherung in `data/tools.toml`
+- [x] Im Heimnetz erreichbar (`0.0.0.0:3000`)
+
+## Mögliche nÀchste Schritte
+
+- [ ] Detailansicht / Klappreihe (Verwendungsliste, alle Felder)
+- [ ] Verwendungsliste im Formular als dynamische Liste (statt Textarea)
+- [ ] Bauabschnitt als eigenes Feld (oder konsequent via Tags)
+- [ ] Druckansicht / Export
+- [ ] Sortierung der Tabellenspalten
+- [ ] ZĂ€hler in der Kopfzeile (z.B. â3 von 12 offen")
diff --git a/documentation.md b/documentation.md
new file mode 100644
index 0000000..6911420
--- /dev/null
+++ b/documentation.md
@@ -0,0 +1,357 @@
+# Toollist â Design-Dokumentation
+
+Dieses Dokument hĂ€lt die HintergrĂŒnde und AbwĂ€gungen fest, die wĂ€hrend der
+Planungsphase getroffen wurden. Es dient als Nachschlagewerk, wenn zukĂŒnftige
+Erweiterungen Fragen ĂŒber den ursprĂŒnglichen Kontext aufwerfen.
+
+---
+
+## 1. Kontext & Ziel
+
+Das Tool entstand aus einer einfachen Textdatei (`toollist_notes.txt`), in der
+Werkzeuge fĂŒr eine Haus-Renovierung grob notiert waren â ohne einheitliche
+Struktur, ohne Status-Tracking, ohne Möglichkeit fĂŒr mehrere Personen gleichzeitig
+darauf zuzugreifen.
+
+**Kernziel:** Eine einfache, im Heimnetz zugÀngliche Web-App, mit der mehrere
+Personen gemeinsam verwalten können, welche Werkzeuge fĂŒr die Renovierung benötigt
+werden, wie sie beschafft werden und ob sie bereits organisiert sind.
+
+---
+
+## 2. Technologie-Entscheidungen
+
+### 2.1 Warum Full-Stack Rust mit Leptos?
+
+Das Projekt startete als Rust-Crate (`Cargo.toml` war bereits vorhanden).
+FĂŒr ein Web-Tool mit mehreren Nutzern wurden drei Optionen verglichen:
+
+| Option | Beschreibung | Verworfen weil |
+|---|---|---|
+| **CLI** | Befehle im Terminal | Kein Mehrbenutzerzugriff, keine Ăbersicht |
+| **TUI** | Interaktive Terminal-UI | Kein Netzwerkzugriff ohne zusÀtzlichen Server |
+| **Web** | Browser-basiert | â GewĂ€hlt |
+
+Innerhalb der Web-Option:
+
+| Option | Beschreibung | Entscheidung |
+|---|---|---|
+| Axum + Vanilla JS | Rust-Backend, JS-Frontend | Zwei Sprachen, mehr Aufwand |
+| Axum + React/Svelte | Beste UX, zwei Codebasen | Zu aufwĂ€ndig fĂŒr ein internes Tool |
+| **Leptos SSR** | Full-Stack Rust, WASM | â GewĂ€hlt â alles in einer Sprache |
+
+**Leptos SSR + Hydration** wurde gewÀhlt, weil:
+- Alles bleibt in Rust (Typsicherheit zwischen Server und Client)
+- Server-seitiges Rendering fĂŒr den initialen Load
+- Die ReaktivitÀt (Signale, Ressourcen) ersetzt ein separates State-Management
+
+### 2.2 Warum TOML als Datenspeicher?
+
+Es wurden drei Optionen diskutiert:
+
+| Option | Vorteile | Nachteile |
+|---|---|---|
+| **TOML-Datei** | Menschenlesbar, versionierbar mit Git, kein DB-Server | Kein Query-Layer, Concurrent Writes brauchen Locking |
+| JSON-Datei | Besser fĂŒr komplexe Typen, weit verbreitet | Weniger lesbar als TOML |
+| SQLite | Besser fĂŒr Abfragen, bewĂ€hrt fĂŒr gleichzeitigen Zugriff | Overhead fĂŒr ein kleines internes Tool |
+
+**TOML wurde gewÀhlt**, da die Datei direkt lesbar und editierbar sein soll,
+das Projekt nur fĂŒr wenige Nutzer im Heimnetz ausgelegt ist (kein
+Hochlast-Concurrent-Access), und die Werkzeug-Liste manuell wartbar bleiben soll.
+
+> **Bekannte EinschrÀnkung:** Bei gleichzeitigen Schreibzugriffen mehrerer
+> Nutzer gibt es kein Locking. FĂŒr den Heimnetz-Einsatz mit wenigen Personen
+> ist das akzeptabel. Bei Bedarf kann ein `tokio::sync::Mutex` in den Axum-State
+> eingebaut werden.
+
+### 2.3 Warum kein Login / keine Auth?
+
+Bewusste Entscheidung: Die App ist ausschlieĂlich im lokalen Heimnetz erreichbar
+und wird von einer kleinen, bekannten Personengruppe genutzt. Eine
+Nutzerverwaltung wĂŒrde den Aufwand signifikant erhöhen, ohne einen echten
+Mehrwert fĂŒr diesen Kontext zu liefern.
+
+Das Feld `verantwortlich` (wer kĂŒmmert sich um ein Werkzeug) ist ein
+Freitextfeld â kein Verweis auf einen eingeloggten Nutzer.
+
+---
+
+## 3. Datenmodell â Designentscheidungen
+
+### 3.1 Werkzeug-Felder
+
+**Kernfelder** (aus der ursprĂŒnglichen Anforderung):
+
+| Feld | Entscheidung | BegrĂŒndung |
+|---|---|---|
+| `name` | `String` | Freitext, keine Normalisierung nötig |
+| `anzahl` | `Anzahl`-Struct | Entweder konkrete Zahl oder âMehrere" (z.B. bei Eimern, Handschuhen) |
+| `beschaffung` | Flache `Beschaffung`-Struct | KontextabhÀngige Zusatzfelder je nach Art (s.u.) |
+| `verwendung` | `Vec` | Mehrere Verwendungszwecke möglich |
+| `tags` | `Vec` | Flexible Kategorisierung ohne fixe Taxonomie |
+
+**Zusatzfelder** (im Design-GesprÀch ergÀnzt):
+
+| Feld | Entscheidung | BegrĂŒndung |
+|---|---|---|
+| `status` | Enum `Offen/Organisiert/VorOrt` | Wichtigster Fortschrittsindikator auf einen Blick |
+| `prioritaet` | Enum `Hoch/Mittel/Niedrig` | Fokussierung auf kritische Werkzeuge |
+| `notizen` | `Option` | Freitext fĂŒr alles was sonst nirgends passt |
+| `verantwortlich` | `Option` | Optional â wer beschafft das Werkzeug |
+| `erstellt_am` | `String` (ISO-8601) | NĂŒtzlich fĂŒr Sortierung und Nachvollziehbarkeit |
+
+**Bewusst nicht aufgenommen (beim MVP):**
+- Bauabschnitt als eigenes Feld â wird ĂŒber Tags abgebildet (z.B. `tag: estrich`, `tag: elektro`)
+- VollstĂ€ndiges Changelog / Audit-Log â zu viel Overhead fĂŒr diesen Kontext
+
+### 3.2 Warum `Anzahl` als Struct statt Enum?
+
+UrsprĂŒnglich war ein Enum geplant:
+```rust
+// Erstentwurf (verworfen)
+enum Anzahl {
+ Zahl(u32),
+ Mehrere,
+}
+```
+
+**Problem:** TOML serialisiert gemischte Enum-Varianten (eine als Zahl, eine als
+String) je nach Variante unterschiedlich, was zu Serialisierungsfehlern fĂŒhren
+kann â insbesondere innerhalb von `[[tools]]`-Arrays, wo alle Felder als
+Inline-Tables dargestellt werden.
+
+**Lösung:** Eine flache Struct mit optionalem Zahlenfeld:
+```rust
+struct Anzahl {
+ zahl: Option, // None = "Mehrere"
+}
+```
+
+TOML-Darstellung ist damit eindeutig:
+```toml
+# Konkrete Zahl:
+[tools.anzahl]
+zahl = 2
+
+# "Mehrere":
+[tools.anzahl]
+# (kein zahl-Feld â None)
+```
+
+### 3.3 Warum `Beschaffung` als flache Struct statt Enum?
+
+UrsprĂŒnglicher Plan:
+```rust
+// Erstentwurf (verworfen)
+enum Beschaffung {
+ Unbekannt,
+ InBesitz,
+ Leihen { von: String },
+ Mieten { wo: String, preis: Option },
+ Kaufen { wo: String, preis: Option },
+}
+```
+
+**Problem:** Rust-Enums mit Struct-Varianten und TOML-Serialisierung können
+sich je nach verwendetem Serde-Tag-Modus (`untagged`, `tag`, extern) unterschiedlich
+verhalten und sind fehleranfÀllig, besonders mit `Option`-Feldern in Varianten.
+
+**Lösung:** Flache Struct mit einem `BeschaffungsArt`-Enum fĂŒr den Typ und
+separaten optionalen Feldern:
+```rust
+struct Beschaffung {
+ art: BeschaffungsArt, // "Unbekannt" | "InBesitz" | "Leihen" | "Mieten" | "Kaufen"
+ von: Option, // nur fĂŒr Leihen
+ wo: Option, // nur fĂŒr Mieten/Kaufen
+ preis: Option, // nur fĂŒr Mieten/Kaufen
+}
+```
+
+**Vorteil:** Einfache, TOML-kompatible Serialisierung. Im Formular werden die
+Felder `von`, `wo`, `preis` je nach gewÀhlter `art` dynamisch ein-/ausgeblendet.
+
+**Bekannter Trade-off:** Nicht alle Felddkombinationen sind sinnvoll (z.B. `von`
+bei `Kaufen`). Die Validierung liegt beim Formular, nicht im Typ selbst.
+
+### 3.4 Warum `String` fĂŒr `id` und `erstellt_am`?
+
+FĂŒr `id` wĂ€re `uuid::Uuid` typsicherer. FĂŒr `erstellt_am` wĂ€re
+`chrono::DateTime` korrekter. Beide Typen haben jedoch **WASM-KompatibilitÀtsprobleme**:
+
+- `uuid::Uuid` mit dem `v4`-Feature benötigt `getrandom`, das fĂŒr WASM die
+ `js`-Feature-Flag braucht.
+- `chrono::Utc::now()` benötigt fĂŒr WASM die `wasmbind`-Feature-Flag.
+
+Da beide Werte **ausschlieĂlich serverseitig generiert** werden (in Server
+Functions) und der Client sie nur als opaque Strings liest, ist `String` die
+pragmatische Wahl:
+
+```rust
+// In Tool::neu() â nur SSR
+#[cfg(feature = "ssr")]
+impl Tool {
+ pub fn neu(input: NewTool) -> Self {
+ Tool {
+ id: uuid::Uuid::new_v4().to_string(),
+ erstellt_am: chrono::Utc::now().to_rfc3339(),
+ // ...
+ }
+ }
+}
+```
+
+`uuid` und `chrono` sind daher als **optionale, SSR-only** Dependencies deklariert.
+
+---
+
+## 4. Architektur-Entscheidungen (Leptos)
+
+### 4.1 SSR-Rendering-Modus
+
+Leptos unterstĂŒtzt verschiedene Rendering-Modi:
+
+| Modus | Beschreibung | Entscheidung |
+|---|---|---|
+| CSR | Nur WASM, kein Server-Rendering | Kein Dateisystemzugriff möglich |
+| **SSR + Hydration** | Server rendert HTML, WASM ĂŒbernimmt | â GewĂ€hlt |
+| Islands | Nur bestimmte Komponenten hydratisieren | Komplexer, kein Vorteil hier |
+
+SSR ist notwendig, weil der Server die TOML-Datei lesen muss. Mit CSR mĂŒsste
+ein separater API-Server betrieben werden.
+
+### 4.2 Server Functions statt REST-API
+
+Leptos `#[server]`-Funktionen generieren automatisch:
+- Serverseitig: die echte Implementierung
+- Clientseitig: einen HTTP-Stub, der den Server aufruft
+
+Das erspart eine manuelle REST-API mit separaten Routen, Serialisierung und
+Client-Code. Alle Typen werden geteilt â wenn sich das Datenmodell Ă€ndert,
+bricht der Compiler sowohl Server- als auch Client-Code.
+
+### 4.3 Suspense-Grenze fĂŒr Resource-Zugriffe
+
+**Problem:** In Leptos 0.8 dĂŒrfen `Resource`-Zugriffe in Hydration-Mode
+ausschlieĂlich innerhalb einer ``-Komponente erfolgen. Anderenfalls
+drohen Hydration-Mismatches und eine Warnung im Log.
+
+**Falsch (ursprĂŒnglicher Ansatz):**
+```rust
+// AuĂerhalb Suspense â Warnung
+let alle_tags = Signal::derive(move || {
+ tools_resource.get() // â Resource-Zugriff auĂerhalb Suspense!
+ .and_then(|r| r.ok())
+ // ...
+});
+```
+
+**Richtig (finaler Ansatz):**
+```rust
+"LĂ€dtâŠ"
}>
+ {move || {
+ tools_resource.get().map(|result| { // â Zugriff korrekt innerhalb
+ let tools = result.unwrap_or_default();
+ // Tags und gefilterte Liste hier berechnen
+ view! { /* Tabelle */ }
+ })
+ }}
+
+```
+
+**Konsequenz:** `FilterBar` und Tabelle liegen beide innerhalb der
+Suspense-Grenze. Bei FilterÀnderungen (reaktive `filter: RwSignal`) re-rendert
+der Suspense-Block, ohne den Ladeindikator zu zeigen (Resource ist bereits geladen).
+
+### 4.4 ReaktivitÀt: Wie Updates die Liste neu laden
+
+Nach jeder Mutation (erstellen, aktualisieren, löschen) muss die Liste neu
+geladen werden. Das geschieht ĂŒber einen einfachen **Versions-ZĂ€hler**:
+
+```rust
+let version = RwSignal::new(0u32);
+let neu_laden = move || version.update(|v| *v += 1);
+
+// Resource hÀngt von version ab
+let tools_resource = Resource::new(
+ move || version.get(), // â neu_laden() erhöht version â Resource re-fetcht
+ |_| tools_laden(),
+);
+```
+
+Das ist bewusst einfach gehalten. Eine Alternative wÀre `ServerAction` mit
+automatischem `resource.refetch()`, was aber mehr Boilerplate erfordert.
+
+---
+
+## 5. Komponentenstruktur
+
+```
+ToolList (Hauptkomponente â hĂ€lt den gesamten Zustand)
+â
+âââ ToolForm (Formular â erscheint wenn bearbeitungs_modus != None)
+â âââ Callback on_fertig / on_abbrechen â zurĂŒck zur Liste
+â
+âââ
+ âââ FilterBar (bekommt filter: RwSignal + alle_tags: Vec)
+ âââ
(iteriert ĂŒber gefilterte Tools)
+```
+
+**Zustand-Management in `ToolList`:**
+
+| Signal | Typ | Zweck |
+|---|---|---|
+| `version` | `RwSignal` | Löst Resource-Reload aus |
+| `bearbeitungs_modus` | `RwSignal