diff --git a/.agents/skills/homarr-custom-widget/SKILL.md b/.agents/skills/homarr-custom-widget/SKILL.md
index 3121374ab..a067f4a54 100644
--- a/.agents/skills/homarr-custom-widget/SKILL.md
+++ b/.agents/skills/homarr-custom-widget/SKILL.md
@@ -11,7 +11,7 @@ Author one widget at a time. When connected to Homarr, retrieve `homarr://custom
2. Create one credential-free widget with keyed `sources`, keyed `requests`, optional keyed `options`, and safe JSX `template`.
3. Validate, preview, test queries and simulated actions, visually inspect, and iterate.
4. Configure deployment-specific source URLs and request credentials through Homarr; never repeat plaintext.
-5. Save only after narrow/wide, light/dark, loading, empty, error, and success states work.
+5. Save only after narrow/wide, light/dark, loading, empty, error, and success states work. When connected through MCP, persist the exact final tested preview with `customWidget_createFromPreview`; do not resend a large template through `customWidget_create` unless no preview session is available.
Use `{option:name}` or `$option` for saved options. Use `{param:name}` or `$param` for values supplied by `SubFetch`, `ActionButton`, or `ToggleSwitch`. Load queries cannot use invocation parameters. Templates read `data`, `status`, `options`, and temporary `inputs`.
diff --git a/apps/docs/docs/management/assistant.mdx b/apps/docs/docs/management/assistant.mdx
index aecf20fd5..e2032ee37 100644
--- a/apps/docs/docs/management/assistant.mdx
+++ b/apps/docs/docs/management/assistant.mdx
@@ -240,8 +240,13 @@ runtime and security references, and the current JSON Schema to the trusted auth
extra provider round trip for the large `customWidget_getSkill` and `customWidget_schema` results. If the model
discovers the Custom Widget workflow mid-response, Homarr promotes the same resources before its next step and
removes the now-redundant resource calls from provider history. Multiline JSX uses `templateLines` to avoid fragile
-streamed JSON escaping. The assistant validates the candidate, creates a preview, executes every returned preview query,
-checks the actual HTTP status and response shape, and iterates before saving. Preview and saved-definition results
+streamed JSON escaping. Custom Widget authoring tools are mandatory: the assistant validates the complete candidate,
+creates a preview from that exact version, executes every returned preview query, checks the actual HTTP status and
+response shape, and repeats the entire validation and preview sequence after any material JSX, request, source, option,
+or binding change. It only reports a widget as validated, working, created, or placed when the corresponding tool result
+proves that checkpoint. Large widgets are saved from the exact final tested preview session, so the approved create step
+does not stream the complete JSX a second time. When the request already names a board, that board is carried into the
+create result and placement continues without asking the user to select it again. Preview and saved-definition results
include safe Homarr paths that open in the assistant without a page reload. When placement is still a choice, the
assistant uses the structured question UI instead of ending with a prose question.
diff --git a/apps/docs/docs/management/custom-widgets/authoring.mdx b/apps/docs/docs/management/custom-widgets/authoring.mdx
index 429827c55..aa2a18805 100644
--- a/apps/docs/docs/management/custom-widgets/authoring.mdx
+++ b/apps/docs/docs/management/custom-widgets/authoring.mdx
@@ -25,10 +25,10 @@ Every option has a default. Supported controls include text, textarea, number, s
## ChatGPT or Claude
-Enter the desired widget and optional API documentation URL, then select **Copy AI prompt**. The single self-contained prompt is below 12,000 characters, starts with your request, includes the lean contract and two polished examples, and never claims that an offline chat has MCP tools.
+Open **Create or fix with any AI**, enter the desired widget and optional API documentation URL, then select the copy action beside the accordion title. The single self-contained prompt is below 12,000 characters, starts with your request, includes the lean contract and two polished examples, and never claims that an offline chat has MCP tools.
The model must return one fenced `json` block containing the complete widget, including the JSX in its `template`
-field. Copy that single block and select **Paste AI response**. Homarr fills the visual builders and the JSX editor
+field. Copy that single block and select **Paste**. Homarr fills the visual builders and the JSX editor
without any placeholder replacement or second paste.
Homarr shows all actionable schema or JSX errors inline. Enter credentials separately. The prompt deliberately
diff --git a/apps/docs/docs/management/workshop/index.mdx b/apps/docs/docs/management/workshop/index.mdx
index 78e9c89cb..1d18f2c97 100644
--- a/apps/docs/docs/management/workshop/index.mdx
+++ b/apps/docs/docs/management/workshop/index.mdx
@@ -35,7 +35,8 @@ Back up your instance before installing untrusted content.
- Sign in with **GitHub** to upvote/downvote, submit your own creations, or report a submission.
- Each submission shows its title, description, linked GitHub author, revision, optional screenshots, comments, and full content. Widget pages also summarize API sources, actions, options, network access, and required credentials before installation.
- A new submission receives one real upvote owned by its author. Votes remain unique per user and submission.
-- Report details and reporter identities are visible only to Workshop moderators. Reported submissions are highlighted in the moderation page, where each open report can be dismissed or the submission can be deleted. Reports do not automatically remove a submission.
+- Open report counts are public on submission cards so visitors can make an informed installation decision. Report explanations and reporter identities remain visible only to Workshop moderators.
+- Signed-in moderators see a **Moderation** button in the Workshop header. The moderation queue is sorted by open report count and lets moderators inspect every report, dismiss it, or delete the submission. Reports do not automatically remove a submission.
- The manual **Outdated** badge warns visitors that a submission may need an update.
## Comments
@@ -52,6 +53,9 @@ Comments are publicly readable and appear in chronological conversation bubbles
3. Review the widget JSON or CSS in the syntax-aware editor, then add a title, description, optional screenshots, and changelog.
4. Submit the entry.
+Community members can report submissions as outdated or no longer working, malicious, spam, copyright-infringing,
+inappropriate, or another concern. An outdated report does not automatically mark or remove the submission.
+
Submissions are published immediately. Reported entries are reviewed and may be removed.
Authors can edit a submission later to publish a new revision. The edit form can keep or remove existing screenshots and add new PNG, JPG, or WebP images without changing the Workshop URL.
diff --git a/apps/docs/docusaurus.config.ts b/apps/docs/docusaurus.config.ts
index 895dab602..6148de793 100644
--- a/apps/docs/docusaurus.config.ts
+++ b/apps/docs/docusaurus.config.ts
@@ -402,6 +402,7 @@ html[data-workshop-detail-loading] #workshop-detail-route-fallback {
path: "/workshop/:id",
component: "@site/src/components/workshop/WorkshopDetailRoutePage",
exact: true,
+ priority: -1,
});
},
};
diff --git a/apps/docs/src/components/workshop/DetailPage.tsx b/apps/docs/src/components/workshop/DetailPage.tsx
index b632af71a..f28c8af9b 100644
--- a/apps/docs/src/components/workshop/DetailPage.tsx
+++ b/apps/docs/src/components/workshop/DetailPage.tsx
@@ -451,7 +451,7 @@ const MarketplaceDetail = ({ workshopUrl }: { workshopUrl: string }) => {
try {
await backend.report(
submission.id,
- reportCategory as "malicious" | "spam" | "copyright" | "inappropriate" | "other",
+ reportCategory as "outdated" | "malicious" | "spam" | "copyright" | "inappropriate" | "other",
reportExplanation,
);
setReportOpen(false);
@@ -881,6 +881,7 @@ const MarketplaceDetail = ({ workshopUrl }: { workshopUrl: string }) => {
{(value) => String(value).replace(/^./u, (letter) => letter.toUpperCase())}
+ Outdated or no longer workingMaliciousSpamCopyright
diff --git a/apps/docs/src/components/workshop/WorkshopAdmin.tsx b/apps/docs/src/components/workshop/WorkshopAdmin.tsx
index 951780d71..c75e80ccb 100644
--- a/apps/docs/src/components/workshop/WorkshopAdmin.tsx
+++ b/apps/docs/src/components/workshop/WorkshopAdmin.tsx
@@ -109,7 +109,11 @@ export function WorkshopAdmin({ workshopUrl }: { workshopUrl?: string }) {
submission,
reports: reports.filter((report) => report.submission === submission.id),
}))
- .sort((left, right) => Number(right.reports.length > 0) - Number(left.reports.length > 0));
+ .sort(
+ (left, right) =>
+ right.reports.length - left.reports.length ||
+ Date.parse(right.submission.updated) - Date.parse(left.submission.updated),
+ );
return (
diff --git a/apps/docs/src/components/workshop/WorkshopApp.tsx b/apps/docs/src/components/workshop/WorkshopApp.tsx
index ca7b32cba..37e0939f3 100644
--- a/apps/docs/src/components/workshop/WorkshopApp.tsx
+++ b/apps/docs/src/components/workshop/WorkshopApp.tsx
@@ -8,12 +8,14 @@ import {
IconChevronLeft,
IconChevronRight,
IconEye,
+ IconFlag,
IconMessage,
IconPackage,
IconPlus,
IconPuzzle,
IconRefresh,
IconSearch,
+ IconShield,
} from "@tabler/icons-react";
import type { WorkshopSubmission } from "@site/src/lib/pocketbase";
@@ -116,6 +118,7 @@ export const WorkshopApp = ({ workshopUrl }: { workshopUrl: string }) => {
const availableTypeFilters = workshop.user
? [...typeFilters, { value: "yours" as const, label: "Yours" }]
: typeFilters;
+ const openReportCount = workshop.submissions.reduce((total, submission) => total + submission.reportCount, 0);
return (