diff --git a/apps/docs/docs/management/api/index.mdx b/apps/docs/docs/management/api/index.mdx
index 359d189e6..35e9f49e4 100644
--- a/apps/docs/docs/management/api/index.mdx
+++ b/apps/docs/docs/management/api/index.mdx
@@ -82,6 +82,8 @@ The OpenAPI and MCP APIs include board management endpoints for common automatio
- Set desktop and mobile home boards for the current user
- Configure global board defaults such as public home boards and status behavior
+Board layout responses identify each layout with a `mobile`, `base`, or `custom` role. Mobile and Base are protected. Layout-saving clients must preserve existing roles, keep Mobile at breakpoint `0`, keep Base as the highest breakpoint, and use unique custom breakpoints between them. The layout save operation returns the canonical saved layouts, including server-generated IDs for new custom layouts. A non-Base layout can be regenerated from Base with the protected reset operation.
+
Use the interactive API reference to inspect the exact request body for each endpoint under the `boards` and `settings` tags.
## Partial integration failures
diff --git a/apps/docs/docs/management/boards/index.mdx b/apps/docs/docs/management/boards/index.mdx
index c89d7fb68..7ec29f1a9 100644
--- a/apps/docs/docs/management/boards/index.mdx
+++ b/apps/docs/docs/management/boards/index.mdx
@@ -90,11 +90,12 @@ Homarr will fall back to the Homarr logo if nothing is set.
### Layout
-In this section you can configure the different responsive layouts.
-Homarr selects a layout from the screen width, then scales its complete fixed-size canvas uniformly.
+In this section you can configure the different responsive layouts. Homarr selects the layout with the highest breakpoint that is not wider than the current screen, then scales its complete fixed-size canvas uniformly.

+Every board has two protected layouts:
+
Every `1 × 1` tile has a `200 × 200` logical-pixel content area.
The `24` logical-pixel gap between tiles is separate from that size.
A `2 × 1` tile therefore contains two fixed cell widths plus the gap between them.
@@ -103,24 +104,27 @@ When fitting below 100%, Homarr keeps widget typography and control density at t
On larger canvases, the complete board grows together like browser zoom.
The complete canvas always fits the available width and never adds horizontal board scrolling.
-The position and size of every item are independent in each responsive layout.
-Moving or resizing an item in one layout does not change any other layout.
-When adding a new layout, its initial positions and sizes are generated from the column count.
-New items use their widget's default grid size, while a new Container starts at a useful `4 × 3` size when the layout has enough columns.
-Both are placed in the first available position.
+- **Mobile** starts at `0px` and uses three columns on new boards.
+- **Base** is the widest layout and starts at `768px` on new boards.
+
+Mobile and Base can be renamed and their column counts and rails can be changed, but they cannot be removed. You can add removable custom layouts between their breakpoints.
+
+Each layout stores independent positions and sizes. Moving or resizing a widget while editing Mobile does not move it in Base or any custom layout. New custom layouts are generated from Base and scaled to their selected column count.
+
+Use **Reset from Base** on Mobile or a custom layout to replace all of its positions and sizes with a newly generated version of Base. The layout's name, breakpoint, and column count are kept.
The canvas is at least one window tall, even when it is empty.
Its left and right edges are hard drag boundaries, while its bottom edge extends automatically as a tile is dragged or resized downward.
Nested Containers keep their own resizable bounds.
+New items use their widget's default size and new Containers use a useful `4 × 3` size when space allows.
#### Name
-The name of the layout can be chosen freely. It is only used for management and will not be visible on the board.
+The name of the layout can be chosen freely. It is only used for management and is not displayed on the board.
#### Column count
-Total number of columns for this layout.
-When changed, Homarr moves items into the next available positions without overlap.
+The total number of columns in the layout. Changing it projects the saved positions and sizes into the new grid width without overlap.
#### Fixed rails
@@ -139,8 +143,7 @@ Apps, widgets, and Containers inside them still use the same fixed `200 × 200`
#### Breakpoint
-Minimum width of the screen where the layout will be used.
-Once the minimum of the next layout is reached, the layout will change.
+The minimum screen width where the layout is used. Mobile is fixed at `0px`. Custom breakpoints must be unique and sit between Mobile and Base. Base must remain the highest breakpoint.
#### View and edit modes
@@ -165,6 +168,10 @@ The **Move / resize item** action in the edit menu remains available for precise
#### Preview
+Layout Settings shows a lightweight structural preview for every layout, including saved spans, Containers, widget labels, and app icons. The canvas width represents the screen range where that layout is active.
+
+Choose **Edit layout** to open the real board editor at that representative width on desktop. Save returns to Layout Settings. Cancel discards the local changes. On phones and tablets, the normal board editor continues to use the layout selected by the device width.
+
diff --git a/apps/docs/docs/management/workshop/index.mdx b/apps/docs/docs/management/workshop/index.mdx
index 24357f761..97c26c40a 100644
--- a/apps/docs/docs/management/workshop/index.mdx
+++ b/apps/docs/docs/management/workshop/index.mdx
@@ -93,6 +93,8 @@ Custom Widget imports detect required bearer, basic, header, or query credential
Workshop is powered by [PocketBase](https://pocketbase.io). The production image compiles the current Docusaurus site and serves it together with the API from one origin. The deployment lives in `apps/workshop/` with a Dockerfile, Compose configuration, version-controlled migrations, hooks, a smoke test, and ACT-local workflow instructions.
+Submission links include Open Graph and X/Twitter metadata for the submission title, description, and type. The first uploaded screenshot is used as the social preview image; submissions without screenshots use the Homarr logo.
+
Run `pnpm docker:workshop` for PocketBase on port 8090, then run
`WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs` for Docusaurus on port 3003. `pnpm docker:docs` builds the
combined production layout. Normal start and stop commands retain data; `down --volumes` is an intentional,
diff --git a/apps/docs/src/components/workshop/DetailPage.tsx b/apps/docs/src/components/workshop/DetailPage.tsx
index 579dd65a9..c806f3f81 100644
--- a/apps/docs/src/components/workshop/DetailPage.tsx
+++ b/apps/docs/src/components/workshop/DetailPage.tsx
@@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import BrowserOnly from "@docusaurus/BrowserOnly";
+import Head from "@docusaurus/Head";
import Link from "@docusaurus/Link";
import { useLocation } from "@docusaurus/router";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
@@ -69,6 +70,7 @@ import { WorkshopErrorBoundary } from "./WorkshopErrorBoundary";
import { downloadSubmissionJson, voteAndReconcile } from "./workshop-utils";
const typeLabels: Record = { customCss: "CSS", customWidget: "Widget" };
+const typeSocialLabels: Record = { customCss: "Custom CSS", customWidget: "Custom widget" };
const typeDotColors: Record = { customCss: "bg-blue-500", customWidget: "bg-yellow-500" };
const contentLanguages: Record = { customCss: "css", customWidget: "json" };
const copyState = [
@@ -536,9 +538,31 @@ const MarketplaceDetail = ({ workshopUrl }: { workshopUrl: string }) => {
const options = widgetDefinition ? Object.entries(widgetDefinition.options) : [];
const protectedSources = sources.filter(([, source]) => sourceNeedsSecret(source.auth));
const displayedContent = formatWorkshopContent(submission.type, submission.content);
+ const socialTitle = `${submission.title} · Homarr Workshop`;
+ const socialDescription = `${typeSocialLabels[submission.type]} for Homarr. ${submission.description}`.trim();
+ const socialUrl = `${window.location.origin}${window.location.pathname}`;
+ const socialImage = screenshotUrls[0] ?? `${window.location.origin}/img/logo.png`;
return (