Skip to content

feat(surface): Sektionslayouts kommen aus dem Theme - #192

Merged
BechsteinDigital merged 2 commits into
mainfrom
feat/theme-section-layouts
Aug 7, 2026
Merged

BechsteinDigital merged 2 commits into
mainfrom
feat/theme-section-layouts

Conversation

@BechsteinDigital

Copy link
Copy Markdown
Owner

1. Why is this change necessary?

Drag & Drop braucht Zielorte, und die gab es nicht. theme.json kannte Templates und Settings,
aber keine Sektionslayouts — der Canvas zeigte nur Regionen, in denen schon ein Block lag, also
sah eine zweispaltige Sektion mit leerer Spalte aus wie eine einspaltige.

Deshalb steht dieser Baustein vor Drag & Drop, nicht daneben.

Und die Sprache allein hätte nicht gereicht: Es gibt kein theme.json im Repo, und
.cal-section stylte nichts. Der Renderer gab data-cal-layout aus, auf das niemand hörte, der
Editor bot keine Wahl an — beides sah nach einem Fehler aus, wo ein Theme fehlte. Deshalb bringt
die Basis-Fläche die Layouts jetzt selbst mit.

2. What does this change do, exactly?

Die Basis kann es selbst. Fünf Layouts (single, two-1-1, two-2-1, sidebar-left,
three-1-1-1), gestylt in derselben tokens.scss, die schon die neutralen --cal-*-Werte
trägt — dieselbe Rolle wie dort: eine Basis, kein Design. Eine frische Installation hat damit
etwas, womit sich komponieren lässt.

Ein Plugin-Theme erbt sie und ergänzt die eigenen. Das Basis-Stylesheet ist immer geladen,
also funktionieren die Basis-Layouts auch unter einem fremden Theme, und wer nur
sidebar-right beisteuern will, muss die Palette nicht wiederholen. Ein gleichnamiges Layout
verdrängt das der Basis vollständig — zusammenzuführen wäre der Weg, auf dem ein Theme ein
two-2-1 mit den Regionen der Basis bekäme, obwohl sein CSS zwei andere kennt, und die Blöcke
lägen dann in Regionen, die es nicht gibt. "inheritSectionLayouts": false steht allein.

Ein Theme deklariert, welche Sektionslayouts es rendern kann und welche Regionen darin liegen
(§7.1). Der Editor bietet ausschließlich das an.

{
  "sectionLayouts": [
    { "key": "single", "label": "Eine Spalte", "regions": ["main"] },
    { "key": "two-2-1", "label": "Zwei Spalten (2:1)",
      "regions": [{ "key": "main", "label": "Inhalt" }, { "key": "aside", "label": "Seitenspalte" }] }
  ]
}

Damit bleibt die Token-Achse die Design-Autorität — niemand komponiert ein Raster, das das Theme
nicht stylen kann — und es steht kein Layout-Name im Core. Ein Theme bringt ein Raster mit,
das niemand vorhergesehen hat, ohne Änderung an der Plattform.

Der Rückfall bei einem Theme-Wechsel (§7.8) unterscheidet zwei Fälle, die sonst gleich
aussehen.
Ein Theme, das two-2-1 nicht mehr kennt, hat es abgelehnt: Die Sektion fällt beim
Rendern auf single zurück, alle Blöcke bleiben, sie steht nur einspaltig statt in einem Raster,
das niemand stylt. Ein Theme, das gar keine Layouts deklariert — heute die meisten —, sagt zu
keinem etwas; dort fällt nichts zurück. Ohne diese Unterscheidung wäre aus einer leeren Liste eine
sichtbare Layout-Änderung geworden, auf jeder bestehenden Seite.

Der Editor benennt die betroffenen Sektionen. Das ist der Unterschied zwischen „meine Seite
sieht anders aus" und „diese Sektion hängt an einem Layout, das das neue Theme nicht mitbringt".

Ein Layout-Wechsel hängt keine Blöcke um. Das wäre die scheinbar hilfreiche Variante und die,
die Arbeit vernichtet: Wer ein Layout ausprobiert und zurückwechselt, fände seine Seitenspalte im
Hauptbereich wieder, ohne dass irgendetwas das rückgängig machen könnte. Heimatlose Blöcke stehen
stattdessen sichtbar hinten in der Sektion — verstecken hieße, Inhalt zu verstecken, den das
Dokument noch trägt.

Der Parser überspringt Kaputtes, statt aufzugeben. Ein Theme mit einem fehlerhaften Layout
bietet seine anderen weiter an. Ein Parser, der abbräche, ließe den Editor ohne jedes Layout
zurück — und das sieht genauso aus wie ein Theme, das keine deklariert.

Regionen liegen als JSON-Spalte, nicht als zweite Tabelle: Sie werden nie ohne ihr Layout gefragt,
und nichts fragt über sie hinweg. Eine Tabelle kaufte einen Join und eine Sortierspalte für eine
Liste, die ganz oder gar nicht gelesen wird.

3. Describe each step to reproduce the issue or behaviour.

Zwölf tragende Aussagen sabotiert — alle beißen, darunter die beiden Fälle des Rückfalls, die
Reihenfolge der Regionen (alphabetisch sortiert stünde die Seitenspalte vor dem Inhalt, neben dem
sie sitzt) und die Vererbung.

Gate Ergebnis
.NET 2033 + 29 Analyzer
Surface Runtime 109
Composer Admin 73
Admin Shell 521
Docs Site lint + build

Neue Migration: AddWorkspaceSectionLayoutDefinitions.

Ein Test über die Sprachgrenze

Die Deklaration steht in C# (der Renderer braucht sie), das CSS in tokens.scss (die Fläche
lädt es). Zwei Dateien, zwei Sprachen, dieselbe Sache — und nichts zwingt sie zusammen außer
SurfaceBaseSectionLayoutsTests, der die SCSS-Datei liest: Ein deklariertes Layout ohne Regel
ist ein Angebot, das nichts tut; eine Regel ohne Deklaration ein Raster, das niemand wählen
kann. Geprüft wird auch, dass die Spaltenzahl zur Regionenzahl passt (zwei Regionen und drei
Spalten wäre eine leere dritte, die niemand füllen kann) und dass nirgends ein fester
Pixelwert
steht — das wäre genau die Freiheit, die die Token-Achse dem Editor verweigert, nur
ohne die Möglichkeit, sie zurückzunehmen.

4. Please link to the relevant issues (if any).

Setzt §7.1 (Sektionslayouts aus dem Theme), §7.7 (Guardrail) und §7.8 (Rückfall) um. Baut auf
#190 auf.

Noch nicht drin

  • Drag & Drop und die Block-Palette. Die Zielorte stehen jetzt.
  • Ein Theme ohne Assembly. Der Theme-Sync wird über den Assembly-Pfad eines Plugins
    ausgelöst, also wird ein reines CSS-plus-Manifest-Theme nie gelesen. Für die Basis spielt das
    keine Rolle (sie ist die Runtime), für ein Drittanbieter-Theme schon.
  • Sektionen löschen und umsortieren.
  • spacing und surfaceRole je Sektion — Token-Stufen, die der Renderer schon ausgibt, für
    die der Editor aber noch kein Feld hat.
  • Der abgeleitete Nutzungsindex („welche Layouts benutzen Block X", §7.2).

BechsteinDigital and others added 2 commits August 7, 2026 12:47
Ein Theme deklariert in `theme.json`, welche Sektionslayouts es rendern kann und
welche Regionen darin liegen. Der Editor bietet ausschließlich das an.

Damit bleibt die Token-Achse die Design-Autorität — niemand komponiert ein Raster,
das das Theme nicht stylen kann — und es steht kein Layout-Name im Core. Ein Theme
kann ein Raster mitbringen, das niemand vorhergesehen hat, ohne Änderung an der
Plattform.

Das ist auch der Schritt, ohne den Drag & Drop keine Ziele hätte: Der Canvas zeigt
jetzt jede Region des Layouts, auch die leere. Bisher zeigte er nur Regionen, in
denen schon ein Block lag — eine zweispaltige Sektion mit leerer Spalte sah aus wie
eine einspaltige, und es gab keinen Ort, an den sich etwas ziehen ließe.

**Der Rückfall bei einem Theme-Wechsel (§7.8)** unterscheidet zwei Fälle, die sonst
gleich aussehen. Ein Theme, das `two-2-1` NICHT MEHR kennt, hat es abgelehnt — die
Sektion fällt beim Rendern auf `single` zurück, alle Blöcke bleiben, sie steht nur
einspaltig statt in einem Raster, das niemand stylt. Ein Theme, das gar keine
Layouts deklariert (heute die meisten), sagt zu keinem etwas; dort fällt nichts
zurück. Sonst wäre aus einer leeren Liste eine sichtbare Layout-Änderung geworden.

Der Editor benennt die betroffenen Sektionen. Das ist der Unterschied zwischen
„meine Seite sieht anders aus" und „diese Sektion hängt an einem Layout, das das
neue Theme nicht mitbringt".

**Ein Layout-Wechsel hängt keine Blöcke um.** Das wäre die scheinbar hilfreiche
Variante und die, die Arbeit vernichtet: Wer ein Layout ausprobiert und
zurückwechselt, fände seine Seitenspalte im Hauptbereich wieder, ohne dass
irgendetwas das rückgängig machen könnte. So bleibt der Wechsel umkehrbar, und
heimatlose Blöcke stehen sichtbar hinten in der Sektion.

**Der Parser überspringt Kaputtes, statt aufzugeben.** Ein Theme mit einem
fehlerhaften Layout bietet seine anderen weiter an; ein Parser, der abbräche, ließe
den Editor ohne jedes Layout zurück — und das sieht genauso aus wie ein Theme, das
keine deklariert.

Regionen als JSON-Spalte statt als zweite Tabelle: Sie werden nie ohne ihr Layout
gefragt, und nichts fragt über sie hinweg. Eine Tabelle kaufte einen Join und eine
Sortierspalte für eine Liste, die ganz oder gar nicht gelesen wird.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvepSeijoZeRaBsyiGqoka
Der vorige Commit gab dem Theme die Sprache für Sektionslayouts — nur sagte sie
niemand. Es gibt kein `theme.json` im Repo, und `.cal-section` stylte nichts: Der
Renderer gab `data-cal-layout` aus, auf das nichts hörte, und der Editor bot keine
Wahl an. Beides sah nach einem Fehler aus, wo ein Theme fehlte.

Jetzt bringt die Basis-Fläche fünf Layouts mit (`single`, `two-1-1`, `two-2-1`,
`sidebar-left`, `three-1-1-1`), gestylt in derselben `tokens.scss`, die auch die
neutralen `--cal-*`-Werte trägt. Dieselbe Rolle wie dort: eine Basis, kein Design.
Eine frische Installation hat damit etwas, womit sich komponieren lässt.

**Ein Plugin-Theme erbt sie und ergänzt die eigenen.** Das ist die sichere
Richtung: Das Basis-Stylesheet ist immer geladen, also funktionieren die
Basis-Layouts auch unter einem fremden Theme, und wer nur `sidebar-right`
beisteuern will, muss die Palette nicht wiederholen. Ein gleichnamiges Layout
verdrängt das der Basis VOLLSTÄNDIG — zusammenzuführen wäre der Weg, auf dem ein
Theme ein `two-2-1` mit den Regionen der Basis bekäme, obwohl sein CSS zwei andere
kennt, und die Blöcke lägen dann in Regionen, die es nicht gibt. Wer ein eigenes
Rastersystem durchsetzen will, setzt `inheritSectionLayouts` auf false.

Die Liste steht in C# und nicht im TypeScript-Paket, obwohl das CSS dort liegt: Der
Kompositions-Renderer muss dieselbe Vererbung kennen wie der Editor, und zwei
Listen wären zwei Wahrheiten. `SurfaceBaseSectionLayoutsTests` hält sie gegen
`tokens.scss` — über die Sprachgrenze, weil genau dort sonst niemand hinsieht: Ein
deklariertes Layout ohne Regel ist ein Angebot, das nichts tut, eine Regel ohne
Deklaration ein Raster, das niemand wählen kann. Der Test prüft auch, dass die
Spaltenzahl zur Regionenzahl passt und dass nirgends ein fester Pixelwert steht —
das wäre genau die Freiheit, die die Token-Achse dem Editor verweigert.

Ein kaputter Schalter (`"inheritSectionLayouts": "false"` als Zeichenkette) heißt
erben. Ihn als Abschaltung zu lesen ließe den Editor still nur noch das eine
Theme-Layout anbieten, und niemand käme auf die Zeichenkette.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QvepSeijoZeRaBsyiGqoka
@BechsteinDigital
BechsteinDigital merged commit 0dc37f1 into main Aug 7, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant