diff --git a/src/govoplan_campaign/backend/manifest.py b/src/govoplan_campaign/backend/manifest.py
index 61e1950..8cc95d2 100644
--- a/src/govoplan_campaign/backend/manifest.py
+++ b/src/govoplan_campaign/backend/manifest.py
@@ -836,6 +836,68 @@ manifest = ModuleManifest(
),
),
documentation=localize_documentation_topics((
+ DocumentationTopic(
+ id="campaigns.module-navigation-and-table-layout",
+ title="Distinguish module reports from one campaign's report",
+ summary="Use accurate module breadcrumbs and shared edge-to-edge report and attachment tables.",
+ body=(
+ "The Campaign audit documentation book sits immediately to the right of the Recent audit "
+ "events heading. "
+ "The Campaigns module has separate Reports (/campaigns/reports) and Operator queue (/campaigns/queue) views. "
+ "Their breadcrumbs name the module section, not a campaign record. A selected campaign's own report remains under "
+ "/campaigns/{campaign_id}/report (including its reports alias) with the singular Campaign and Report context. "
+ "The legacy /operator link still redirects to the queue; report selection query parameters, editor deep links and "
+ "Quick Access return-to-origin history behavior remain unchanged. These labels grant no extra report or delivery permissions. "
+ "The Campaign reports available to you and Global Attachments cards use the shared table-body layout: tables reach "
+ "the card boundary, including while loading, without module-local negative margins. Global Attachments remains collapsible; "
+ "chooser warnings, row actions, empty-state actions and existing filters remain available. Layout changes do not change "
+ "recipients, attachment rules or bytes, report privacy suppression, saved data, exports or delivery state. "
+ "In Recipient data, automatic fitting prefers at most 640/480 pixels for Recipient(s)/Delivery and 360 pixels "
+ "for configurable fields before distributing spare space. These are starting-layout preferences, not manual "
+ "resize limits: all three kinds of column can be widened further and reduced again. "
+ "The table scrolls horizontally while manual resizing keeps the non-resizable Active and Attachments columns "
+ "at their current widths. These are personal "
+ "browser layout changes, not campaign autosaves. Recipient and global-attachment grids wait for the initial draft "
+ "before restoring their saved column layout, so a reload does not replace personal widths with temporary loading columns."
+ ),
+ layer="available",
+ documentation_types=("user", "admin"),
+ audience=("campaign_manager", "campaign_operator", "campaign_admin"),
+ order=19,
+ conditions=(DocumentationCondition(required_modules=("campaigns",), any_scopes=CAMPAIGN_MODULE_REQUIRED_ANY),),
+ links=(
+ DocumentationLink(label="Campaigns", href="/campaigns", kind="runtime"),
+ DocumentationLink(label="Reports", href="/campaigns/reports", kind="runtime"),
+ DocumentationLink(label="Operator queue", href="/campaigns/queue", kind="runtime"),
+ ),
+ metadata={"kind": "reference", "related_topic_ids": ["campaigns.workflow.prepare-validate-and-build"]},
+ translations={"de": {
+ "title": "Modulberichte vom Bericht einer einzelnen Kampagne unterscheiden",
+ "summary": "Eindeutige Modul-Breadcrumbs sowie gemeinsame, bündige Berichts- und Anhangstabellen verwenden.",
+ "body": (
+ "Das Dokumentationsbuch im Kampagnen-Audit steht unmittelbar rechts neben der Überschrift "
+ "Letzte Audit-Ereignisse. "
+ "Das Modul Kampagnen besitzt getrennte Ansichten für Berichte (/campaigns/reports) und die Operator-Warteschlange "
+ "(/campaigns/queue). Ihre Breadcrumbs benennen den Modulbereich und keinen Kampagnendatensatz. Der Bericht einer "
+ "ausgewählten Kampagne bleibt unter /campaigns/{campaign_id}/report (einschließlich des reports-Alias) im Kontext "
+ "Kampagne und Bericht. Der bisherige Link /operator leitet weiterhin zur Warteschlange um; Auswahlparameter für "
+ "Berichte, Editor-Deep-Links und die Rückkehr zum Ursprung über Quick Access bleiben unverändert. Die Beschriftung "
+ "vergibt keine zusätzlichen Berichts- oder Versandberechtigungen. Die Karten für verfügbare Kampagnenberichte und "
+ "globale Anhänge verwenden das gemeinsame Tabellenlayout: Tabellen reichen auch beim Laden bis an den Kartenrand, "
+ "ohne negative modulspezifische Abstände. Globale Anhänge bleiben einklappbar; Auswahlwarnungen, Zeilenaktionen, "
+ "Aktionen für leere Tabellen und vorhandene Filter bleiben verfügbar. Das Layout ändert weder Empfänger, "
+ "Anhangsregeln oder Bytes noch Datenschutzunterdrückung, gespeicherte Daten, Exporte oder Versandzustand. "
+ "In den Empfängerdaten bevorzugt die automatische Anpassung zunächst höchstens 640/480 Pixel für Empfänger/Zustellung "
+ "und 360 Pixel für konfigurierbare Felder, bevor sie freien Platz verteilt. Diese Werte bestimmen nur das "
+ "Ausgangslayout und begrenzen nicht die manuelle Größenänderung: Alle drei Spaltenarten lassen sich weiter "
+ "verbreitern und wieder verkleinern. Die Tabelle wird horizontal scrollbar; bei der manuellen Größenänderung "
+ "behalten die nicht verstellbaren Spalten Aktiv und Anhänge ihre aktuellen Breiten. Dies sind persönliche "
+ "Browser-Einstellungen und keine automatische Speicherung der Kampagne. "
+ "Empfänger- und globale Anhangstabellen warten beim ersten Laden auf den Entwurf, bevor sie gespeicherte "
+ "Spaltenbreiten wiederherstellen. Vorläufige Ladespalten überschreiben dadurch beim Neuladen keine persönlichen Breiten."
+ ),
+ }},
+ ),
DocumentationTopic(
id="campaigns.attachment-filename-fidelity",
title="Deterministic attachment and ZIP names",
diff --git a/tests/test_documentation_contract.py b/tests/test_documentation_contract.py
index fb39357..b050c85 100644
--- a/tests/test_documentation_contract.py
+++ b/tests/test_documentation_contract.py
@@ -5,3 +5,15 @@ def test_static_documentation_has_complete_german_reference_copy() -> None:
for topic in get_manifest().documentation:
german = topic.translations.get("de", {})
assert all(german.get(field, "").strip() for field in ("title", "summary", "body")), topic.id
+
+
+def test_module_breadcrumb_and_table_layout_contract_is_bilingual_and_non_mutating() -> None:
+ topic = next(item for item in get_manifest().documentation if item.id == "campaigns.module-navigation-and-table-layout")
+ assert set(topic.documentation_types) == {"user", "admin"}
+ assert topic.layer == "available"
+ for body in (topic.body, topic.translations["de"]["body"]):
+ for route in ("/campaigns/reports", "/campaigns/queue", "/campaigns/{campaign_id}/report", "/operator"):
+ assert route in body
+ assert "Quick Access" in body
+ assert "report privacy suppression" in topic.body
+ assert "without module-local negative margins" in topic.body
diff --git a/webui/src/features/campaigns/AttachmentsDataPage.tsx b/webui/src/features/campaigns/AttachmentsDataPage.tsx
index ef2d58d..0dfddc0 100644
--- a/webui/src/features/campaigns/AttachmentsDataPage.tsx
+++ b/webui/src/features/campaigns/AttachmentsDataPage.tsx
@@ -444,8 +444,10 @@ export default function AttachmentsDataPage({ settings, auth, campaignId }: {set
}
-