From eae48f50fd05ded3888f7eafcab86e8ad1f3ca57 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sat, 18 Jul 2026 15:33:25 +0200 Subject: [PATCH] feat(custom-widgets): complete JSX v2 --- .agents/skills/homarr-custom-widget/SKILL.md | 33 + .../references/runtime.md | 21 + .../homarr-custom-widget/references/schema.md | 31 + .../references/security.md | 11 + .env.example | 5 +- .github/workflows/workshop.yml | 42 + .gitignore | 6 +- AUDIT.md | 535 - PRODUCT.md | 33 + .../advanced/environment-variables/index.mdx | 1 + apps/docs/docs/community/faq.mdx | 6 +- apps/docs/docs/management/api/index.mdx | 9 + .../custom-widgets/agent-authoring.mdx | 40 +- .../custom-widgets/architecture.mdx | 29 - .../management/custom-widgets/authoring.mdx | 44 +- .../management/custom-widgets/custom-jsx.mdx | 76 +- .../custom-widgets/extension-recipes.mdx | 66 - .../docs/management/custom-widgets/index.mdx | 215 +- .../management/custom-widgets/migration.mdx | 19 - .../custom-widgets/requests-and-security.mdx | 34 +- .../custom-widgets/troubleshooting.mdx | 32 +- apps/docs/docs/management/mcp.mdx | 61 +- apps/docs/docs/management/workshop/index.mdx | 11 + apps/docs/docs/widgets/custom-api/index.mdx | 14 +- apps/docs/docs/widgets/custom-api/index.ts | 3 +- apps/docs/docusaurus.config.ts | 21 +- apps/docs/package.json | 2 +- .../src/components/workshop/WorkshopAdmin.tsx | 129 + .../src/components/workshop/WorkshopPage.tsx | 436 + .../components/workshop/workshop.module.css | 244 + .../src/generated/custom-jsx-components.json | 4085 +- apps/docs/src/pages/workshop/admin/index.tsx | 12 + apps/docs/src/pages/workshop/index.tsx | 12 + .../downloads/homarr-custom-widget-2.0.0.zip | Bin 0 -> 4280 bytes apps/nextjs/package.json | 1 + .../custom-widget-secret/[token]/page.tsx | 13 + .../[token]/secret-entry.tsx | 114 + .../manage/custom-widgets/_beta-banner.tsx | 22 - .../manage/custom-widgets/_code-editor.tsx | 41 +- .../custom-widgets/_copy-ai-prompt-button.tsx | 62 +- .../custom-widgets/_custom-widget-actions.tsx | 72 +- .../custom-widgets/_custom-widget-ai-card.tsx | 58 + .../_custom-widget-code-language.ts | 57 + .../_custom-widget-form-analysis.ts | 41 + .../_custom-widget-form-errors.ts | 42 - .../_custom-widget-form-layout.tsx | 76 + .../_custom-widget-form-sections.tsx | 238 - .../_custom-widget-form-utils.ts | 97 + .../_custom-widget-form.module.css | 100 +- .../custom-widgets/_custom-widget-form.tsx | 490 +- .../custom-widgets/_custom-widget-list.tsx | 32 +- .../_custom-widget-preview-action.tsx | 93 + .../_custom-widget-preview-panel.tsx | 278 + .../custom-widgets/_custom-widget-preview.tsx | 277 - .../_custom-widget-request-tools.tsx | 75 + .../_custom-widget-sources-editor.tsx | 243 + .../custom-widgets/_display-field-shared.ts | 39 - .../custom-widgets/_display-field-types.ts | 12 - .../custom-widgets/_display-fields-basic.tsx | 178 - .../custom-widgets/_display-fields-jsx.tsx | 97 - .../custom-widgets/_display-fields-metric.tsx | 167 - .../custom-widgets/_display-fields-status.tsx | 198 - .../custom-widgets/_display-type-fields.tsx | 89 - .../custom-widgets/_preview-widget-panel.tsx | 99 - .../_use-custom-widget-clipboard.tsx | 87 - .../_use-custom-widget-form-actions.ts | 169 + .../_use-custom-widget-preview.ts | 156 - .../manage/custom-widgets/edit/[id]/page.tsx | 49 +- .../manage/custom-widgets/new/page.tsx | 4 +- .../[locale]/manage/custom-widgets/page.tsx | 18 +- .../custom-widgets/preview/[id]/page.tsx | 74 + .../nextjs/src/app/[locale]/manage/layout.tsx | 9 +- .../src/app/[locale]/manage/workshop/page.tsx | 18 + .../custom-widgets/authoring-prompt/route.ts | 17 + .../secret-request/[token]/route.ts | 96 + .../src/app/api/custom-widgets/skill/route.ts | 9 + .../src/app/api/mcp/[transport]/route.ts | 106 +- .../components/board/items/item-content.tsx | 3 +- .../board/items/item-select-modal.tsx | 40 +- .../components/workshop/workshop-browser.tsx | 416 + .../workshop/workshop-install-button.tsx | 40 + .../workshop/workshop-installer.tsx | 36 + .../workshop/workshop-publish-modal.tsx | 124 + apps/workshop/.gitignore | 1 + apps/workshop/Dockerfile | 51 + apps/workshop/Dockerfile.dockerignore | 7 + apps/workshop/README.md | 21 + apps/workshop/docker-compose.yml | 21 + apps/workshop/entrypoint.sh | 11 + apps/workshop/pb_hooks/workshop-utils.js | 666 + apps/workshop/pb_hooks/workshop.pb.js | 78 + .../1784240000_workshop_widgets.js | 211 + apps/workshop/test.sh | 32 + apps/workshop/tests/workshop.integration.mjs | 249 + package.json | 1 + packages/api/package.json | 3 +- .../src/router/custom-widget/cache-version.ts | 17 + .../custom-widget/custom-widget-router.ts | 243 +- .../custom-widget/management-queries.ts | 181 +- .../custom-widget/metadata-procedures.ts | 57 +- .../custom-widget/parse-display-config.ts | 17 - .../preview-action-procedures.ts | 73 +- .../custom-widget/preview-base-procedures.ts | 242 +- .../preview-procedure-helpers.ts | 21 + .../custom-widget/preview-query-procedures.ts | 54 +- .../router/custom-widget/preview-sessions.ts | 5 +- .../router/custom-widget/request-executor.ts | 4 + .../src/router/custom-widget/secret-policy.ts | 25 + .../router/custom-widget/secret-procedures.ts | 145 + .../router/custom-widget/secret-requests.ts | 102 + .../router/custom-widget/stored-definition.ts | 50 + .../custom-widget/template-procedures.ts | 174 +- .../custom-widget/transfer-procedures.ts | 83 +- .../custom-widget/preview-sessions.spec.ts | 67 - .../custom-widget/secret-requests.spec.ts | 31 + .../router/test/widgets/custom-api.spec.ts | 224 - packages/api/src/router/widgets/custom-api.ts | 356 +- packages/api/src/test/mcp.spec.ts | 43 +- packages/api/src/trpc.ts | 13 +- packages/custom-widgets/package.json | 3 + .../scripts/generate-component-docs.ts | 20 + .../scripts/generate-component-metadata.ts | 72 + packages/custom-widgets/src/core/ai-prompt.ts | 263 +- .../src/core/authoring-resources.ts | 94 + .../src/core/built-in-display-schemas.ts | 235 - .../src/core/component-discovery.ts | 92 + .../src/core/component-props.generated.json | 44282 ++++++++++++++++ .../src/core/component-props.ts | 4 +- .../src/core/component-registry-denied.ts | 274 +- .../src/core/component-registry-enabled.ts | 198 +- .../src/core/component-registry.ts | 48 + .../src/core/custom-jsx-schema.ts | 495 +- .../src/core/definition-schemas.ts | 196 +- .../src/core/definition-security.ts | 104 + .../src/core/display-config-schema.ts | 57 - .../custom-widgets/src/core/display-data.ts | 123 - .../custom-widgets/src/core/display-form.ts | 264 - packages/custom-widgets/src/core/examples.ts | 356 +- packages/custom-widgets/src/core/import.ts | 105 +- packages/custom-widgets/src/core/index.ts | 5 +- .../src/core/option-value-types.ts | 8 + .../custom-widgets/src/core/options-schema.ts | 300 + packages/custom-widgets/src/core/options.ts | 108 + .../custom-widgets/src/core/request-schema.ts | 204 + .../custom-widgets/src/core/schema-types.ts | 14 - packages/custom-widgets/src/core/schema.ts | 17 +- .../custom-widgets/src/core/tabler-icons.ts | 103 + .../custom-widgets/src/jsx/analyzer-ast.ts | 66 + packages/custom-widgets/src/jsx/analyzer.ts | 145 +- .../custom-widgets/src/jsx/jsx-emitter.tsx | 26 +- packages/custom-widgets/src/jsx/policy.ts | 38 +- .../src/jsx/runtime-components.tsx | 154 +- .../custom-widgets/src/jsx/safe-properties.ts | 95 +- .../custom-widgets/src/runtime/actions.tsx | 75 +- .../src/runtime/collection-displays.tsx | 125 - .../src/runtime/custom-jsx-renderer.tsx | 91 +- .../src/runtime/display-registry.ts | 20 - .../src/runtime/display-types.ts | 10 - packages/custom-widgets/src/runtime/index.ts | 2 - .../src/runtime/metric-displays.tsx | 124 - .../custom-widgets/src/runtime/status.tsx | 4 +- .../custom-widgets/src/runtime/sub-fetch.tsx | 51 +- packages/custom-widgets/src/runtime/types.ts | 26 +- packages/custom-widgets/src/server/index.ts | 9 +- .../src/server/preview-sessions.ts | 91 +- .../src/server/request-executor.ts | 64 +- .../src/server/request-manifest.ts | 29 +- .../custom-widgets/src/test/ai-prompt.spec.ts | 101 +- .../src/test/authoring-resources.spec.ts | 33 + .../src/test/component-registry.spec.ts | 13 +- .../src/test/display-data.spec.ts | 135 - .../src/test/display-form.spec.ts | 77 - .../custom-widgets/src/test/import.spec.ts | 75 +- .../src/test/jsx-policy.spec.ts | 93 + .../src/test/preview-sessions.spec.ts | 26 +- .../src/test/request-manifest.spec.ts | 4 + .../custom-widgets/src/test/runtime.spec.tsx | 86 +- .../custom-widgets/src/test/schema.spec.ts | 526 +- .../src/test/server-policy.spec.ts | 61 + .../custom-widgets/src/test/workbench.spec.ts | 88 +- .../custom-widgets/src/workbench/analyzer.ts | 32 +- .../src/workbench/form-schema.ts | 163 +- .../src/workbench/import-review.tsx | 6 +- .../custom-widgets/src/workbench/index.ts | 2 +- .../src/workbench/preview-utils.ts | 56 +- .../src/workbench/request-references.ts | 52 + .../mysql/0043_custom_widget_v2_reset.sql | 30 + .../migrations/mysql/meta/0043_snapshot.json | 2366 + .../db/migrations/mysql/meta/_journal.json | 7 + .../0009_custom_widget_v2_reset.sql | 29 + .../postgresql/meta/0009_snapshot.json | 2242 + .../migrations/postgresql/meta/_journal.json | 7 + packages/db/migrations/seed.ts | 132 +- .../sqlite/0041_custom_widget_v2_reset.sql | 31 + .../migrations/sqlite/meta/0041_snapshot.json | 2276 + .../db/migrations/sqlite/meta/_journal.json | 7 + packages/db/schema/mysql.ts | 26 +- packages/db/schema/postgresql.ts | 26 +- packages/db/schema/sqlite.ts | 26 +- .../custom-widgets/currency-exchange.json | 18 - .../db/seed/custom-widgets/dog-facts.json | 17 - packages/db/seed/custom-widgets/jellyfin.json | 23 - packages/db/seed/custom-widgets/pokedex.json | 14 - packages/definitions/src/permissions.ts | 3 + packages/modals/src/confirm-modal.tsx | 8 +- packages/translation/src/lang/ca.json | 238 - packages/translation/src/lang/cn.json | 238 - packages/translation/src/lang/cr.json | 160 +- packages/translation/src/lang/cs.json | 238 - packages/translation/src/lang/da.json | 238 - packages/translation/src/lang/de-CH.json | 238 - packages/translation/src/lang/de.json | 166 +- packages/translation/src/lang/el.json | 238 - packages/translation/src/lang/en-gb.json | 215 +- packages/translation/src/lang/en.json | 560 +- packages/translation/src/lang/es.json | 238 - packages/translation/src/lang/et.json | 238 - packages/translation/src/lang/fi.json | 238 - packages/translation/src/lang/fr.json | 187 +- packages/translation/src/lang/he.json | 238 - packages/translation/src/lang/hr.json | 238 - packages/translation/src/lang/hu.json | 238 - packages/translation/src/lang/it.json | 160 +- packages/translation/src/lang/ja.json | 238 - packages/translation/src/lang/ko.json | 174 +- packages/translation/src/lang/lt.json | 238 - packages/translation/src/lang/lv.json | 230 +- packages/translation/src/lang/nl.json | 238 - packages/translation/src/lang/no.json | 238 - packages/translation/src/lang/pl.json | 160 +- packages/translation/src/lang/pt-br.json | 188 +- packages/translation/src/lang/pt.json | 238 - packages/translation/src/lang/ro.json | 238 - packages/translation/src/lang/ru.json | 238 - packages/translation/src/lang/sk.json | 238 - packages/translation/src/lang/sl.json | 238 - packages/translation/src/lang/sv.json | 238 - packages/translation/src/lang/tr.json | 238 - packages/translation/src/lang/uk.json | 232 +- packages/translation/src/lang/vi.json | 238 - packages/translation/src/lang/zh.json | 238 - packages/widgets/package.json | 2 + packages/widgets/src/_inputs/index.ts | 4 + ...dget-custom-widget-configuration-input.tsx | 577 + .../widget-custom-widget-select-input.tsx | 163 +- .../src/_inputs/widget-internal-input.tsx | 3 + packages/widgets/src/custom-api/component.tsx | 266 +- .../src/custom-api/custom-jsx-display.spec.ts | 45 +- .../src/custom-api/custom-jsx-display.tsx | 57 +- packages/widgets/src/custom-api/index.ts | 2 + .../{jsx-whitelist.ts => jsx-components.ts} | 2 +- .../src/custom-api/jsx-icon-adapter.tsx | 2 + .../custom-api/widget-definition-context.tsx | 44 +- packages/widgets/src/definition.ts | 1 + packages/widgets/src/options.ts | 12 + packages/workshop/package.json | 36 + packages/workshop/src/client.ts | 297 + packages/workshop/src/index.ts | 2 + packages/workshop/src/schema.spec.ts | 51 + packages/workshop/src/schema.ts | 197 + packages/workshop/tsconfig.json | 6 + pnpm-lock.yaml | 192 +- pnpm-workspace.yaml | 1 + 263 files changed, 65503 insertions(+), 17816 deletions(-) create mode 100644 .agents/skills/homarr-custom-widget/SKILL.md create mode 100644 .agents/skills/homarr-custom-widget/references/runtime.md create mode 100644 .agents/skills/homarr-custom-widget/references/schema.md create mode 100644 .agents/skills/homarr-custom-widget/references/security.md create mode 100644 .github/workflows/workshop.yml delete mode 100644 AUDIT.md create mode 100644 PRODUCT.md delete mode 100644 apps/docs/docs/management/custom-widgets/architecture.mdx delete mode 100644 apps/docs/docs/management/custom-widgets/extension-recipes.mdx delete mode 100644 apps/docs/docs/management/custom-widgets/migration.mdx create mode 100644 apps/docs/docs/management/workshop/index.mdx create mode 100644 apps/docs/src/components/workshop/WorkshopAdmin.tsx create mode 100644 apps/docs/src/components/workshop/WorkshopPage.tsx create mode 100644 apps/docs/src/components/workshop/workshop.module.css create mode 100644 apps/docs/src/pages/workshop/admin/index.tsx create mode 100644 apps/docs/src/pages/workshop/index.tsx create mode 100644 apps/docs/static/downloads/homarr-custom-widget-2.0.0.zip create mode 100644 apps/nextjs/src/app/[locale]/custom-widget-secret/[token]/page.tsx create mode 100644 apps/nextjs/src/app/[locale]/custom-widget-secret/[token]/secret-entry.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_beta-banner.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-ai-card.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-code-language.ts create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-analysis.ts delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-errors.ts create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-layout.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-sections.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form-utils.ts create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview-action.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview-panel.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-request-tools.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-sources-editor.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-field-shared.ts delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-field-types.ts delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-fields-basic.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-fields-jsx.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-fields-metric.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-fields-status.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_display-type-fields.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_preview-widget-panel.tsx delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_use-custom-widget-clipboard.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_use-custom-widget-form-actions.ts delete mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/_use-custom-widget-preview.ts create mode 100644 apps/nextjs/src/app/[locale]/manage/custom-widgets/preview/[id]/page.tsx create mode 100644 apps/nextjs/src/app/[locale]/manage/workshop/page.tsx create mode 100644 apps/nextjs/src/app/api/custom-widgets/authoring-prompt/route.ts create mode 100644 apps/nextjs/src/app/api/custom-widgets/secret-request/[token]/route.ts create mode 100644 apps/nextjs/src/app/api/custom-widgets/skill/route.ts create mode 100644 apps/nextjs/src/components/workshop/workshop-browser.tsx create mode 100644 apps/nextjs/src/components/workshop/workshop-install-button.tsx create mode 100644 apps/nextjs/src/components/workshop/workshop-installer.tsx create mode 100644 apps/nextjs/src/components/workshop/workshop-publish-modal.tsx create mode 100644 apps/workshop/.gitignore create mode 100644 apps/workshop/Dockerfile create mode 100644 apps/workshop/Dockerfile.dockerignore create mode 100644 apps/workshop/README.md create mode 100644 apps/workshop/docker-compose.yml create mode 100644 apps/workshop/entrypoint.sh create mode 100644 apps/workshop/pb_hooks/workshop-utils.js create mode 100644 apps/workshop/pb_hooks/workshop.pb.js create mode 100644 apps/workshop/pb_migrations/1784240000_workshop_widgets.js create mode 100644 apps/workshop/test.sh create mode 100644 apps/workshop/tests/workshop.integration.mjs create mode 100644 packages/api/src/router/custom-widget/cache-version.ts delete mode 100644 packages/api/src/router/custom-widget/parse-display-config.ts create mode 100644 packages/api/src/router/custom-widget/secret-policy.ts create mode 100644 packages/api/src/router/custom-widget/secret-procedures.ts create mode 100644 packages/api/src/router/custom-widget/secret-requests.ts create mode 100644 packages/api/src/router/custom-widget/stored-definition.ts delete mode 100644 packages/api/src/router/test/custom-widget/preview-sessions.spec.ts create mode 100644 packages/api/src/router/test/custom-widget/secret-requests.spec.ts delete mode 100644 packages/api/src/router/test/widgets/custom-api.spec.ts create mode 100644 packages/custom-widgets/scripts/generate-component-docs.ts create mode 100644 packages/custom-widgets/scripts/generate-component-metadata.ts create mode 100644 packages/custom-widgets/src/core/authoring-resources.ts delete mode 100644 packages/custom-widgets/src/core/built-in-display-schemas.ts create mode 100644 packages/custom-widgets/src/core/component-discovery.ts create mode 100644 packages/custom-widgets/src/core/component-props.generated.json create mode 100644 packages/custom-widgets/src/core/definition-security.ts delete mode 100644 packages/custom-widgets/src/core/display-config-schema.ts delete mode 100644 packages/custom-widgets/src/core/display-data.ts delete mode 100644 packages/custom-widgets/src/core/display-form.ts create mode 100644 packages/custom-widgets/src/core/option-value-types.ts create mode 100644 packages/custom-widgets/src/core/options-schema.ts create mode 100644 packages/custom-widgets/src/core/options.ts create mode 100644 packages/custom-widgets/src/core/request-schema.ts create mode 100644 packages/custom-widgets/src/core/tabler-icons.ts create mode 100644 packages/custom-widgets/src/jsx/analyzer-ast.ts delete mode 100644 packages/custom-widgets/src/runtime/collection-displays.tsx delete mode 100644 packages/custom-widgets/src/runtime/display-registry.ts delete mode 100644 packages/custom-widgets/src/runtime/display-types.ts delete mode 100644 packages/custom-widgets/src/runtime/metric-displays.tsx create mode 100644 packages/custom-widgets/src/test/authoring-resources.spec.ts delete mode 100644 packages/custom-widgets/src/test/display-data.spec.ts delete mode 100644 packages/custom-widgets/src/test/display-form.spec.ts create mode 100644 packages/custom-widgets/src/workbench/request-references.ts create mode 100644 packages/db/migrations/mysql/0043_custom_widget_v2_reset.sql create mode 100644 packages/db/migrations/mysql/meta/0043_snapshot.json create mode 100644 packages/db/migrations/postgresql/0009_custom_widget_v2_reset.sql create mode 100644 packages/db/migrations/postgresql/meta/0009_snapshot.json create mode 100644 packages/db/migrations/sqlite/0041_custom_widget_v2_reset.sql create mode 100644 packages/db/migrations/sqlite/meta/0041_snapshot.json delete mode 100644 packages/db/seed/custom-widgets/currency-exchange.json delete mode 100644 packages/db/seed/custom-widgets/dog-facts.json delete mode 100644 packages/db/seed/custom-widgets/jellyfin.json delete mode 100644 packages/db/seed/custom-widgets/pokedex.json create mode 100644 packages/widgets/src/_inputs/widget-custom-widget-configuration-input.tsx create mode 100644 packages/widgets/src/_inputs/widget-internal-input.tsx rename packages/widgets/src/custom-api/{jsx-whitelist.ts => jsx-components.ts} (83%) create mode 100644 packages/workshop/package.json create mode 100644 packages/workshop/src/client.ts create mode 100644 packages/workshop/src/index.ts create mode 100644 packages/workshop/src/schema.spec.ts create mode 100644 packages/workshop/src/schema.ts create mode 100644 packages/workshop/tsconfig.json diff --git a/.agents/skills/homarr-custom-widget/SKILL.md b/.agents/skills/homarr-custom-widget/SKILL.md new file mode 100644 index 000000000..5999daa91 --- /dev/null +++ b/.agents/skills/homarr-custom-widget/SKILL.md @@ -0,0 +1,33 @@ +--- +name: homarr-custom-widget +description: Author, validate, preview, and install safe Homarr Custom JSX v2 widgets from API documentation. Use when creating or fixing Homarr custom widgets, including multi-source dashboards, queries, actions, options, local state, and Mantine JSX. +license: Apache-2.0 +metadata: + version: "2.0.0" +--- + +# Homarr Custom Widget + +Create one widget at a time. Repeat the workflow when the user asks for several widgets; do not invent packs or shared connections. + +## Connected workflow + +1. Get `homarr-custom-widget-author`, then read `homarr://custom-widgets/schema` and `homarr://custom-widgets/components`. +2. Read the API documentation supplied by the user. Ask for missing base URLs and credentials only when needed. +3. Build one `homarr-custom-widget-v2` document. Keep credentials outside the document. +4. Call `customWidget_validate`, fix every error, and treat advisory Mantine prop warnings as review items. +5. Call `customWidget_previewCreate`, then test load/manual queries with `customWidget_previewQuery` and actions with `customWidget_previewAction`. +6. If credentials are missing, call `customWidget_secretRequestUser`. Use `customWidget_secretSet` only when the user already supplied a credential and the tool is authorized. +7. Inspect the preview URL and `customWidget_previewJournal`. Patch JSX with `customWidget_templatePatch` or update the draft and repeat. +8. Call `customWidget_create` only after validation and preview pass. Continue with the next widget independently. + +## Output rules + +- Use inline sources, named requests, definition-owned options, session-local state, and one safe JSX template. +- Use `kind`, not the HTTP method, to distinguish reads from user-triggered actions. +- Never put a credential in JSX, options, headers, URLs, examples, exports, logs, or chat output. +- Use declarative `bind` controls and Homarr request components; never emit imports, hooks, callbacks, browser requests, or arbitrary JavaScript. +- Include responsive loading, empty, error, and success states. +- Keep actions explicit, permission-scoped, confirmed when destructive, and followed by targeted invalidation. + +Read [references/schema.md](references/schema.md), [references/runtime.md](references/runtime.md), and [references/security.md](references/security.md) before offline authoring. When connected, prefer live resources because installed Mantine components can differ by Homarr release. diff --git a/.agents/skills/homarr-custom-widget/references/runtime.md b/.agents/skills/homarr-custom-widget/references/runtime.md new file mode 100644 index 000000000..bd19682b9 --- /dev/null +++ b/.agents/skills/homarr-custom-widget/references/runtime.md @@ -0,0 +1,21 @@ +# JSX runtime + +Templates read four roots: `data.`, `status.`, `options`, and `state`. + +Use Mantine Core, Dates, Charts, safe Tabler icons, and Homarr runtime components. Safe installed Mantine exports are discovered automatically. Ordinary serializable props and Mantine style props pass through; `style` and `styles` are scoped and sanitized. + +Render icons as ``. When connected, retrieve `homarr://custom-widgets/components/TablerIcon` for the supported icon names. + +Use `SubFetch` for a manual query, `ActionButton` or `ToggleSwitch` for an action, and `RefreshButton` to refresh data. A query with `trigger: "load"` populates its data automatically and follows the widget refresh interval. + +Bind local controls declaratively: + +```jsx + + + setSearch(event.currentTarget.value)} + /> + + + {error &&
{error}
} +
+ {items.map((item) => ( +
+ {item.screenshots[0] ? ( + + ) : ( +
JSX
+ )} +
+
+ Revision {item.revision} + + ▲ {item.upvotes} · ▼ {item.downvotes} + +
+

{item.title}

+

{item.description || "No description"}

+ by {item.authorName} + +
+
+ ))} +
+ + {selected && ( +
event.target === event.currentTarget && setSelected(null)} + > + +

{selected.title}

+

{selected.description}

+
+ + by {selected.authorName} · revision {selected.revision} + + SHA-256 {selected.contentHash.slice(0, 12)}… +
+ {selected.screenshots.length > 0 && ( +
+ {selected.screenshots.map((file, index) => ( + {`${selected.title} + ))} +
+ )} + +
{selected.content}
+
+ + + + {selected.author === user?.id && ( + <> + + + + )} + + +
+
+
+ )} + + {editorOpen && ( +
+ +

{selected?.author === user?.id ? "Update submission" : "Publish widget"}

+ +