{# Der Bausatz — die Sprache, in der jede Portalseite ab Etappe 2 spricht (Spec 2026-09-02, §4). Makros und keine Teilvorlagen: Sie werden in Schleifen und mehrfach je Seite gebraucht, und `{% include %}` teilte sich dabei den Kontext. Kein Makro setzt ein `style`-Attribut. Die Richtlinie belegt Stil-BLOECKE per Hash und deckt Attribute nicht; die Werte stehen in `statisch/portal.css` unter demselben Klassennamen (`tests/test_csp.py`). Die Klasse der Karte heisst `kasten` und nicht `karte`: `.karte` gehoert der Kategorie-Kachel des Meldeformulars, und deren Skript bleibt unveraendert (Spec §5). Dasselbe gilt fuer `kapsel` statt `chip`. `tests/test_bausatz.py` rendert jedes Makro einmal. **Jedes `href` traegt `|ziel`.** Autoescape schliesst den Ausbruch aus dem Attribut, sagt aber nichts ueber das Schema; `javascript:` kaeme unveraendert durch. Der Filter in `vorlage.py` laesst genau einen Pfad der eigenen Herkunft durch. **Jedes Makro verlangt sein Wort.** Eine Kapsel, eine Meldung, ein Leerzustand oder ein Knopf ohne Text ist Farbe ohne Aussage — genau das, was `gestaltung.md` §1 verbietet, und am Bildschirm sieht ein leerer farbiger Kasten aus wie Absicht. Alle vier brechen darum ab (siebter Code-Review 2026-09-02). **Der Ton steht als `t-` in der Klassenliste**, nicht als blosser Name. Das ist die Konvention dieses Hauses (`t-offen`, `t-wartend` am Meldungsverlauf) und sie loest ein gemessenes Problem: `.info`, `.erfolg` und `.erledigt` sind seit Story 4.5 GLOBALE Regeln mit eigenem Radius, Polster und Rand. Ohne Praefix gewaenne die spaetere globale Regel, und aus der 20-px-Pille wuerde ein Kasten mit 0.9rem Polster, aus dem der Text laeuft (sechster Code-Review 2026-09-02). Dieselbe Ueberlegung fuer `zusatz` statt `freiwillig`. **`ton` und `art` sind Aufzaehlungen.** Ein Wert daneben faerbte bis zum zweiten Code-Review vom 2026-09-02 gar nichts: `meldung("warnung", …)` blieb neutralgrau, `knopf("x", "primaer")` stand ohne Flaeche und ohne Rahmen da. Kein Abbruch, kein roter Test, und am Bildschirm sieht ein tonloser Kasten aus wie Absicht. Die Listen unten brechen den Aufruf ab. #} {% set TOENE = ('info', 'achtung', 'neutral', 'erfolg', 'erledigt', 'gefahr', 'marke') %} {% set ARTEN = ('haupt', 'zweit', 'still', 'gefahr') %} {# Ein Statuswort in seiner Tonflaeche. Das Wort ist Pflicht; die Farbe allein sagt nichts (gestaltung.md §1). #} {% import "bausteine/symbol.html" as s %} {% macro kapsel(wort, ton='neutral', klasse='') -%} {% if ton not in TOENE %}{{ UNBEKANNTER_TON }}{% endif -%} {% if not wort %}{{ EINE_KAPSEL_OHNE_WORT_IST_NUR_FARBE }}{% endif -%} {{ wort }} {%- endmacro %} {# Ein Hinweiskasten: sagt, was ist, und wo es geht, was hilft. Mit `text` oder als `{% call %}`-Block, wenn der Inhalt Auszeichnung traegt. #} {% macro meldung(ton, text='', titel='', aktion_href='', aktion_wort='') -%} {% if aktion_href and not aktion_wort %}{{ EIN_ZIEL_OHNE_WORT_IST_EIN_UNSICHTBARER_VERWEIS }}{% endif -%} {% if aktion_wort and not aktion_href %}{{ EIN_WORT_OHNE_ZIEL_IST_KEIN_VERWEIS }}{% endif -%} {% if ton not in TOENE %}{{ UNBEKANNTER_TON }}{% endif -%} {% if not text and not titel and caller is not defined %}{{ EINE_MELDUNG_OHNE_TEXT_IST_NUR_FARBE }}{% endif -%} {% if text and caller is defined %}{{ ENTWEDER_TEXT_ODER_BLOCK_NICHT_BEIDES }}{% endif -%}
{% if titel %}{{ titel }} {% endif %}{% if caller is defined %}{{ caller() }}{% else %}{{ text }}{% endif %}{% if aktion_href %} {{ aktion_wort }}{% endif %}
{%- endmacro %} {# Zwei Leerzustaende, die nicht dasselbe sagen (Spec §8): `bestand` heisst «es gibt nichts», `filter` heisst «zu diesen Filtern gibt es nichts». Eine dritte Art gibt es nicht; der Name unten ist absichtlich nirgends definiert, damit `StrictUndefined` den Aufruf abbricht. **Zwei Sätze, nicht einer** (05.09.2026). Der erste sagt, WAS fehlt; der zweite, leiser, sagt WARUM oder was der nächste Schritt ist. Der Baustein kannte bis dahin nur einen, und die Portalsaetze packten deshalb beides in einen langen Satz. Das Immotool hat die zwei von Anfang an: «Noch keine Liegenschaft.» plus «Eine Liegenschaft ist eine Stockwerkeigentümer-Gemeinschaft, ein Mietobjekt oder eine MEG mit eigener Buchhaltung.» **Und `art` entscheidet über den Knopf**, nicht der Geschmack: `filter` bekommt IMMER einen Weg heraus (sonst sitzt man im leeren Filter fest), `bestand` bekommt keinen — dort gibt es nichts zurückzusetzen. Der Baustein bricht ab, wenn ein `filter` ohne Ziel kommt. #} {% macro leerzustand(art, text, hinweis='', aktion_href='', aktion_wort='') -%} {% if aktion_href and not aktion_wort %}{{ EIN_ZIEL_OHNE_WORT_IST_EIN_UNSICHTBARER_VERWEIS }}{% endif -%} {% if aktion_wort and not aktion_href %}{{ EIN_WORT_OHNE_ZIEL_IST_KEIN_VERWEIS }}{% endif -%} {% if art not in ('bestand', 'filter') %}{{ LEERZUSTAND_ART_MUSS_BESTAND_ODER_FILTER_SEIN }}{% endif -%} {% if not text %}{{ EIN_LEERZUSTAND_OHNE_SATZ_SAGT_NICHTS }}{% endif -%} {% if art == 'filter' and not aktion_href %}{{ EIN_LEERER_FILTER_BRAUCHT_DEN_WEG_HERAUS }}{% endif -%}
{{ text }}{% if aktion_href %} {{ aktion_wort }}{% endif %}{% if hinweis %}

{{ hinweis }}

{% endif %}
{%- endmacro %} {# Die Karte: Radius 12, Rahmen, 16 px innen, kein Schatten. Der Zaehler steht leise in Klammern hinter dem Titel. Nur als `{% call %}`. `zaehler is not none and != ''` und nicht bloss `if zaehler`: Die NULL ist ein Zaehlerstand und kein fehlender Wert. «Dateien (0)» sagt, dass nachgesehen wurde; «Dateien» laesst offen, ob die Zahl fehlt (sechster Code-Review 2026-09-02). Wer keinen Zähler will, gibt keinen mit. **`icon` und `weg` sind für die Bereichskarten des Dashboards** (04.09.2026). Sie standen zuerst in einem eigenen Baustein `bereichskarte.html`, der weggefallen ist — mit demselben Vertrag wie dieser: Titel Pflicht, Zähler in Klammern, `klasse` durchgereicht. Zwei Fassungen derselben Karte sind genau das, was im Immotool bis zum 2026-08-19 zwei Zählkacheln nebeneinander stehen liess, bis sie auseinandergelaufen waren (Code-Review 2026-09-04). Also EIN Kasten, mit zwei zusätzlichen Teilen im Kopf. **Der Weg ist ein Verweis im Kopf, nicht die ganze Karte.** Eine Karte, die als Ganzes verlinkt ist, verschluckt die Verweise darin — und in jeder Bereichskarte stehen welche. #} {% macro kasten(titel, zaehler='', klasse='', icon='', weg_href='', weg_wort='') -%} {% if not titel %}{{ EINE_KARTE_OHNE_TITEL_IST_EIN_KASTEN_OHNE_AUSSAGE }}{% endif -%} {% if weg_href and not weg_wort %}{{ EIN_ZIEL_OHNE_WORT_IST_EIN_UNSICHTBARER_VERWEIS }}{% endif -%} {% if weg_wort and not weg_href %}{{ EIN_WORT_OHNE_ZIEL_IST_KEIN_VERWEIS }}{% endif -%}

{% if icon %}{{ s.symbol(icon, 16) }}{% endif %}{{ titel }}{% if zaehler is not none and zaehler != '' %} ({{ zaehler }}){% endif %}{% if weg_href %}{{ weg_wort }}{% endif %}

{{ caller() }}
{%- endmacro %} {# Die Hinweiskarte: eine Seite, die nur etwas mitzuteilen hat. Vier Seiten dieser Art gibt es (unbekannter Zugang, zu viele Versuche, Absage angekommen, Quellarchiv fehlt), und bis zum 15.09.2026 trugen alle vier ihren Text nackt im `
`, während jede andere Portalseite ihren Inhalt in einer Karte trägt. Befund des Auftraggebers: «So sieht es nicht fertig aus.» **Warum nicht `kasten`.** Der trägt seinen Titel als `

` und gehört in eine Seite mit eigener Überschrift; hier IST die Karte die Seite, und ihr Titel ist das `

`. Das ist ein anderer Vertrag und nicht dieselbe Karte mit anderem Rand — eine blosse Abwandlung wäre der Fall, den der Kommentar oben beschreibt, und die dort genannte Vorlage ist genau deshalb weggefallen. **Zentriert, aber nicht leer.** Der Satz darunter kommt als `caller()`, damit die Seiten mehrere Absätze und einen `.hinweis` mitgeben können (die Absageseite tut das). #} {% macro hinweiskarte(titel) -%} {% if not titel %}{{ EINE_HINWEISKARTE_OHNE_TITEL_SAGT_NICHTS }}{% endif -%}

{{ titel }}

{{ caller() }}
{%- endmacro %} {# Vier Arten: haupt (Marke, weiss; genau einer je Ansicht), zweit (weiss mit Rahmen), still (nur Text), gefahr. Mit `href` ein Verweis, sonst ein Knopf. `klein` fuer Leisten (28 px), `breit` fuer volle Breite. **Leeres `href` heisst Knopf, nicht kaputter Verweis** — das ist der Vertrag und der Grund, warum der Vorgabewert leer ist. Wer ein Ziel RECHNET und dabei nichts herausbekommt, bekommt hier also einen Knopf; das ist beabsichtigt und der einzige Fall, in dem der Bausatz nicht abbricht. Wer navigieren will, uebergibt einen Pfad. **`kurzwort` für schmale Schirme** (05.09.2026). Am Telefon teilt sich der Hauptknopf die Zeile mit dem Hausnamen; «Meldung erfassen» ist 151 px breit und liess dem Namen 57 px — «Feldstrasse 3 & 5» stand als «Feldstras» da, bei 320 px gar nicht mehr (gemessen). Mit `kurzwort` rendert der Knopf BEIDE Fassungen und der Stil blendet je eine aus; was `display: none` verbirgt, liest auch keine Vorlesesoftware, es bleibt also genau eine Beschriftung. Kein Ersatz für eine kurze Beschriftung: Wer ohnehin zwei Wörter braucht, lässt `kurzwort` weg. Es ist für den Fall, dass die lange Fassung am Schreibtisch die bessere ist. #} {#- **`ziel`** schickt EIN Formular woandershin als der Rest des Formulars (`formaction`). Gebraucht wird das von einem zweiten Knopf, der dieselben Felder an ein anderes Ziel gibt — die Vorschau am Antwortfeld ist der erste Fall. Es geht nur an einem `submit`, nie an einem Verweis: Ein `` sendet kein Formular, und ein `formaction` daran wäre ein Attribut, das nichts tut. -#} {% macro knopf(wort, art='haupt', typ='submit', href='', name='', value='', klasse='', id='', kurzwort='', ziel='') -%} {% if art not in ARTEN %}{{ UNBEKANNTE_KNOPFART }}{% endif -%} {% if href and (name or value or id or typ != 'submit') %}{{ EIN_VERWEIS_TRAEGT_KEIN_NAME_VALUE_ID_ODER_TYP }}{% endif -%} {% if href and ziel %}{{ EIN_VERWEIS_HAT_SCHON_EIN_ZIEL_UND_BRAUCHT_KEIN_FORMACTION }}{% endif -%} {% if ziel and typ != 'submit' %}{{ NUR_EIN_SUBMIT_KANN_EIN_FORMULARZIEL_TRAGEN }}{% endif -%} {% if value and not name %}{{ EIN_VALUE_OHNE_NAME_WIRD_NICHT_MITGESENDET }}{% endif -%} {% if not wort %}{{ EIN_KNOPF_OHNE_WORT_IST_UNSICHTBAR }}{% endif -%} {% set beschriftung %}{% if kurzwort %}{{ wort }}{{ kurzwort }}{% else %}{{ wort }}{% endif %}{% endset -%} {% if href %}{{ beschriftung }}{% else %}{% endif %} {%- endmacro %} {# Beschriftung ueber dem Feld: 12 px, Gewicht 500, gedaempft. Das Feld selbst kommt als Block, weil jedes anders ist (Typ, Muster, Vorbelegung). #} {% macro feld(label, fuer, zusatz='') -%} {% if not label or not fuer %}{{ EIN_FELD_BRAUCHT_BESCHRIFTUNG_UND_KENNUNG }}{% endif -%} {{ caller() }} {%- endmacro %}