Compare commits

...
Author SHA1 Message Date
ajnart da2e121358 docs: remove obsolete mobile sidebar references 2026-08-12 11:49:23 +02:00
ajnart c834b41d39 docs: refresh modernized widget screenshots 2026-08-12 11:40:24 +02:00
ajnart ed588d46c1 docs: finish onboarding mobile coverage 2026-08-12 01:36:53 +02:00
ajnart 8f2d28c032 docs: finish assistant coverage 2026-08-12 01:35:55 +02:00
ajnart 31d5f0801d docs: finish custom widget coverage 2026-08-12 01:34:34 +02:00
ajnart c397f69ef4 docs: finish boards dnd coverage 2026-08-12 01:33:56 +02:00
ajnart bb1cfea898 docs: finish downloads coverage 2026-08-12 01:32:46 +02:00
ajnart 2a93055068 docs: finish widget interaction coverage 2026-08-12 01:31:43 +02:00
ajnart 76d4eb6ee0 docs: deepen v2 stack coverage 2026-08-12 01:05:39 +02:00
ajnart a1feca1e1f docs: align v2 stack documentation 2026-08-11 23:27:24 +02:00
Thomas Camlong 6743ffebf1 Merge branch 'feat/repair-stack-assistant-20260811' into feat/repair-stack-release-20260811 2026-08-11 17:03:22 +02:00
Thomas Camlong 9143c15454 Merge branch 'feat/repair-stack-mobile-20260811' into feat/repair-stack-assistant-20260811 2026-08-11 17:03:22 +02:00
Thomas Camlong 3428e3682b Merge branch 'feat/repair-stack-widgets-20260811' into feat/repair-stack-mobile-20260811 2026-08-11 17:03:22 +02:00
Thomas Camlong 5b8f9187d5 Merge branch 'feat/repair-stack-dnd-20260811' into feat/repair-stack-widgets-20260811 2026-08-11 17:03:22 +02:00
Thomas Camlong a61e9d5efc Merge branch 'feat/repair-stack-v2-20260811' into feat/repair-stack-dnd-20260811 2026-08-11 17:03:21 +02:00
Thomas Camlong bf15e64b63 style: format v2 workshop files 2026-08-11 17:03:09 +02:00
Thomas Camlong 132e0e8acb Merge branch 'feat/homarr-assistant' into release/v2 2026-08-11 16:59:31 +02:00
Thomas Camlong e2dd68fdbc Merge branch 'feat/auto-mobile-boards' into feat/homarr-assistant 2026-08-11 16:59:12 +02:00
Thomas Camlong 16953fc019 Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-11 16:57:29 +02:00
Thomas Camlong 545a5c1d96 Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-11 16:57:05 +02:00
Thomas Camlong e55e709b72 Merge branch 'v2' into feat/dnd-kit-grid 2026-08-11 16:55:34 +02:00
Thomas Camlong 117eabd30b Merge branch 'dev' into v2 2026-08-11 16:55:16 +02:00
Thomas 527e911eef fix(beszel): let Recharts own tooltip lifecycle (#6560) 2026-08-11 15:35:17 +02:00
Thomas 38af22aba0 test: cover public custom widgets for guests (#6558) 2026-08-11 15:25:22 +02:00
Thomas Camlong cba20bf665 fix: simplify grid interaction previews 2026-08-11 15:09:53 +02:00
Thomas 827ba7cda4 feat: rewrite image picker (#6556)
* feat: rewrite image picker

* style: auto-fix lint and format

* fix: address image picker review

* fix: preserve unicode icon searches

* test: stabilize custom widget login

* fix: suggest image searches only on create
2026-08-11 14:51:31 +02:00
Thomas ff2cdecc86 feat: rewrite image picker (#6556)
* feat: rewrite image picker

* style: auto-fix lint and format

* fix: address image picker review

* fix: preserve unicode icon searches

* test: stabilize custom widget login

* fix: suggest image searches only on create
2026-08-11 14:49:52 +02:00
Thomas Camlong 79d9ef8bb4 Merge branch 'feat/homarr-assistant' into release/v2 2026-08-11 11:36:31 +02:00
Thomas Camlong 513549784f Merge branch 'feat/auto-mobile-boards' into feat/homarr-assistant 2026-08-11 11:36:06 +02:00
Thomas Camlong a96105fb09 Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-11 11:35:40 +02:00
Thomas Camlong f28d61faa1 Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-11 11:35:14 +02:00
Thomas Camlong dc66493caa perf: isolate board grid previews 2026-08-11 11:27:58 +02:00
Thomas Camlong 3f86134881 Merge branch 'feat/homarr-assistant' into release/v2 2026-08-11 11:06:42 +02:00
Thomas Camlong b7934d7e87 Merge branch 'feat/auto-mobile-boards' into feat/homarr-assistant 2026-08-11 11:06:18 +02:00
Thomas Camlong 4b5166dfed Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-11 11:05:52 +02:00
Thomas Camlong afe17eefc8 Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-11 11:05:26 +02:00
Thomas Camlong a7ce15eaea Merge branch 'v2' into feat/dnd-kit-grid 2026-08-11 11:05:01 +02:00
Thomas Camlong 44c22ccad3 ci: retry flaky container-backed unit tests in fast gate
Integration tests (nzbget, nextcloud, pi-hole, etc.) start Docker
containers and intermittently time out under shared-runner load, which
fails the fast gate and skips the container job that publishes the PR
preview image. Retry failing unit tests twice so transient container
startup failures do not block the image publish.
2026-08-11 11:03:33 +02:00
Thomas Camlong eae17877a1 ci: make e2e tests resilient to CI resource contention
E2E specs spin up Docker containers in parallel and intermittently time
out waiting for UI elements under shared-runner load. Limit parallel
workers to 2 and retry flaky failures twice so the container job can
complete and publish the PR preview image.
2026-08-11 11:03:12 +02:00
ajnart 6435955a51 test: wait for Home Assistant API readiness 2026-08-11 11:02:43 +02:00
Sven 742bde1d27 fix(synology): support vol_info in storage_v2 responses (#6554) 2026-08-11 08:59:28 +00:00
Thomas Camlong 959d863ec0 Merge branch 'feat/homarr-assistant' into release/v2 2026-08-10 20:43:23 +02:00
Thomas Camlong 09bc5a16f4 test: align assistant credential flow 2026-08-10 20:43:03 +02:00
Thomas Camlong 58bb0f5500 Merge branch 'feat/homarr-assistant' into release/v2 2026-08-10 20:13:28 +02:00
Thomas Camlong eb150b32e3 Merge branch 'feat/auto-mobile-boards' into feat/homarr-assistant 2026-08-10 20:13:28 +02:00
Thomas Camlong 38702fb66b Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-10 20:13:28 +02:00
Thomas Camlong 593443fc72 Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-10 20:13:27 +02:00
Thomas Camlong 223ef33bfd Merge branch 'feat/auto-mobile-boards' into feat/homarr-assistant 2026-08-10 20:13:05 +02:00
Thomas Camlong d8f71f46be Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-10 20:13:00 +02:00
Thomas Camlong bc687b90ad Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-10 20:13:00 +02:00
Thomas Camlong 3f9e83ab97 Merge branch 'feat/widget-modernization' into feat/auto-mobile-boards 2026-08-10 20:12:36 +02:00
Thomas Camlong 8fff3e413e Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-10 20:12:33 +02:00
Thomas Camlong c2b7e43d7f Merge branch 'feat/dnd-kit-grid' into feat/widget-modernization 2026-08-10 20:12:05 +02:00
Thomas Camlong 16dd251276 fix: disable app context menu 2026-08-10 20:10:22 +02:00
Thomas Camlong b830479f09 Merge branch 'v2' into feat/dnd-kit-grid 2026-08-10 20:08:16 +02:00
Thomas Camlong 7a45b0d119 feat(workshop): add social previews 2026-08-10 20:07:51 +02:00
ajnart 8dd6cbbfdb fix assistant focus and settings 2026-08-10 19:49:33 +02:00
Thomas Camlong 057c048d01 Merge commit 'b7ddc028a' into release/v2 2026-08-10 19:48:41 +02:00
Thomas Camlong b7ddc028a5 Merge commit '1f17c77c9' into feat/homarr-assistant 2026-08-10 19:48:29 +02:00
Thomas Camlong 1f17c77c97 Merge commit '7d3625ecd' into feat/auto-mobile-boards 2026-08-10 19:48:20 +02:00
Thomas Camlong 7d3625ecdc Merge commit '6a08ae395' into feat/widget-modernization 2026-08-10 19:48:12 +02:00
Thomas Camlong 6a08ae3951 Merge commit '8b49982c3' into feat/dnd-kit-grid 2026-08-10 19:48:03 +02:00
Thomas Camlong 8b49982c3a fix: persist Workshop PocketBase data 2026-08-10 19:47:29 +02:00
Thomas Camlong 73cb1d7d9c Merge commit '3c8fe5461' into HEAD 2026-08-10 19:17:58 +02:00
Thomas Camlong 3c8fe54618 Merge commit '05b4f4dc5' into HEAD 2026-08-10 19:17:26 +02:00
Thomas Camlong 05b4f4dc52 Merge commit 'cae663407' into HEAD 2026-08-10 19:17:14 +02:00
Thomas Camlong cae6634074 Merge commit 'e5899521b' into HEAD 2026-08-10 19:16:56 +02:00
Thomas Camlong e5899521b9 Merge commit 'ad2fa1605' into HEAD 2026-08-10 19:16:39 +02:00
Thomas Camlong ad2fa1605e fix: defer Spotlight without loading feedback 2026-08-10 19:16:16 +02:00
Thomas Camlong b4bbaa2e12 Merge commit '55bc3353b' into HEAD 2026-08-10 19:14:07 +02:00
Thomas Camlong 55bc3353b6 Merge commit 'a60f15d36' into HEAD
# Conflicts:
#	apps/nextjs/src/app/[locale]/boards/[name]/settings/page.tsx
2026-08-10 19:13:51 +02:00
Thomas Camlong a60f15d366 Merge commit '9a2fe1324' into HEAD 2026-08-10 19:13:13 +02:00
Thomas Camlong 9a2fe13242 Merge remote-tracking branch 'origin/v2' into HEAD 2026-08-10 19:12:48 +02:00
Thomas Camlong 106fca2a61 fix: simplify assistant key management 2026-08-10 19:09:04 +02:00
Thomas Camlong 920631373c fix: refine responsive layout settings 2026-08-10 19:09:04 +02:00
ajnart 533003e49b test custom widget assistant authoring 2026-08-10 19:02:55 +02:00
Thomas Camlong a6061348c3 fix: polish settings and command menu 2026-08-10 19:00:43 +02:00
Thomas Camlong 077362a9d9 Merge commit '8557f2107' into release/v2 2026-08-10 18:49:18 +02:00
Thomas Camlong 8557f2107c Merge commit 'f81d66a16' into feat/homarr-assistant 2026-08-10 18:48:57 +02:00
Thomas Camlong f81d66a16f Merge commit 'd04eec39b' into feat/auto-mobile-boards 2026-08-10 18:48:37 +02:00
Thomas Camlong d04eec39b6 Merge commit '22e955529' into feat/widget-modernization 2026-08-10 18:48:22 +02:00
Thomas Camlong 22e955529b Merge commit 'e524515ca' into feat/dnd-kit-grid
# Conflicts:
#	apps/nextjs/src/components/workshop/workshop-css-import-button.tsx
2026-08-10 18:48:05 +02:00
Thomas Camlong e524515ca5 fix(workshop): streamline import review 2026-08-10 18:47:23 +02:00
Thomas Camlong 7b996a985a Merge commit '920c1a7a4' into feat/propagate-workshop-install-release 2026-08-10 18:22:08 +02:00
Thomas Camlong 920c1a7a4c Merge commit '3fbd7ef04' into feat/propagate-workshop-install-assistant 2026-08-10 18:21:51 +02:00
Thomas Camlong 3fbd7ef04d Merge commit '333bab6d2' into feat/propagate-workshop-install-mobile 2026-08-10 18:21:31 +02:00
Thomas Camlong 333bab6d2a Merge commit 'ba582d04c' into feat/propagate-workshop-install-widgets 2026-08-10 18:21:18 +02:00
Thomas Camlong ba582d04ca Merge commit 'cf3c4b415' into feat/propagate-workshop-install-dnd 2026-08-10 18:21:00 +02:00
Thomas Camlong cf3c4b4152 fix(workshop): stack widget install flow 2026-08-10 18:19:31 +02:00
Thomas Camlong e127e3de5c Merge commit '3ea931d45' into feat/propagate-workshop-admin-release 2026-08-10 17:40:58 +02:00
Thomas Camlong 3ea931d456 Merge commit '46b782847' into feat/propagate-workshop-admin-assistant
# Conflicts:
#	apps/nextjs/src/app/[locale]/manage/custom-widgets/_copy-ai-prompt-button.tsx
2026-08-10 17:40:40 +02:00
Thomas Camlong 46b782847c Merge commit 'cbb383959' into feat/propagate-workshop-admin-mobile 2026-08-10 17:38:05 +02:00
Thomas Camlong cbb383959e Merge commit '3ac76491c' into feat/propagate-workshop-admin-widgets 2026-08-10 17:37:51 +02:00
Thomas Camlong 3ac76491c6 Merge commit '378bc0cd2' into feat/propagate-workshop-admin-dnd 2026-08-10 17:37:29 +02:00
Thomas Camlong 378bc0cd28 fix(workshop): expose moderation 2026-08-10 17:37:13 +02:00
Thomas Camlong be93fb330a fix: simplify custom widget preview controls 2026-08-10 17:22:40 +02:00
ajnart 6d72f4ab83 fix assistant custom widget creation 2026-08-10 17:14:15 +02:00
Thomas Camlong 2b32ece92d Merge commit '5125b46f0' into release/v2 2026-08-10 16:26:09 +02:00
Thomas Camlong 5125b46f03 Merge commit 'eebe3ab0f' into feat/homarr-assistant 2026-08-10 16:25:54 +02:00
Thomas Camlong eebe3ab0fd Merge commit '001bb9dc4' into feat/auto-mobile-boards 2026-08-10 16:25:36 +02:00
Thomas Camlong 001bb9dc4e Merge commit '4a96078bf' into feat/widget-modernization 2026-08-10 16:25:23 +02:00
Thomas Camlong 4a96078bf7 Merge commit '893fcd6ed' into feat/dnd-kit-grid 2026-08-10 16:25:10 +02:00
Thomas Camlong 893fcd6ed1 fix(workshop): use PocketBase name 2026-08-10 16:24:52 +02:00
Thomas Camlong a2be1826bb Merge final Workshop identity fix into release 2026-08-10 15:34:00 +02:00
Thomas Camlong 5387b3f92a Merge mobile Workshop consumer fix into assistant 2026-08-10 15:33:44 +02:00
Thomas Camlong aa8d68d224 Merge widget Workshop consumer fix into mobile 2026-08-10 15:33:30 +02:00
Thomas Camlong 2f12fd6fb3 Merge DnD Workshop consumer fix into widgets 2026-08-10 15:33:17 +02:00
Thomas Camlong 581e5105cc Merge v2 Workshop consumer fix into DnD 2026-08-10 15:33:03 +02:00
Thomas Camlong 0422675bc1 fix(workshop): use GitHub handles in Homarr 2026-08-10 15:32:32 +02:00
Thomas Camlong 5039441e64 Merge latest assistant fixes into release 2026-08-10 15:26:13 +02:00
ajnart c7fd5eceae fix assistant tool result streaming 2026-08-10 15:24:34 +02:00
Thomas Camlong ae5b5a2fcb Merge docs typecheck fix into release 2026-08-10 15:23:06 +02:00
Thomas Camlong 414f5dfe53 Merge mobile docs typecheck fix into assistant 2026-08-10 15:22:55 +02:00
Thomas Camlong 34918352f1 Merge widget docs typecheck fix into mobile 2026-08-10 15:22:43 +02:00
Thomas Camlong 1450162d31 Merge DnD docs typecheck fix into widgets 2026-08-10 15:22:29 +02:00
Thomas Camlong 6e1b6d9ff3 Merge v2 docs typecheck fix into DnD 2026-08-10 15:22:17 +02:00
Thomas Camlong d610206896 fix(docs): exclude generated build from typecheck 2026-08-10 15:21:47 +02:00
Thomas Camlong 08a9572772 Merge latest v2 stack into release 2026-08-10 15:16:17 +02:00
Thomas Camlong a6dc90601e Merge mobile Workshop identity fix into assistant 2026-08-10 15:15:57 +02:00
Thomas Camlong efd3dae0db Merge widget Workshop identity fix into mobile 2026-08-10 15:15:36 +02:00
Thomas Camlong 2d594c22cc Merge DnD Workshop identity fix into widgets 2026-08-10 15:15:17 +02:00
Thomas Camlong 78d22c2514 Merge v2 Workshop identity fix into DnD 2026-08-10 15:14:56 +02:00
Thomas Camlong 64403079d8 fix(workshop): simplify GitHub identities 2026-08-10 15:14:32 +02:00
ajnart dc40ec2715 test assistant v7 request payload 2026-08-10 14:59:57 +02:00
ajnart b03849604e test assistant AI SDK v7 transport 2026-08-10 14:57:47 +02:00
ajnart 82bc4e21a9 chore upgrade assistant to AI SDK v7 2026-08-10 14:48:09 +02:00
Thomas Camlong 1e91aed14a Merge latest assistant stack into release/v2 2026-08-10 14:25:08 +02:00
Thomas Camlong 2495b4c4a2 Merge latest mobile boards into assistant 2026-08-10 14:24:50 +02:00
Thomas Camlong e5920b2f56 Merge latest widget modernization into mobile boards 2026-08-10 14:24:33 +02:00
Thomas Camlong 6ce08508fa Merge latest dnd-kit grid into widget modernization 2026-08-10 14:24:13 +02:00
Thomas Camlong 76a2aebb4a Merge latest v2 into dnd-kit grid 2026-08-10 14:23:52 +02:00
Thomas Camlong 015e0cbf63 Merge dev into v2 2026-08-10 14:23:19 +02:00
Thomas 735be2debb Merge branch 'dev' into release/v2 2026-08-10 14:20:34 +02:00
ajnart 305c9af2da fix assistant exports and tool calls 2026-08-10 14:09:44 +02:00
ajnart f7eed7ce8a refine assistant window controls 2026-08-10 14:09:44 +02:00
homarr-update-contributors[bot] 64febbce12 chore: update integrations list readme (#6551)
Co-authored-by: Homarr Update Contributors <210161987+homarr-update-contributors[bot]@users.noreply.github.com>
2026-08-10 11:44:39 +00:00
Thomas Camlong 20c032f8e8 Merge commit '6baac9298' into release/v2 2026-08-10 13:22:25 +02:00
Thomas Camlong 6baac9298f Merge commit '9054dcad7' into feat/homarr-assistant 2026-08-10 13:22:00 +02:00
Thomas Camlong 9054dcad7a Merge commit 'f9a457982' into feat/auto-mobile-boards 2026-08-10 13:21:29 +02:00
Thomas Camlong f9a4579821 Merge commit '7cd56bdbd' into feat/widget-modernization 2026-08-10 13:20:49 +02:00
Thomas Camlong 7cd56bdbda perf: isolate grid interaction renders 2026-08-10 13:20:13 +02:00
Thomas Camlong b040fbc2d0 Merge feat/homarr-assistant into release/v2 2026-08-10 12:03:15 +02:00
Thomas Camlong 0f862de208 Merge feat/auto-mobile-boards into feat/homarr-assistant 2026-08-10 12:02:53 +02:00
Thomas Camlong 8ee8990f09 Merge feat/widget-modernization into feat/auto-mobile-boards 2026-08-10 12:02:35 +02:00
Thomas Camlong b6053a7b31 Merge feat/dnd-kit-grid into feat/widget-modernization 2026-08-10 12:02:06 +02:00
Thomas Camlong fe37673c95 fix: stabilize rail and resize interactions 2026-08-10 12:01:35 +02:00
ajnart 491b430b64 fix assistant dashboard placement 2026-08-10 11:58:32 +02:00
ajnart 12fe596a9e feat: add assistant window controls 2026-08-10 11:39:52 +02:00
ajnart 4118b6dd3b fix assistant tool continuation 2026-08-10 11:34:33 +02:00
Thomas Camlong 1a57581228 Merge commit 'ceec25af8' into feat/schema-release 2026-08-10 10:55:21 +02:00
Thomas Camlong ceec25af81 merge WUD OpenAPI schema 2026-08-10 10:54:58 +02:00
Thomas Camlong 6088061953 Merge commit 'b3e55eb20' into feat/schema-auto 2026-08-10 10:54:18 +02:00
Thomas Camlong b3e55eb20c Merge commit '1a349b9e9' into feat/schema-widget 2026-08-10 10:53:58 +02:00
Thomas Camlong 1a349b9e96 Merge commit '52266fc7f' into feat/schema-dnd 2026-08-10 10:53:46 +02:00
Thomas Camlong 52266fc7f6 docs: update WUD OpenAPI schema 2026-08-10 10:53:30 +02:00
Thomas Camlong 0dfc8c48c2 Merge commit '2aa8455cb' into feat/final-refresh-release 2026-08-10 10:46:17 +02:00
Thomas Camlong 2aa8455cbf merge latest widget catalog updates 2026-08-10 10:46:00 +02:00
Thomas Camlong 3b1a979d4f Merge commit '2a4ed87c6' into feat/final-refresh-auto 2026-08-10 10:45:21 +02:00
Thomas Camlong 2a4ed87c6e Merge commit '58db9ddf6' into feat/final-refresh-widget 2026-08-10 10:45:03 +02:00
Thomas Camlong 58db9ddf65 Merge commit 'd53021a0f' into feat/final-refresh-dnd 2026-08-10 10:44:39 +02:00
Thomas Camlong d53021a0f3 Merge remote-tracking branch 'origin/dev' into feat/refresh-latest-dev-v2 2026-08-10 10:44:22 +02:00
TimonandThomas 98bb1aecf8 feat(integrations): add What's Up Docker (WUD) integration and widget (#6523)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-08-10 08:42:23 +00:00
Thomas Camlong 0006c79673 Merge commit 'b86cfaeb7' into feat/propagate-workshop-test-release 2026-08-10 10:17:20 +02:00
Thomas Camlong b86cfaeb73 Merge commit '2372416fa' into feat/propagate-workshop-test-assistant 2026-08-10 10:17:02 +02:00
Thomas Camlong 2372416fac Merge commit '781c62a0f' into feat/propagate-workshop-test-auto 2026-08-10 10:16:47 +02:00
Thomas Camlong 781c62a0f5 Merge commit 'ae40fa032' into feat/propagate-workshop-test-widget 2026-08-10 10:16:32 +02:00
Thomas Camlong ae40fa0328 Merge commit 'c7e6ed894' into feat/propagate-workshop-test-dnd 2026-08-10 10:16:13 +02:00
Thomas Camlong c7e6ed8943 test: validate Workshop source locale 2026-08-10 10:15:58 +02:00
Thomas Camlong 9862e886f0 Merge commit 'd1c5cfbca' into feat/refresh-release-from-assistant 2026-08-10 10:09:46 +02:00
Thomas Camlong d1c5cfbcaf chore: leave en-GB to Crowdin 2026-08-10 10:09:30 +02:00
Thomas Camlong 69547495aa Merge commit '605439828' into feat/refresh-assistant-from-auto 2026-08-10 10:09:10 +02:00
Thomas Camlong 6054398285 Merge commit 'ff0b97dfb' into feat/refresh-auto-from-widget 2026-08-10 10:08:49 +02:00
Thomas Camlong ff0b97dfb4 chore: drop manual translations 2026-08-10 10:08:14 +02:00
Thomas Camlong 07422896aa Merge commit 'c1d93d4c7' into feat/refresh-widget-from-dnd 2026-08-10 10:07:53 +02:00
Thomas Camlong c1d93d4c7d chore: leave translations to Crowdin 2026-08-10 10:07:35 +02:00
Thomas Camlong 673fdf0027 Merge commit '5efe053c0' into feat/refresh-dnd-from-v2 2026-08-10 10:06:39 +02:00
Thomas Camlong 5efe053c0f merge dev into v2 2026-08-10 10:06:18 +02:00
Thomas Camlong 9344d4da18 fix: keep HTTP methods untranslated 2026-08-10 10:04:22 +02:00
Thomas Camlong a8df31d825 Merge remote-tracking branch 'origin/feat/homarr-assistant' into feat/stack-agent-widget-fixes-release 2026-08-10 09:40:50 +02:00
Thomas Camlong 004eb94b95 Merge remote-tracking branch 'origin/feat/auto-mobile-boards' into feat/stack-agent-widget-fixes-assistant 2026-08-10 09:40:38 +02:00
Thomas Camlong 4822c2fa12 Merge remote-tracking branch 'origin/feat/widget-modernization' into feat/stack-agent-widget-fixes-mobile 2026-08-10 09:40:29 +02:00
Thomas Camlong 9c91ab0977 fix: rework media session details 2026-08-10 09:39:49 +02:00
Thomas Camlong 7e1edb844b fix: refine Beszel stats period controls 2026-08-10 09:39:49 +02:00
Thomas Camlong 26f141f246 Merge remote-tracking branch 'origin/feat/homarr-assistant' into feat/stack-immich-fit-release 2026-08-10 09:32:48 +02:00
Thomas Camlong a5e0427fe7 Merge remote-tracking branch 'origin/feat/auto-mobile-boards' into feat/stack-immich-fit-assistant 2026-08-10 09:32:35 +02:00
Thomas Camlong 68070796a0 Merge remote-tracking branch 'origin/feat/widget-modernization' into feat/stack-immich-fit-mobile 2026-08-10 09:32:22 +02:00
Thomas Camlong b639ce8741 fix: show complete Immich photos in advanced mode 2026-08-10 09:31:54 +02:00
Thomas Camlong 57ce8e5d99 Merge remote-tracking branch 'origin/feat/homarr-assistant' into feat/stack-downloads-tooltip-release 2026-08-10 09:27:36 +02:00
Thomas Camlong 34344ed57a Merge remote-tracking branch 'origin/feat/auto-mobile-boards' into feat/stack-downloads-tooltip-assistant 2026-08-10 09:27:24 +02:00
Thomas Camlong 58c0260e70 Merge remote-tracking branch 'origin/feat/widget-modernization' into feat/stack-downloads-tooltip-mobile 2026-08-10 09:27:17 +02:00
Thomas Camlong f5cde54705 fix: dismiss Downloads tooltips in advanced mode 2026-08-10 09:26:46 +02:00
Thomas Camlong 223a20b271 Merge remote-tracking branch 'origin/feat/homarr-assistant' into feat/stack-beszel-release 2026-08-10 09:20:13 +02:00
Thomas Camlong bf4f93de4a Merge remote-tracking branch 'origin/feat/auto-mobile-boards' into feat/stack-beszel-assistant 2026-08-10 09:19:59 +02:00
Thomas Camlong ddff19c932 Merge remote-tracking branch 'origin/feat/widget-modernization' into feat/stack-beszel-mobile 2026-08-10 09:19:44 +02:00
Thomas Camlong 8307024abe fix: contain Beszel table status dots 2026-08-10 09:19:05 +02:00
homarr-crowdin[bot]andThomas 06c489bd48 chore(lang): updated translations from crowdin (#6548)
Co-authored-by: Thomas <thomas@ajnart.fr>
2026-08-10 09:06:56 +02:00
Thomas Camlong 914b96d671 Merge v2 CI repair into release roll-up 2026-08-09 22:28:05 +02:00
Thomas Camlong 68125d8d30 Merge v2 CI repair through mobile boards 2026-08-09 22:28:01 +02:00
Thomas Camlong ff6a273cd1 Merge v2 CI repair through widget modernization 2026-08-09 22:27:57 +02:00
Thomas Camlong 7d560e0675 Merge v2 CI repair through dnd kit grid 2026-08-09 22:27:54 +02:00
Thomas Camlong aafd5f4c9c Merge v2 CI repair into dnd kit grid 2026-08-09 22:27:48 +02:00
Thomas Camlong 1b489cc2e0 fix: stabilize v2 CI 2026-08-09 22:27:28 +02:00
Thomas Camlong 9ea04aa3c1 Merge refreshed v2 stack into release roll-up 2026-08-09 22:01:08 +02:00
Thomas Camlong 955c232c20 Merge refreshed mobile boards into Homarr Assistant 2026-08-09 22:00:50 +02:00
Thomas Camlong fe71b18636 Merge refreshed widget modernization into mobile boards 2026-08-09 22:00:30 +02:00
Thomas Camlong a547e29607 Merge refreshed dnd kit grid into widget modernization 2026-08-09 22:00:05 +02:00
Thomas Camlong 8cbd53d362 Merge current v2 into dnd kit grid 2026-08-09 21:59:44 +02:00
Thomas Camlong 7cf27c3f39 feat: add Workshop staging deployment 2026-08-09 21:40:20 +02:00
Thomas Camlong 8a3f8d5c12 fix: allow Workshop runtime config writes 2026-08-09 21:21:01 +02:00
Thomas Camlong 8f75954ca9 fix: prevent horizontal rail auto-scroll 2026-08-09 21:17:41 +02:00
ajnart 19abd46414 feat assistant context and agent trace 2026-08-09 21:07:33 +02:00
Thomas Camlong 1028752cbc fix: restore Workshop runtime URLs 2026-08-09 20:11:13 +02:00
Thomas Camlong 7ff1e98c3e Merge custom option test fix into v2 roll-up 2026-08-09 18:09:41 +02:00
Thomas Camlong 2c7178a475 Merge custom option test fix into assistant 2026-08-09 18:09:41 +02:00
Thomas Camlong 9453a0a0e6 Merge custom option test fix into mobile boards 2026-08-09 18:09:41 +02:00
Thomas Camlong 7e33d5a326 Merge custom option test fix into widget modernization 2026-08-09 18:09:41 +02:00
Thomas Camlong e169427360 Merge custom option test fix into dnd kit 2026-08-09 18:09:40 +02:00
ajnart f495dc8be7 fix assistant reasoning mount lifecycle 2026-08-09 18:09:11 +02:00
ajnart a06d8b99d9 feat: improve assistant thinking states 2026-08-09 18:09:11 +02:00
ajnart 90066f8e37 fix: refine assistant controls 2026-08-09 18:09:11 +02:00
ajnart 112db000fd fix: adjust assistant response limits 2026-08-09 18:09:11 +02:00
ajnart 45c1f9e5a9 fix: stabilize custom widget authoring 2026-08-09 18:09:11 +02:00
ajnart edc7b4d55b fix: harden assistant authoring and UI 2026-08-09 18:09:11 +02:00
Thomas Camlong fa0e55cc93 fix: validate MCP v2 routes 2026-08-09 18:09:11 +02:00
Thomas Camlong d1042ce09b Merge custom option test fix into assistant 2026-08-09 18:09:11 +02:00
Thomas Camlong 3b82ee034d Merge custom option test fix into mobile boards 2026-08-09 18:09:07 +02:00
Thomas Camlong 2215fdacfb Merge custom option test fix into widget modernization 2026-08-09 18:09:07 +02:00
Thomas Camlong 7821377f59 Merge custom option test fix into dnd kit 2026-08-09 18:09:07 +02:00
Thomas Camlong 2c97eec1d0 Merge custom option test fix into mobile boards 2026-08-09 18:08:39 +02:00
Thomas Camlong b30f8bc6ea Merge custom option test fix into widget modernization 2026-08-09 18:06:57 +02:00
Thomas Camlong cd8a69fa01 Merge custom option test fix into dnd kit 2026-08-09 18:06:57 +02:00
Thomas Camlong b8bdbc2f3e fix: restore focus after preview exit 2026-08-09 18:06:26 +02:00
Thomas Camlong 889d732e03 test: preserve advanced preview hover ownership 2026-08-09 18:06:26 +02:00
Thomas Camlong 54bd8d6343 feat: improve advanced preview activation 2026-08-09 18:06:26 +02:00
Thomas Camlong 6f1c81bc04 fix: smooth advanced focus entrance 2026-08-09 18:06:26 +02:00
Thomas Camlong 00688d5c1a Remove Docker advanced focus 2026-08-09 18:06:26 +02:00
Thomas Camlong fc8b9178a3 Merge custom option test fix into widget modernization 2026-08-09 18:06:25 +02:00
Thomas Camlong fb7c7d35d9 Merge custom option test fix into dnd kit 2026-08-09 18:05:27 +02:00
Thomas Camlong 198fda7854 Revert "ci: use self-hosted runners for stack"
This reverts commit a1d80515b0e00240bb662180626a44bfb205656e.
2026-08-09 18:03:38 +02:00
Thomas Camlong 66a1d5b210 ci: use self-hosted runners for stack 2026-08-09 18:03:38 +02:00
Thomas Camlong 5e68aab970 fix: anchor dashboard rail labels 2026-08-09 18:03:38 +02:00
Thomas Camlong ca9d836fac Merge remote-tracking branch 'origin/feat/dnd-kit-grid' into HEAD 2026-08-09 18:03:38 +02:00
Thomas Camlong 82a50de403 fix: size dashboard rails to viewport 2026-08-09 18:03:37 +02:00
Thomas Camlong 41606cef86 fix: widen workshop CSS import 2026-08-09 18:03:37 +02:00
Thomas Camlong 7d9caf407c style: format board grid test 2026-08-09 18:03:37 +02:00
Thomas Camlong 59528ca1c3 test: allow dnd-kit DOM modifier import 2026-08-09 18:03:37 +02:00
Thomas Camlong 9373e3d69a fix: preserve narrow board interactions 2026-08-09 18:03:37 +02:00
Thomas Camlong 8acc134a36 fix: keep scaled board rails sticky 2026-08-09 18:03:37 +02:00
Thomas Camlong a4ff57ed85 test: assert clipped zoom overflow 2026-08-09 18:03:37 +02:00
Thomas Camlong c530190638 test: avoid physical zoom size assumption 2026-08-09 18:03:37 +02:00
Thomas Camlong d6b6b696b4 test: assert zoomed canvas fit invariant 2026-08-09 18:03:37 +02:00
Thomas Camlong 1473605445 style: format board grid coverage 2026-08-09 18:03:37 +02:00
Thomas Camlong 255b544a6c docs: align board geometry behavior 2026-08-09 18:03:37 +02:00
Thomas Camlong bcbde92c2e Merge sidebar stability fix into dnd kit 2026-08-09 18:03:37 +02:00
Thomas Camlong 63f82f1a35 Merge grid geometry fixes into dnd kit 2026-08-09 18:03:37 +02:00
Thomas Camlong 0a8e4fead0 fix: stabilize board rails 2026-08-09 18:03:37 +02:00
Thomas Camlong 03ecfef429 Merge layout compatibility fix into dnd kit 2026-08-09 18:03:37 +02:00
Thomas Camlong 7a97b7187b Fix board grid bounds and live growth 2026-08-09 18:03:37 +02:00
Thomas Camlong 899dfd7230 Merge custom option test fix into dnd kit 2026-08-09 18:03:37 +02:00
Thomas Camlong fc1dbd37f1 fix: accept legacy dynamic sections 2026-08-09 18:03:37 +02:00
Thomas Camlong b9c89c61a2 test: harden integration icon checks 2026-08-09 13:39:16 +02:00
Thomas Camlong b5dbeae864 test: fix custom option repair fixture 2026-08-09 09:21:55 +02:00
Thomas Camlong f08a365fc7 Merge widget option and advanced focus fixes into v2 roll-up 2026-08-09 09:21:15 +02:00
Thomas Camlong 5daa0570ac test: keep assistant widget compact 2026-08-09 09:20:51 +02:00
Thomas Camlong 9cd6bc90ed Merge widget option and advanced focus fixes into assistant 2026-08-09 09:20:18 +02:00
Thomas Camlong cfa104baf7 Merge advanced widget opt-in into mobile boards 2026-08-09 09:20:15 +02:00
Thomas Camlong f214c25e9c fix: require advanced widget focus opt-in 2026-08-09 09:19:54 +02:00
Thomas Camlong fcee8177af Merge custom widget option repair into widget modernization 2026-08-09 09:18:01 +02:00
Thomas Camlong 0d6f9337b4 Merge v2 custom widget option repair 2026-08-09 09:17:58 +02:00
Thomas Camlong d513c7fe46 fix: repair stale custom widget options 2026-08-09 09:17:32 +02:00
ajnart fa9d080eed feat: migrate MCP server to v2 2026-08-09 08:25:17 +02:00
Thomas Camlong df47b1bcf6 Merge migration renames into v2 roll-up 2026-08-09 08:15:35 +02:00
Thomas Camlong 8fea2a2851 Merge migration renames into Homarr Assistant 2026-08-09 08:15:08 +02:00
Thomas Camlong 19a6e32d61 Merge migration renames into mobile boards 2026-08-09 08:14:40 +02:00
Thomas Camlong 0425c3630c Merge migration renames into widget modernization 2026-08-09 08:14:06 +02:00
Thomas Camlong 84db5ccfe7 Rename layout gutter migrations 2026-08-09 08:12:46 +02:00
Thomas Camlong d56e2b5d60 Rename Assistant migrations 2026-08-09 08:12:43 +02:00
Thomas Camlong ee7e090a1f Rename layout role migrations 2026-08-09 08:12:41 +02:00
Thomas Camlong 9257e3bd7c Merge branch 'feat/v2-stack-final-runtime-assistant' into release/v2 2026-08-09 02:57:00 +02:00
Thomas Camlong 895ec9fad5 Merge branch 'feat/v2-stack-final-runtime-mobile' into feat/v2-stack-final-runtime-assistant 2026-08-09 02:56:50 +02:00
Thomas Camlong 9df91587b2 Merge branch 'feat/v2-stack-final-runtime-modernization' into feat/v2-stack-final-runtime-mobile 2026-08-09 02:56:39 +02:00
Thomas Camlong db7f1a0374 Merge branch 'feat/v2-stack-final-runtime-dnd' into feat/v2-stack-final-runtime-modernization 2026-08-09 02:56:26 +02:00
Thomas Camlong 1f045b2abf Merge branch 'feat/v2-stack-final-runtime-v2' into feat/v2-stack-final-runtime-dnd 2026-08-09 02:56:11 +02:00
Thomas Camlong 8dad83f720 test(e2e): serialize container specs 2026-08-09 02:55:51 +02:00
Thomas Camlong cae0ff3fb7 Merge branch 'feat/v2-stack-final-runtime-assistant' into release/v2 2026-08-09 02:22:29 +02:00
Thomas Camlong c237ceff9f Merge branch 'feat/v2-stack-final-runtime-mobile' into feat/v2-stack-final-runtime-assistant 2026-08-09 02:22:19 +02:00
Thomas Camlong cf6c362a4a Merge branch 'feat/v2-stack-final-runtime-modernization' into feat/v2-stack-final-runtime-mobile
# Conflicts:
#	apps/nextjs/src/app/[locale]/boards/[name]/settings/_settings-form.tsx
2026-08-09 02:22:03 +02:00
Thomas Camlong da99dc3080 Merge branch 'feat/v2-stack-final-runtime-dnd' into feat/v2-stack-final-runtime-modernization 2026-08-09 02:21:24 +02:00
Thomas Camlong 52ab119a1f Merge branch 'feat/v2-stack-final-runtime-v2' into feat/v2-stack-final-runtime-dnd 2026-08-09 02:21:13 +02:00
Thomas Camlong bbc23a6c65 fix(settings): avoid nested forms 2026-08-09 02:21:01 +02:00
Thomas Camlong 6299e85924 Merge branch feat/v2-stack-final-runtime-assistant into release/v2 2026-08-09 01:47:57 +02:00
Thomas Camlong 4e9b7a68ba Merge branch feat/v2-stack-final-runtime-mobile into feat/v2-stack-final-runtime-assistant 2026-08-09 01:47:41 +02:00
Thomas Camlong a75f6e1251 Merge branch feat/v2-stack-final-runtime-modernization into feat/v2-stack-final-runtime-mobile 2026-08-09 01:47:31 +02:00
Thomas Camlong 1db3b2c2cf Merge branch feat/v2-stack-final-runtime-dnd into feat/v2-stack-final-runtime-modernization
# Conflicts:
#	packages/widgets/src/clock/component.tsx
2026-08-09 01:47:20 +02:00
Thomas Camlong 1b663e56ee Merge branch feat/v2-stack-final-runtime-v2 into feat/v2-stack-final-runtime-dnd 2026-08-09 01:46:45 +02:00
Thomas Camlong 5ee14136ac fix(clock): defer live time until mount 2026-08-09 01:46:30 +02:00
Thomas Camlong 58342fabc5 Merge branch 'feat/v2-stack-final-runtime-mobile' into feat/v2-stack-final-runtime-assistant 2026-08-09 01:12:40 +02:00
Thomas Camlong 4da705b752 Merge branch 'feat/v2-stack-final-runtime-modernization' into feat/v2-stack-final-runtime-mobile 2026-08-09 01:12:40 +02:00
Thomas Camlong 4a47fc4551 Merge branch 'feat/v2-stack-final-runtime-dnd' into feat/v2-stack-final-runtime-modernization
# Conflicts:
#	packages/widgets/src/clock/component.tsx
2026-08-09 01:12:32 +02:00
Thomas Camlong 3abdae490e Merge branch 'feat/v2-stack-final-runtime-v2' into feat/v2-stack-final-runtime-dnd 2026-08-09 01:12:12 +02:00
Thomas Camlong 9d3d40d4bd fix(e2e): stabilize initial board hydration 2026-08-09 01:11:38 +02:00
Thomas Camlong b85719bc77 Merge branch 'feat/v2-stack-e2e-fix-mobile' into feat/v2-stack-e2e-fix-assistant 2026-08-09 00:40:17 +02:00
Thomas Camlong 77caf92e16 Merge branch 'feat/v2-stack-e2e-fix-modernization' into feat/v2-stack-e2e-fix-mobile 2026-08-09 00:40:16 +02:00
Thomas Camlong 1367e07f20 test(widgets): stabilize advanced focus hover 2026-08-09 00:39:38 +02:00
Thomas Camlong bbdbae636a Merge branch 'feat/v2-stack-e2e-fix-dnd' into feat/v2-stack-e2e-fix-modernization
# Conflicts:
#	packages/widgets/src/clock/component.tsx
2026-08-09 00:39:38 +02:00
Thomas Camlong 10719bc72e Merge branch 'feat/v2-stack-e2e-fix-v2' into feat/v2-stack-e2e-fix-dnd 2026-08-09 00:37:47 +02:00
Thomas Camlong 48d8517ed1 fix(clock): suppress live timestamp hydration noise 2026-08-09 00:37:33 +02:00
Thomas Camlong bd608f8985 Merge branch 'feat/v2-stack-e2e-fix-mobile' into feat/v2-stack-e2e-fix-assistant 2026-08-09 00:14:56 +02:00
Thomas Camlong d9f988a534 Merge branch 'feat/v2-stack-e2e-fix-modernization' into feat/v2-stack-e2e-fix-mobile 2026-08-09 00:14:44 +02:00
Thomas Camlong 074902ecc8 Merge branch 'feat/v2-stack-e2e-fix-dnd' into feat/v2-stack-e2e-fix-modernization 2026-08-09 00:14:32 +02:00
Thomas Camlong d17752188f Merge branch 'feat/v2-stack-e2e-fix-v2' into feat/v2-stack-e2e-fix-dnd 2026-08-09 00:14:22 +02:00
Thomas Camlong 7619492516 test(v2): make board tour dismissal optional 2026-08-09 00:14:10 +02:00
Thomas Camlong 89b9940c43 Merge branch 'feat/v2-stack-e2e-fix-mobile' into feat/v2-stack-e2e-fix-assistant 2026-08-09 00:11:59 +02:00
Thomas Camlong 60e0b537c1 Merge branch 'feat/v2-stack-e2e-fix-modernization' into feat/v2-stack-e2e-fix-mobile 2026-08-09 00:11:51 +02:00
Thomas Camlong 5e035e14ed Merge commit '9f36be028' into feat/v2-stack-e2e-fix-modernization 2026-08-09 00:11:41 +02:00
Thomas Camlong 9f36be0284 Merge commit 'ca6a019c1' into feat/v2-stack-e2e-fix-dnd
# Conflicts:
#	apps/nextjs/src/app/[locale]/boards/(content)/_board-add-menu.tsx
#	apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx
#	packages/translation/src/lang/en.json
2026-08-09 00:11:33 +02:00
Thomas Camlong ca6a019c1e test(v2): stabilize production E2E flows 2026-08-09 00:10:49 +02:00
Thomas Camlong b9f2054d35 Merge branch 'feat/v2-stack-unit-fix-mobile' into feat/v2-stack-unit-fix-assistant 2026-08-08 23:53:43 +02:00
Thomas Camlong 2182d8df92 test(mcp): review board reset layout tool 2026-08-08 23:53:33 +02:00
Thomas Camlong 50dc17484b Merge branch 'feat/v2-stack-unit-fix-mobile' into feat/v2-stack-unit-fix-assistant 2026-08-08 23:39:29 +02:00
Thomas Camlong 1d9f29b4f1 Merge commit '05eebc097' into feat/v2-stack-unit-fix-mobile 2026-08-08 23:39:29 +02:00
Thomas Camlong 05eebc097f test(mcp): review smart home entity details tool 2026-08-08 23:39:15 +02:00
Thomas Camlong 6b1870e813 Merge branch 'feat/v2-stack-unit-fix-mobile' into feat/v2-stack-unit-fix-assistant 2026-08-08 23:28:52 +02:00
Thomas Camlong 8ea4bc293d Merge commit '0bd9c6f29' into feat/v2-stack-unit-fix-mobile 2026-08-08 23:28:45 +02:00
Thomas Camlong 0bd9c6f290 style(widgets): split query matcher type imports 2026-08-08 23:28:33 +02:00
Thomas Camlong 87b2c0cdcc fix(assistant): preserve lazy board boundaries 2026-08-08 23:24:13 +02:00
Thomas Camlong d79774be1d Merge commit 'c3483a1ec' into feat/v2-stack-unit-fix-assistant 2026-08-08 23:22:00 +02:00
Thomas Camlong c3483a1ec9 test(boards): include layout roles in gutter fixtures 2026-08-08 23:21:55 +02:00
Thomas Camlong 406dc1f14f Merge commit 'bc896c0b0' into feat/v2-stack-unit-fix-mobile 2026-08-08 23:21:40 +02:00
Thomas Camlong bc896c0b03 fix(widgets): keep board graph imports scoped 2026-08-08 23:21:35 +02:00
Thomas Camlong 89d974d7ef Merge mobile boards into Homarr assistant
# Conflicts:
#	package.json
2026-08-08 23:07:39 +02:00
Thomas Camlong d8b46f239c Merge widget modernization into mobile boards 2026-08-08 23:07:10 +02:00
Thomas Camlong 14380e1cdf Merge dnd-kit grid into widget modernization 2026-08-08 23:07:00 +02:00
Thomas Camlong 0ece179558 Merge v2 unit-test project filter into dnd-kit grid 2026-08-08 23:06:54 +02:00
Thomas Camlong cae976b6d8 fix(ci): exclude e2e project from unit gate 2026-08-08 23:06:42 +02:00
Thomas Camlong 1259dd7bf7 Merge mobile boards into Homarr assistant
# Conflicts:
#	package.json
2026-08-08 22:52:10 +02:00
Thomas Camlong 6157a2504b Merge widget modernization into mobile boards 2026-08-08 22:51:24 +02:00
Thomas Camlong c2064faa95 Merge dnd-kit grid into widget modernization 2026-08-08 22:51:17 +02:00
Thomas Camlong 6d1538d3b1 Merge v2 unit-test gate into dnd-kit grid 2026-08-08 22:51:11 +02:00
Thomas Camlong 215f9a7d21 fix(ci): exclude e2e tests from unit gate 2026-08-08 22:50:57 +02:00
Thomas Camlong 882c0dcf07 Merge mobile boards into Homarr assistant 2026-08-08 22:39:29 +02:00
Thomas Camlong 06353862cf Merge widget modernization into mobile boards 2026-08-08 22:39:19 +02:00
Thomas Camlong eae55ce2f0 Merge dnd-kit grid into widget modernization 2026-08-08 22:39:10 +02:00
Thomas Camlong 12412947ee Merge v2 bundle checker into dnd-kit grid 2026-08-08 22:39:00 +02:00
Thomas Camlong dec46f3ee4 fix(ci): detect custom widget runtime chunks 2026-08-08 22:38:42 +02:00
Thomas Camlong 5a62b23cc2 docs: refresh assistant OpenAPI schema 2026-08-08 22:22:09 +02:00
Thomas Camlong adb4d7c6a2 Merge commit '360fd59771bd8e62247b4ea9b1b7cb4839b0105b' into feat/v2-stack-final-assistant 2026-08-08 22:21:07 +02:00
Thomas Camlong 360fd59771 style: format mobile migration metadata 2026-08-08 22:20:48 +02:00
Thomas Camlong e04db94631 Merge commit 'e65c98c29063d80cbfd39c48c699de7018892624' into feat/v2-stack-final-mobile 2026-08-08 22:20:17 +02:00
Thomas Camlong e65c98c290 fix(ci): make modernization independently installable 2026-08-08 22:19:20 +02:00
Thomas Camlong 1b6c4a9022 Merge commit '3246dc4c91656dec3c87267ae074436267ad77ac' into feat/v2-stack-final-modernization
# Conflicts:
#	apps/docs/docs/widgets/downloads/index.mdx
#	apps/docs/docs/widgets/media-server/index.mdx
2026-08-08 22:17:04 +02:00
Thomas Camlong 3246dc4c91 style: format dnd migration metadata 2026-08-08 22:16:16 +02:00
Thomas Camlong 97e89a936c Merge commit 'aaf86a9d1' into feat/v2-stack-final-dnd 2026-08-08 22:15:41 +02:00
Thomas Camlong aaf86a9d13 fix(ci): refresh v2 generated artifacts 2026-08-08 22:15:24 +02:00
Thomas Camlong b49cbb7791 Merge commit '254ed9afbabb957617fc17ebc0122063b0dde803' into feat/v2-stack-e2e-assistant 2026-08-08 22:07:09 +02:00
Thomas Camlong 254ed9afba Merge commit '298937b00ca522855e0ebcb95d8c3adccd34fef7' into feat/v2-stack-e2e-mobile 2026-08-08 22:06:53 +02:00
Thomas Camlong 298937b00c Merge commit '25ac7b6a9' into feat/v2-stack-e2e-modernization 2026-08-08 22:06:36 +02:00
Thomas Camlong 25ac7b6a9d test(e2e): wait for settled grid geometry 2026-08-08 22:06:19 +02:00
Thomas Camlong 84f5001bb2 merge: propagate mobile stack CI fixes 2026-08-08 21:53:28 +02:00
Thomas Camlong 83790f6875 merge: propagate modernization CI fixes 2026-08-08 21:53:09 +02:00
Thomas Camlong b937d4eb18 style: format modernization notes 2026-08-08 21:52:51 +02:00
Thomas Camlong 8d35c0c71f merge: propagate dnd stack CI fix 2026-08-08 21:52:32 +02:00
Thomas Camlong a5ce2e9b7a merge: propagate v2 CI fix 2026-08-08 21:52:16 +02:00
Thomas Camlong 2efc225617 fix(ci): align Workshop pnpm version 2026-08-08 21:52:01 +02:00
Thomas Camlong 0c7cbe17fb merge: propagate mobile stack fixes 2026-08-08 21:42:50 +02:00
Thomas Camlong 69359bb39d merge: propagate modernization fixes 2026-08-08 21:42:31 +02:00
Thomas Camlong f24d757ec8 fix: preserve app links and hover previews 2026-08-08 21:42:13 +02:00
Thomas Camlong 9fd7a8a05b merge: stack assistant on mobile boards 2026-08-08 21:27:56 +02:00
Thomas Camlong a039049084 merge: stack auto mobile boards 2026-08-08 20:59:56 +02:00
Thomas Camlong 9edeba5cd2 merge: stack widget modernization on dnd kit 2026-08-08 20:40:26 +02:00
Thomas Camlong 93276abc8c merge: stack v2 into dnd-kit grid 2026-08-08 20:16:04 +02:00
Thomas Camlong dd63d5600b merge dev into v2 2026-08-08 19:54:57 +02:00
ajnart 25be53244b ci(workshop): restore image publishing 2026-08-08 19:25:35 +02:00
ajnart 2657ff4384 fix: repair restored board layouts 2026-08-08 18:44:32 +02:00
ajnart bc41125b7f fix: guard recursive section rendering 2026-08-08 18:24:38 +02:00
ajnart a9fc3c6faa fix: stabilize responsive layout editing 2026-08-08 18:02:39 +02:00
Thomas 8ab3f136b8 fix(workshop): harden deployment and deep links (#6542) 2026-08-08 17:41:54 +02:00
ajnart 1877b4f9a4 fix: improve layout preview density 2026-08-08 16:45:38 +02:00
ajnart a33eb83655 fix: address responsive layout review 2026-08-08 16:19:26 +02:00
ajnart 991e79310a fix: avoid nested board settings forms 2026-08-08 16:02:40 +02:00
ajnart 43a2b4ac30 Merge remote-tracking branch 'origin/dev' into feat/auto-mobile-boards
# Conflicts:
#	apps/docs/docs/management/boards/index.mdx
#	apps/nextjs/src/app/[locale]/boards/[name]/settings/_layout.tsx
2026-08-08 15:47:10 +02:00
ajnart 6728228387 feat: protect responsive board layouts 2026-08-08 15:41:14 +02:00
Thomas 464d98e124 feat(settings): unify manage and board settings into a single save form (#6541) 2026-08-08 15:33:38 +02:00
ajnart 74c7754890 fix(assistant): prevent panel focus capture 2026-08-08 14:26:04 +02:00
ajnart fa9444323b refactor: simplify mobile board layouts 2026-08-08 13:54:42 +02:00
ajnart 7e7b338d18 fix: refine mobile board preview 2026-08-08 13:09:20 +02:00
ajnart 0d1dd02184 fix: address mobile board review 2026-08-08 12:34:10 +02:00
ajnart bc90faf5f2 test: cover tablet mobile boards 2026-08-08 12:23:36 +02:00
ajnart 2c1e697f68 feat: add mobile layout preview 2026-08-08 12:21:53 +02:00
ajnart 5a21df86e2 merge: sync dev changes 2026-08-08 11:56:18 +02:00
Thomas 987c9ade18 fix: make internal nginx proxy tolerate disabled IPv6 (#4596) (#6539)
* fix: keep nginx IPv6 listen only when IPv6 is available (#4596)

Commit 2c48b1896 (1.45.2) made the internal nginx proxy listen on the IPv6
wildcard ('listen [::]:7575;'), which aborts nginx startup on hosts/VMs with
IPv6 disabled:

    nginx: [emerg] socket() [::]:7575 failed (97: Address family not supported by protocol)

A plain revert would reintroduce #2697 and break IPv6-only/dual-stack
environments where this listener is required. Instead, only emit the IPv6
listen directive when the host actually has IPv6 configured.

Reuse the envsubst step run.sh already performs: compute NGINX_LISTEN_IPV6
from /proc/net/if_inet6 and substitute it into the template. proc files always
report size 0, so presence is detected by grepping the file (not 'test -s').

* fix: honor HOMARR_DISABLE_IPV6 override in nginx config generation

Review feedback on #6539: give users an explicit opt-out from the IPv6
listener even when the host reports IPv6. The issue reporter (#4596)
requested such an env var, and the pattern matches the existing
ENABLE_DNS_CACHING IPv6 toggle. Auto-detection behavior is unchanged when
the var is unset. Validated in containers: override wins, dual-stack
preserved by default, IPv4-only fallback on IPv6-less hosts.
2026-08-08 11:16:49 +02:00
Thomas 20fd49f082 fix(assistant): mysql message storage, migration metadata and per-page bundle cost (#6530) 2026-08-08 09:34:16 +02:00
Sven Gottwald 6141d505e4 fix(docker): Expose TCP port in Dockerfile (#6538) 2026-08-08 08:53:50 +02:00
Thomas 6178843198 Merge pull request #6536 from homarr-labs/docs/update-helm-docs 2026-08-07 23:53:22 +02:00
github-actions[bot] a7006d746e docs(helm): update chart installation page 2026-08-07 19:37:35 +00:00
Thomas 06724cfeb2 ci: checkout repo in bot auto-merge workflows (#6535)
* ci: drop --delete-branch from reconcile auto-merge call

The reconcile job runs without a git checkout, so gh pr merge --delete-branch
fails with 'not a git repository' before enabling auto-merge. Auto-merge
itself needs no local git state; branch cleanup is already handled by the
PR-creating workflows.

* ci: checkout repo in bot auto-merge workflows

gh pr merge fails with 'not a git repository' when no checkout is present,
which prevented the reconcile job from enabling auto-merge. Restore the
checkout the previous approval workflow relied on.
2026-08-07 20:31:56 +02:00
homarr-update-contributors[bot] 131a5558b5 chore: update contributors (#6527) 2026-08-07 20:26:34 +02:00
Thomas b32ad82850 perf: cut cold route compilation and unblock preview images (#6441) 2026-08-07 20:24:26 +02:00
Thomas 622e1dcfa5 ci: drop --delete-branch from reconcile auto-merge call (#6534)
The reconcile job runs without a git checkout, so gh pr merge --delete-branch
fails with 'not a git repository' before enabling auto-merge. Auto-merge
itself needs no local git state; branch cleanup is already handled by the
PR-creating workflows.
2026-08-07 20:12:37 +02:00
homarr-releases[bot] 9674bde758 chore: update bug report template (#6533)
Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
2026-08-07 18:08:24 +00:00
Thomas b479ada14f ci: auto-approve and auto-merge trusted bot pull requests (#6531)
* ci: auto-approve and auto-merge trusted bot pull requests

Replace the approve-only automatic-approval workflow with a combined
approve + enable-auto-merge workflow that also re-runs on every push,
plus a scheduled reconcile job that re-enables auto-merge for prepared
bot PRs if GitHub ever disables it. Bot PRs are gated to the known bot
actors, same-repo head, and base branch dev.

* ci: match bot authors by app slug in reconcile filter

gh pr list reports pull request authors of GitHub Apps as app/<slug>;
adjust the allowlist so the reconcile job actually matches bot PRs.
2026-08-07 20:05:46 +02:00
Releases Homarr bff3383ac9 chore(release): 1.74.0 [skip ci] 2026-08-07 17:41:07 +00:00
homarr-releases[bot] d865733838 chore(release): automatic release v1.74.0 2026-08-07 17:39:49 +00:00
Thomas Camlong 0e3461c6d7 fix: only auto-focus the assistant composer in the floating panel
The composer auto-focused in both the board widget and the floating
panel, so on page load the widget stole focus and scrolled the page to
it, and opening the panel made focus jump between the two composers.
Make auto-focus opt-in via an autoFocusComposer prop: the floating panel
enables it (explicit intent to compose), while the widget leaves it off.
2026-08-07 18:57:44 +02:00
homarr-crowdin[bot] 85fcea3837 chore(lang): updated translations from crowdin (#6500) 2026-08-07 18:22:43 +02:00
Thomas Camlong 231ac14703 test(integrations): add missing timeout to nextcloud recurring-range test
The 'recurring events that go beyond the range' test was missing the
20s timeout used by every other test in the file, so it fell back to
vitest's default 5s and intermittently timed out against the slow
Nextcloud container. Add the explicit timeout.
2026-08-07 17:52:49 +02:00
Thomas Camlong aa1e6ebdc8 ci: retry flaky container-backed unit tests in fast gate
Integration tests (nzbget, nextcloud, pi-hole, etc.) start Docker
containers and intermittently time out under shared-runner load, which
fails the fast gate and skips the container job that publishes the PR
preview image. Retry failing unit tests twice so transient container
startup failures do not block the image publish.
2026-08-07 17:47:47 +02:00
Thomas Camlong da93adac14 ci: make e2e tests resilient to CI resource contention
E2E specs spin up Docker containers in parallel and intermittently time
out waiting for UI elements under shared-runner load. Limit parallel
workers to 2 and retry flaky failures twice so the container job can
complete and publish the PR preview image.
2026-08-07 16:28:33 +02:00
Thomas Camlong 926b7e2a5e test(e2e): wait for login form hydration before filling
The customJsx E2E test occasionally failed at login with the form
showing 'This field is required' on the username, because the submit
button was clicked while the form was still hydrating (it is disabled
until mounted) and the field values were reset by the re-mount. Wait
for the submit button to become enabled before filling, and assert the
username value stuck before submitting.
2026-08-07 15:49:21 +02:00
Thomas Camlong 17f08c6d14 ci: skip CI for autofix commits
The auto-fix workflow pushes a 'style: auto-fix lint and format' commit
after every user push, which triggers a new CI run that cancels the
in-progress run (concurrency cancel-in-progress) before the container
job can finish and publish the PR preview image. Mark the autofix commit
with [skip ci] so it does not restart CI, letting the user's run
complete and publish the image.
2026-08-07 15:25:03 +02:00
Thomas Camlong 32cad81e1c chore: remove unused assistant subtitle translation 2026-08-07 15:14:23 +02:00
Thomas Camlong eaacfdf7ce chore: simplify assistant header branding
Remove the 'Connected to this Homarr instance' subtitle (and the running
state text) from the assistant panel header, keeping only the robot icon
and title.
2026-08-07 15:07:26 +02:00
Thomas Camlong 96659fb543 chore: shrink demo assistant widget to 3x3 2026-08-07 15:04:24 +02:00
Thomas d625640051 feat: add assistant widget to demo dashboard (#6529) 2026-08-07 15:02:03 +02:00
Thomas b14756874e fix: handle missing message in assistant generation telemetry (#6528) 2026-08-07 14:59:59 +02:00
ajnart 6bbb786ede style: auto-fix lint and format 2026-08-07 12:54:24 +00:00
Thomas Camlong 31d9e8d582 fix: prevent infinite render loop in assistant telemetry
RequestTelemetry derived thread message metadata inside the useAuiState
selector with messages.map(), returning a new array on every render.
useSyncExternalStore treats the new reference as a changed snapshot,
causing an infinite re-render loop (React error #185) while an assistant
message streams. Select the stable messages reference and memoize the
metadata derivation instead.
2026-08-07 14:53:05 +02:00
Thomas bc12aba433 Merge branch 'dev' into feat/homarr-assistant 2026-08-06 21:53:57 +02:00
Thomas f76a0f240a feat(apps): allow custom URI schemes in application links (#6521) 2026-08-06 21:52:56 +02:00
Thomas 637f03e107 docs(settings): update analytics docs to PostHog (#6525) 2026-08-06 21:47:04 +02:00
Thomas 55214ccbc5 fix(integrations): throw on total item-info failure to preserve stale-cache fallback (#6522) 2026-08-06 21:42:46 +02:00
Thomas eca8b4286d feat: mock Homarr Assistant in demo mode and fix board page crash (#6524) 2026-08-06 21:33:03 +02:00
ajnart fce950d406 test: wait for Home Assistant API readiness 2026-08-04 18:31:23 +02:00
ajnart bcdd56f714 feat: polish assistant conversations 2026-08-04 18:22:18 +02:00
ajnart 8e557fb9fd feat: improve assistant interactions 2026-08-04 13:21:39 +02:00
Thomas Camlong 4bb862ee76 fix: stabilize advanced widget interactions 2026-08-03 21:45:00 +02:00
Thomas Camlong 0c5251a971 fix: finish widget modernization audit 2026-08-03 21:01:55 +02:00
Thomas Camlong 454752e46d fix: address widget review feedback 2026-08-03 20:04:56 +02:00
Thomas Camlong 596bfc317d fix: scope advanced preview controls 2026-08-03 19:42:43 +02:00
Thomas Camlong 97444d209a ci: limit turbo concurrency 2026-08-03 19:17:28 +02:00
Thomas Camlong ee51227a55 fix: restore notifications query scope 2026-08-03 18:53:26 +02:00
Thomas Camlong db7c850596 fix: remove stale notifications import 2026-08-03 18:52:02 +02:00
ajnart 30344bdaae style: auto-fix lint and format 2026-08-03 16:46:35 +00:00
Thomas Camlong 7847eea1a3 fix: refine widget interactions 2026-08-03 18:45:16 +02:00
ajnart 1642a862d9 stabilize hydrated login actions 2026-08-03 18:37:05 +02:00
ajnart 62f5c1cbc3 stabilize integration router tests 2026-08-03 18:14:25 +02:00
ajnart 5e857c0e0a fix Docker dependency fetch scripts 2026-08-03 17:55:03 +02:00
ajnart 5da70164a6 style: auto-fix lint and format 2026-08-03 15:40:15 +00:00
ajnart fbe381cbb5 style: auto-fix lint and format 2026-08-03 15:39:36 +00:00
Thomas af25e78120 Merge branch 'dev' into feat/auto-mobile-boards 2026-08-03 17:39:10 +02:00
Thomas 8d98408cfe Merge branch 'dev' into feat/dnd-kit-grid 2026-08-03 17:38:51 +02:00
Thomas 9234ca0f32 Merge branch 'dev' into feat/widget-modernization 2026-08-03 17:38:28 +02:00
Thomas 778533a62e fix: revert regressions from recent integration/releases/app-href changes (#6514) 2026-08-03 17:37:53 +02:00
ajnart f66d982ff3 fix duplicate release success flag 2026-08-03 17:35:28 +02:00
ajnart cd16ab8e84 Merge remote-tracking branch 'origin/dev' into feat/homarr-assistant 2026-08-03 17:34:33 +02:00
ajnart eaa7fd8bc3 clarify future Homarr provider 2026-08-03 17:25:14 +02:00
ajnart 4b62cbfb4b fix integration permission checks 2026-08-03 17:25:14 +02:00
Thomas Camlong 61a2f8cb6c feat: modernize widget experience 2026-08-03 17:18:22 +02:00
ajnart 5155d7b742 fix merged dependency lockfile 2026-08-03 17:14:29 +02:00
Thomas Camlong 596833f227 fix container header spacing 2026-08-03 17:13:02 +02:00
Thomas b4fba47a23 fix(releases): add success flag to successful response for correct error detection (#6512) 2026-08-03 17:10:35 +02:00
ajnart 56a6d4989a Merge remote-tracking branch 'origin/dev' into feat/homarr-assistant
# Conflicts:
#	pnpm-lock.yaml
2026-08-03 17:08:45 +02:00
Thomas 76ddfceb13 fix: restore auto-refresh for docker and notifications widgets (#6511) 2026-08-03 17:07:46 +02:00
ajnart c43ab2ee56 fix assistant provider icon contrast 2026-08-03 17:05:12 +02:00
Thomas 2f6b96e5ba feat: restrict integrations management to integration-full-all permission (#6509) 2026-08-03 17:04:35 +02:00
ajnart f7b291b465 add OpenRouter web search option 2026-08-03 16:59:27 +02:00
Thomas f1b7a5146a fix: allow [homarr_base] template variables in app href validation (#6508) 2026-08-03 16:47:40 +02:00
Thomas Camlong cc71ab8cd9 fix: contain board canvas overflow 2026-08-03 16:46:43 +02:00
ajnart 35673f113e fix assistant widget configuration 2026-08-03 16:43:08 +02:00
ajnart bd3e4c33f3 enhance assistant batch responses 2026-08-03 15:48:41 +02:00
ajnart 6a98036c50 show assistant icon search previews 2026-08-03 15:36:10 +02:00
ajnart 59bed6b995 fix assistant streaming scroll lock 2026-08-03 15:30:10 +02:00
Thomas Camlong 05292ae1f5 fix: map legacy media server options 2026-08-03 15:24:30 +02:00
ajnart 5a3022a073 integrate assistant custom widget workflow 2026-08-03 15:22:08 +02:00
Thomas Camlong 49d623e5c2 fix: defer dependency scripts during container fetch 2026-08-03 15:18:07 +02:00
ajnart ab21a9fece style: auto-fix lint and format 2026-08-03 12:50:22 +00:00
Thomas Camlong 41fffbb79b Merge dev into feat/dnd-kit-grid 2026-08-03 14:48:31 +02:00
homarr-renovate[bot] 48fc8db2b4 chore(deps): update semantic-release monorepo (major) (#6486) 2026-08-03 14:39:28 +02:00
Thomas 018f84f80d feat(dev): add Docker development CLI (#6495) 2026-08-03 14:38:56 +02:00
Thomas 36ac1b40ff Merge branch 'dev' into feat/auto-mobile-boards 2026-08-03 14:38:45 +02:00
Thomas Camlong 08da71d67f fix: contain collapsed layout and widget overflow 2026-08-03 14:27:45 +02:00
ajnart 65ff0ecb34 docs: add widget modernization audit 2026-08-03 14:27:30 +02:00
ajnart abc17e7c2a fix assistant prompt submission 2026-08-03 14:12:13 +02:00
ajnart c81a3b9432 refresh views after assistant changes 2026-08-03 13:38:20 +02:00
Thomas Camlong 80d46b0d85 restore context menu focus 2026-08-03 13:28:12 +02:00
Thomas Camlong 6e15f8bcf8 fix context menu keyboard focus 2026-08-03 13:24:13 +02:00
Thomas Camlong 294217fcba fix board E2E login race 2026-08-03 13:09:56 +02:00
ajnart 8dd1d70900 improve assistant question placement 2026-08-03 13:07:07 +02:00
Thomas Camlong 3c99c75ad7 fix SVG focus scrolling 2026-08-03 12:51:10 +02:00
ajnart 9e0a6a758f fix assistant automatic approvals 2026-08-03 12:34:54 +02:00
Thomas Camlong 20dbdcedf2 fix focused pane scrolling 2026-08-03 12:25:56 +02:00
Thomas Camlong a2f5668c44 fix focus scroll layering 2026-08-03 12:21:31 +02:00
Thomas Camlong 09e9cd4388 fix widget focus interactions 2026-08-03 12:15:12 +02:00
Thomas Camlong 5c9fd28ae1 test: wait for resized grid geometry 2026-08-02 19:27:09 +02:00
Thomas Camlong 8f0c9435ff test: stabilize board grid authentication 2026-08-02 19:14:40 +02:00
Thomas Camlong 96e92485a5 fix: align responsive widget actions 2026-08-02 18:10:25 +02:00
Thomas Camlong f9aae23f46 fix: keep session labels readable 2026-08-02 17:41:27 +02:00
Thomas Camlong fd024e32d2 fix: simplify widget feedback 2026-08-02 17:39:28 +02:00
Thomas Camlong 5010ecf188 fix: harden widget error feedback 2026-08-02 17:34:52 +02:00
Thomas Camlong f0d8e97f8d fix: address final widget review 2026-08-02 17:25:02 +02:00
ajnart 8f81de775e chore: remove unused ai sdk dependency 2026-08-02 16:57:11 +02:00
Thomas Camlong ed8ef15f12 fix: localize widget chart formatting 2026-08-02 16:48:34 +02:00
ajnart 8a2375b3ef fix: address assistant review feedback 2026-08-02 16:45:14 +02:00
Thomas Camlong d97f1bd34b fix: align widget accessibility and locales 2026-08-02 16:42:08 +02:00
Thomas Camlong d86293de0f fix: canonicalize widget date locales 2026-08-02 16:32:59 +02:00
Thomas Camlong 54e444d69d fix: close final widget review gaps 2026-08-02 16:26:04 +02:00
Thomas Camlong d5c3aebd26 fix: harden widget review follow-ups 2026-08-02 16:10:16 +02:00
ajnart 1777ca4446 style: auto-fix lint and format 2026-08-02 13:58:01 +00:00
ajnart cf6aefa48a fix: isolate assistant conversation state 2026-08-02 15:57:05 +02:00
Thomas Camlong 56e3bd1cf2 fix: smooth container collapse behavior 2026-08-02 15:57:05 +02:00
Thomas Camlong 7b7045202d fix: address widget review findings 2026-08-02 14:35:01 +02:00
ajnart d7969e1ec1 fix: harden assistant interactive tools 2026-08-02 14:20:40 +02:00
Thomas Camlong 1e0bf40040 feat: refine widget interactions 2026-08-02 14:12:18 +02:00
Thomas Camlong bdffb63ebc Merge remote-tracking branch 'origin/dev' into feat/widget-modernization 2026-08-02 14:06:03 +02:00
ajnart eff3168f1c feat: improve assistant tool workflows 2026-08-02 11:24:11 +02:00
ajnart 4db4d947db style: auto-fix lint and format 2026-08-02 09:04:11 +00:00
Thomas Camlong 0755fc1e26 fix: refine container interactions 2026-08-02 11:02:55 +02:00
ajnart 81a2f18c7c fix: map legacy media server options 2026-08-02 10:20:40 +02:00
ajnart 9eaadd83fb Merge remote-tracking branch 'origin/dev' into feat/homarr-assistant 2026-08-02 10:18:52 +02:00
Thomas d58a47b776 fix: restrict system health monitoring integrations (#6505) 2026-08-02 10:17:05 +02:00
Thomas Camlong d460ac8bbb fix: secure widget navigation links 2026-08-02 10:01:36 +02:00
Thomas Camlong 1d73513989 fix: polish widget failure feedback 2026-08-02 09:50:08 +02:00
Thomas Camlong e3f679237d fix: polish board grid interactions 2026-08-02 09:36:33 +02:00
Thomas Camlong 45ff97972c fix: address final widget review findings 2026-08-02 09:27:31 +02:00
ajnart 8bb0ddef42 fix: secure assistant generation telemetry 2026-08-02 09:09:01 +02:00
Thomas Camlong 36b75a071b fix: complete widget review cleanup 2026-08-02 09:05:02 +02:00
ajnart 04520cf67c feat: enrich assistant request telemetry 2026-08-02 08:51:48 +02:00
Thomas Camlong 3f7d934fa5 fix: address remaining widget review feedback 2026-08-02 08:44:00 +02:00
ajnart d562706c8b fix: sync assistant runtime preferences 2026-08-02 08:27:31 +02:00
ajnart 3245356f0f fix: align assistant management layout 2026-08-02 08:23:58 +02:00
Thomas Camlong 76974f676d fix: allow dynamic database schema access 2026-08-02 08:19:46 +02:00
ajnart 0d23bafa3d fix: hide inactive assistant scroll control 2026-08-02 08:18:05 +02:00
Thomas Camlong 5ddbf184fd ci: limit fast gate concurrency 2026-08-02 08:10:14 +02:00
Thomas Camlong c61695b142 fix: make integration warnings keyboard accessible 2026-08-02 08:09:55 +02:00
Thomas Camlong b9b938c658 fix: address board grid CI and review 2026-08-02 08:05:25 +02:00
Thomas Camlong 6f27ede8bf fix: complete widget modernization review 2026-08-02 08:05:04 +02:00
Thomas Camlong 79ae42f0c3 fix: sync widget modernization with dev 2026-08-02 01:37:01 +02:00
Thomas Camlong c754946089 fix: address widget review feedback 2026-08-01 23:44:17 +02:00
ajnart ab7d385a13 style: auto-fix lint and format 2026-08-01 21:38:41 +00:00
Thomas c53b38a738 Merge branch 'dev' into feat/dnd-kit-grid 2026-08-01 23:37:27 +02:00
Thomas 797bdd001f fix: keep direct icon URLs visible (#6504) 2026-08-01 23:28:18 +02:00
Thomas Camlong d0c7ba401f fix: address board grid review 2026-08-01 23:26:02 +02:00
Bergasha 49314533bf feat(Media player widget): expands on Direct Play and Transcoding badges (#6489) 2026-08-01 23:21:13 +02:00
Thomas 27ca058088 feat(widgets): persist Beszel and Docker table layouts (#6490) 2026-08-01 23:20:09 +02:00
homarr-releases[bot] f4449f9581 chore: update bug report template (#6498) 2026-08-01 23:08:14 +02:00
Thomas b52bc03c8d Merge pull request #6499 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-08-01 23:07:58 +02:00
Thomas edb56f3fcd fix: accept direct icon URLs
Use valid direct HTTP(S) icon URLs without repository search.
2026-08-01 23:06:26 +02:00
Thomas Camlong d94cadd3e7 feat: rebuild board grid with dnd-kit 2026-08-01 22:30:40 +02:00
ajnart cce3e455fa style: auto-fix lint and format 2026-08-01 19:47:19 +00:00
Thomas Camlong b28beec2c6 fix: refine widget modernization 2026-08-01 21:45:01 +02:00
Thomas Camlong d421046d15 feat: modernize dashboard widgets 2026-08-01 21:14:33 +02:00
ajnart b7034e59c1 fix: polish assistant widget interactions 2026-08-01 18:25:48 +02:00
ajnart d6c1a87a03 test: wait for grid reconciliation between drags 2026-08-01 17:56:09 +02:00
ajnart d111f7e981 fix: correct assistant widget docs metadata 2026-08-01 15:00:03 +02:00
ajnart 28b6321b1c style: auto-fix lint and format 2026-08-01 12:52:44 +00:00
ajnart 0a8e6ed194 feat: add assistant conversation widget 2026-08-01 14:51:20 +02:00
ajnart 50907c7289 fix: clarify board grid editing affordances 2026-08-01 14:13:07 +02:00
ajnart 6e28712ea7 fix: simplify assistant shortcut 2026-08-01 11:09:10 +02:00
ajnart 5458e7d647 feat: add assistant board styling tools 2026-08-01 10:39:41 +02:00
ajnart a5fa6d3afe fix: restore assistant approval flows 2026-08-01 09:54:11 +02:00
ajnart 9c8fa8158b fix: improve board grid interactions 2026-08-01 00:43:36 +02:00
Thomas Camlong e6e413bcb7 test: target visible layout preview 2026-08-01 00:43:36 +02:00
Thomas Camlong 81d14e39ab test: tolerate sticky rail subpixel rounding 2026-08-01 00:43:36 +02:00
Thomas Camlong f207587791 test: avoid grid drag rounding boundary 2026-08-01 00:43:36 +02:00
Thomas Camlong 5cc2df19ed fix: satisfy database namespace lint 2026-08-01 00:43:36 +02:00
ajnart 0a80d70730 style: auto-fix lint and format 2026-08-01 00:43:36 +02:00
ajnart 25c9955cc6 feat: rebuild board grid experience 2026-08-01 00:43:28 +02:00
homarr-crowdin[bot] fcc5130068 chore(lang): update translations from crowdin (#6457) 2026-07-31 23:50:10 +02:00
homarr-update-contributors[bot] c7751e293d chore: update contributors (#6496) 2026-07-31 23:49:37 +02:00
github-actions[bot] bd669a1a8f docs(helm): update chart installation page 2026-07-31 19:35:37 +00:00
Releases Homarr 9b5b79525f chore(release): 1.73.0 [skip ci] 2026-07-31 18:16:44 +00:00
homarr-releases[bot] 426929556f chore(release): automatic release v1.73.0 2026-07-31 18:15:13 +00:00
Thomas ade640dcea test(navidrome): complete cleanup review (#6494)
* test(navidrome): hoist album fixture helper

* test(navidrome): address coderabbit cleanup feedback
2026-07-31 14:39:11 +02:00
Thomas 61128ddcc8 fix(navidrome): clean up audio stats integration (#6493) 2026-07-31 14:20:51 +02:00
Thomas d0e28a5fc9 Merge branch 'dev' into feat/homarr-assistant 2026-07-31 13:55:31 +02:00
Thomas 6a3cee8107 fix(navidrome): complete media server session data (#6492) 2026-07-31 13:53:06 +02:00
Thomas 1b55514d98 Merge branch 'dev' into feat/homarr-assistant 2026-07-31 13:32:03 +02:00
Matt Van Horn 06d9c07d22 feat: expose Navidrome as a media-server (now-playing) integration (#6070) 2026-07-31 13:30:44 +02:00
Thomas 557ebe0a2f fix: harden v2 custom widgets and Workshop (#6480) 2026-07-31 13:28:53 +02:00
ajnart 9a2d6f692e feat: expose assistant global shortcuts 2026-07-31 06:58:39 +02:00
ajnart bf53a2a262 fix: keep assistant menu selection visible 2026-07-31 06:32:57 +02:00
ajnart 1d15b614f3 refactor: group assistant runtime controls 2026-07-31 06:00:49 +02:00
ajnart e6981a7f44 fix: invoke mutations before approval 2026-07-30 23:31:24 +02:00
ajnart 48dbdcd835 feat: redesign assistant management 2026-07-30 22:59:27 +02:00
ajnart 5fdf408378 docs: explain assistant search focus 2026-07-30 22:04:45 +02:00
ajnart 38e7d2c15f style: auto-fix lint and format 2026-07-30 20:02:54 +00:00
ajnart 82b146bdf6 feat: improve assistant confirmations 2026-07-30 22:01:21 +02:00
ajnart 2aefa23c36 feat: improve assistant background controls 2026-07-30 18:33:54 +02:00
ajnart 19cc115730 refactor: simplify assistant message controls 2026-07-30 17:33:43 +02:00
ajnart 429b587529 fix: complete assistant markdown lockfile 2026-07-30 17:03:14 +02:00
ajnart 93059c1e48 feat: render assistant content 2026-07-30 16:56:43 +02:00
ajnart af75bcd741 fix: send provider model ids 2026-07-30 16:00:09 +02:00
ajnart 6bd331bc73 fix: preserve assistant trigger focus 2026-07-30 14:33:29 +02:00
ajnart ee08870925 fix: address assistant capability review 2026-07-30 14:22:01 +02:00
ajnart 33e23021ae feat: expand assistant interaction capabilities 2026-07-30 14:09:15 +02:00
ajnart 9e6a0c7748 refactor: remove assistant chat archiving 2026-07-30 12:08:21 +02:00
ajnart 212ce64c04 fix: polish assistant mobile layout 2026-07-30 11:47:30 +02:00
ajnart 828f2ebecd fix: harden assistant floating panel 2026-07-30 11:17:08 +02:00
ajnart 0434f6ac10 feat: streamline assistant interactions 2026-07-30 11:06:16 +02:00
ajnart 7f946e45f4 fix: surface assistant stream errors 2026-07-30 10:01:36 +02:00
ajnart c10395fe5d fix: keep assistant in user menu 2026-07-30 09:37:52 +02:00
ajnart f3bdac4d23 fix: clarify assistant credential removal 2026-07-30 09:18:34 +02:00
ajnart be0496af5a fix: invalidate persisted assistant model state 2026-07-30 08:59:01 +02:00
ajnart 5da5433b58 fix: invalidate assistant models on discovery changes 2026-07-30 08:49:51 +02:00
ajnart 703c27c639 fix: gate assistant models on saved connection 2026-07-30 08:42:41 +02:00
ajnart 676ba6c885 fix: clear assistant credentials on destination changes 2026-07-30 08:34:44 +02:00
ajnart aa83fe4986 feat: promote assistant configuration 2026-07-30 08:25:00 +02:00
ajnart 1c3aac5e82 feat: expand assistant providers and polish experience 2026-07-30 00:39:22 +02:00
ajnart 7fec6c8a07 test: wait for Home Assistant restart 2026-07-29 21:18:35 +02:00
ajnart b025cc593c fix: label board edit mode control 2026-07-29 20:58:09 +02:00
ajnart 60cc1c1e29 fix: initialize tablet mobile boards consistently 2026-07-29 20:38:12 +02:00
ajnart b5ffefe707 fix: keep mobile board widgets unchanged 2026-07-29 20:21:59 +02:00
ajnart e2732dce2c Revert "feat: improve mobile board experience"
This reverts commit 7c0e98c55b.
2026-07-29 20:07:37 +02:00
ajnart c1819d6fdc Revert "fix: handle mobile logout failures"
This reverts commit 9f0969ec22.
2026-07-29 20:07:27 +02:00
ajnart 5c4cc81b05 Revert "feat: refine mobile dashboard experience"
This reverts commit 315520beca.
2026-07-29 20:07:17 +02:00
ajnart bbc560706a Revert "style: auto-fix lint and format"
This reverts commit 4bd0d8e8dd.
2026-07-29 20:06:59 +02:00
ajnart 84b334abab Revert "fix: safely extract notebook summaries"
This reverts commit fa3d0375c5.
2026-07-29 20:06:59 +02:00
ajnart 38a710efe3 Revert "fix: address mobile dashboard review findings"
This reverts commit 1039bb18b3.
2026-07-29 20:06:59 +02:00
ajnart 43e95ff185 Revert "fix: collect mobile board e2e test"
This reverts commit 6dbfbb1417.
2026-07-29 20:06:59 +02:00
ajnart d6bb6542c4 Revert "fix: preserve mobile section focus"
This reverts commit 7ce1081161.
2026-07-29 20:06:59 +02:00
ajnart 55ee5ef063 Revert "fix: layer mobile widget details above header"
This reverts commit a540f07920.
2026-07-29 20:06:59 +02:00
ajnart ee3b45b73a Revert "fix: keep interface layers server safe"
This reverts commit cf83e0d849.
2026-07-29 20:06:59 +02:00
ajnart b612fb9a89 Revert "feat: complete automatic mobile board UX"
This reverts commit 7959c20d88.
2026-07-29 20:06:47 +02:00
ajnart dba9a250ff Revert "fix: harden automatic mobile boards"
This reverts commit d0b9348b86.
2026-07-29 20:06:32 +02:00
Thomas 49d0f1a141 docs: explain Podman socket setup (#6487)
* docs: explain Podman socket setup

* docs: clarify Podman runtime requirements

* docs: complete Podman compose examples
2026-07-29 16:53:29 +02:00
ajnart f90fb4512b fix: derive assistant approval key at runtime 2026-07-28 23:47:07 +02:00
ajnart 92a2be2b65 merge: sync dev changes 2026-07-28 23:45:19 +02:00
ajnart 97fc9dda4c ci: limit turbo concurrency 2026-07-28 23:29:45 +02:00
ajnart 5c60489503 fix: hide closed assistant history 2026-07-28 23:19:31 +02:00
Thomas cfad8dd642 Merge branch 'dev' into feat/homarr-assistant 2026-07-28 23:13:43 +02:00
Clarkar00 ac73b9d891 fix(widgets): refetch after render errors (#6399)
Remove incompatible persisted widget query data before resetting a crashed widget so it can fetch fresh data instead of re-rendering the same stale payload.
2026-07-28 23:12:03 +02:00
Thomas 489980814c Merge pull request #6465 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-28 22:59:08 +02:00
ajnart 36ae9f93ee fix: harden assistant integration 2026-07-28 22:56:28 +02:00
Thomas 14654acced chore: gate Renovate updates behind dashboard approval (#6485) 2026-07-28 22:55:32 +02:00
ajnart 82987d1f69 ci: limit fast gate concurrency 2026-07-28 22:48:00 +02:00
Thomas 2885f7a297 fix(widgets): preserve data on integration failures (#6484) 2026-07-28 22:18:08 +02:00
Thomas 906c9f11fa feat(widgets): rework downloads widget with mantine-datatable (#6430) 2026-07-28 21:50:09 +02:00
Thomas 3419805a49 fix(beszel): restore balanced grid layout for Beszel System Grid widget (#6389) 2026-07-28 21:49:04 +02:00
ajnart 00e8790ec2 feat: add Homarr assistant 2026-07-28 21:33:17 +02:00
ajnart d0b9348b86 fix: harden automatic mobile boards 2026-07-28 21:28:53 +02:00
ajnart 7959c20d88 feat: complete automatic mobile board UX 2026-07-28 19:39:13 +02:00
ajnart cf83e0d849 fix: keep interface layers server safe 2026-07-28 19:39:13 +02:00
ajnart a540f07920 fix: layer mobile widget details above header 2026-07-28 19:39:13 +02:00
ajnart 7ce1081161 fix: preserve mobile section focus 2026-07-28 19:39:13 +02:00
ajnart 6dbfbb1417 fix: collect mobile board e2e test 2026-07-28 19:39:12 +02:00
ajnart 1039bb18b3 fix: address mobile dashboard review findings 2026-07-28 19:39:12 +02:00
ajnart fa3d0375c5 fix: safely extract notebook summaries 2026-07-28 19:39:12 +02:00
ajnart 4bd0d8e8dd style: auto-fix lint and format 2026-07-28 19:39:12 +02:00
ajnart 315520beca feat: refine mobile dashboard experience 2026-07-28 19:39:12 +02:00
ajnart 9f0969ec22 fix: handle mobile logout failures 2026-07-28 19:39:12 +02:00
ajnart 7c0e98c55b feat: improve mobile board experience 2026-07-28 19:39:12 +02:00
ajnart bc57f9579d fix: align mobile root section ordering 2026-07-28 19:39:12 +02:00
ajnart e2f9fa871a fix: address board review and CI feedback 2026-07-28 19:39:12 +02:00
ajnart f133e09dc6 feat: add automatic mobile board layout 2026-07-28 19:39:12 +02:00
homarr-releases[bot] 51a6cbe6d4 chore: update bug report template (#6464) 2026-07-28 14:56:56 +02:00
homarr-update-contributors[bot] d08eb2afa6 chore: update contributors (#6461) 2026-07-28 14:56:42 +02:00
Tim MooreandTim Moore 91a6b19ba1 fix(beszel): expire stored sessions so the integration recovers from token expiry (#6476)
Co-authored-by: Tim Moore <tim.moore@ingenuity.com.au>
2026-07-28 14:55:33 +02:00
Thomas e66979bab6 feat(auth): link credentials users with OIDC (#6445) 2026-07-28 14:49:51 +02:00
homarr-renovate[bot] 4ef7641cb2 chore(deps): update pnpm to v11.15.1 (#6466)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-25 02:43:47 +00:00
github-actions[bot] f33fc838cf docs(helm): update chart installation page 2026-07-24 21:17:32 +00:00
Releases Homarr f0019fa213 chore(release): 1.72.0 [skip ci] 2026-07-24 18:11:58 +00:00
homarr-releases[bot] ba8536b36d chore(release): automatic release v1.72.0 2026-07-24 18:10:37 +00:00
homarr-renovate[bot] 965aec5155 chore(deps): update pnpm to v11.15.0 (#6455)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-23 16:29:37 +00:00
homarr-crowdin[bot] 387db030cd chore(lang): updated translations from crowdin (#6283) 2026-07-23 09:14:14 +02:00
homarr-renovate[bot] 8e557c9924 chore(deps): update pnpm to v11.14.0 (#6449)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-23 05:58:19 +00:00
Thomas e8a2c87c2d Delete review.md 2026-07-23 02:13:20 +02:00
Thomas 4f6e335f72 fix(custom-widgets): address final v2 review feedback (#6451) 2026-07-23 02:11:32 +02:00
Thomas f26e60edf0 fix(old-import): map new clock weather options (#6452) 2026-07-23 02:08:50 +02:00
Thomas 7bbfd74454 Merge pull request #6384 from homarr-labs/docs/update-helm-docs
docs(helm): update chart installation page
2026-07-23 02:04:11 +02:00
homarr-releases[bot] 018b1ea4da chore: update bug report template (#6382) 2026-07-23 02:03:43 +02:00
Thomas 14c3fa2e76 fix(beszel): prevent live subscription memory growth (#6448)
* fix(beszel): bound live stats subscriptions

* fix(beszel): avoid blocking subscription teardown

* fix(import): map new clock options
2026-07-23 02:03:08 +02:00
Thomas 9168f8114b fix(manage): hide sidebar nav items from unprivileged users (#6236) 2026-07-23 02:01:10 +02:00
Thomas df967751f6 fix(custom-widgets): address v2 merge review (#6446) 2026-07-23 01:55:15 +02:00
Thomas 9cabb42edb fix(widgets): add 350ms hover delay before opening calendar day popover (#6431) 2026-07-23 01:54:23 +02:00
Thomas bfba3c9215 fix(releases): avoid unnecessary GitHub pagination for LinuxServer updates (#6444) 2026-07-23 00:20:54 +02:00
Bergasha 7184bc09c1 feat(clock widget): Adds animated weather and temp (#6372) 2026-07-23 00:04:32 +02:00
Thomas 6aceb2fd1f feat(custom-widgets): complete Custom JSX v2 (#6390) 2026-07-22 20:47:19 +02:00
Thomas 9bb0c0f811 fix(media-requests): cache statistics responses (#6443) 2026-07-22 17:38:28 +02:00
Thomas cf0e469465 feat: restrict apps management to board modifiers (#6425) 2026-07-21 16:56:33 +02:00
Thomas 8e7fa782b3 feat(weather): add humidity display (#6429) 2026-07-21 16:36:03 +02:00
Thomas 97652802f0 feat(immich): show random photos without album (#6424) 2026-07-21 16:10:45 +02:00
Thomas 1706289124 fix(media-server): guard against undefined playback from stale cached data (#6433) 2026-07-21 16:01:37 +02:00
homarr-renovate[bot] dd23de7aaa chore(deps): update pnpm to v11.13.1 (#6422)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-07-21 05:54:52 +00:00
Thomas f1602f67d4 fix: stop auto-seeding ArchiveTeam Warrior integration and widget on default dashboards (#6413) 2026-07-20 17:01:07 +02:00
Thomas c03cd97d3a fix(test): use Home Assistant long-lived token JWT
* fix(test): use Home Assistant long-lived token

* fix(test): revert to correct Home Assistant long-lived token JWT
2026-07-20 15:27:50 +02:00
Thomas a8e9a074d3 Merge pull request #6411 from homarr-labs/mysql-migration-test-timeout
fix(db): increase mysql migration test timeout to 120s
2026-07-20 15:19:12 +02:00
Thomas Camlong 1e9d3e0656 fix(db): increase mysql migration test timeout to 120s 2026-07-20 15:18:50 +02:00
Thomas 01e0f3612d docs(immich): list Asset View permission (#6408) 2026-07-20 14:57:07 +02:00
Thomas 0015a6d88d fix(immich): use thumbnail endpoint instead of original for album carousel (#6396)
* fix(immich): use thumbnail endpoint instead of original for album carousel

Switches from /api/assets/:id/original to /api/assets/:id/thumbnail to fix
HEIC image incompatibility (thumbnails are always JPEG), reduce bandwidth,
and improve loading speed.

Closes #6388

* working on immich

* Improve image proxy error handling

* fix(image-proxy): use trusted fetch response type

* docs(immich): require Asset View permission
2026-07-20 14:53:27 +02:00
github-actions[bot] ac6a0c9254 docs(helm): update chart installation page 2026-07-17 19:24:43 +00:00
1585 changed files with 196526 additions and 56563 deletions

No files matched your search

@@ -0,0 +1,36 @@
---
name: homarr-custom-widget
description: Author safe Homarr Custom JSX v2 widgets from API documentation.
---
# Homarr Custom Widget
Author one widget at a time. When connected to Homarr, retrieve `homarr://custom-widgets/schema` and only the component resources needed for the design. The live resources are authoritative for the installed release.
Keep documentation retrieval targeted: fetch at most eight named component documents before the first validation, or at most four after loading a complete example. Fetch another component only when a validation issue or a concrete missing interaction requires it; never load the catalog component-by-component.
1. Read the API documentation.
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. 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.
Treat a supplied sample or successful preview response as the binding contract. Render every core requested field, guard optional arrays and nested values before indexing, and do not silently drop returned items. Show concise freshness context when the API supplies a timestamp. Give recoverable load errors and empty states a clear refresh or retry path.
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`.
`SubFetch` with `trigger="manual"` renders its own load button. Pass a card or image through `triggerContent` with `triggerAriaLabel` when that content should launch the request. Its callback is `(result, meta)`; never author `onClick` or a fetch callback.
Use standard Mantine compound names and deliberate visual hierarchy, spacing, restrained semantic color, and responsive layouts. Avoid excessive nested cards.
Aim for distinctive polish without decorative gradients or fragile custom styling: use one semantic accent, clear surface contrast, purposeful typography, and compact rows that remain scannable in narrow tiles. Give standalone icons an accessible label or pair them with visible text.
Make initial states actionable with an example, useful hint, or clear next step. Use wrapping groups for variable-length labels and values on narrow tiles. Do not use an unlabeled decorative icon as an empty state.
Do not use imports, hooks, refs, raw HTML, raw event callbacks, browser requests, arbitrary functions, eval, bigint, npm packages, authored statement blocks, IIFEs, or recursion. Do not pretend MCP tools exist in an offline chat session.
To install an existing widget, use `customWidget_workshopSearch`, inspect it with `customWidget_workshopGet`, then use `customWidget_workshopInstall`. Configure each self-hosted source afterward with `customWidget_sourceConfigure`, or call `customWidget_configurationRequestUser` so the user can enter the server URL and credentials directly in Homarr.
Preview-scoped configuration expires with the preview. To persist user-entered values for a new widget, create the widget first, request configuration for its definition ID, then create a final preview with that definition ID so Homarr loads the stored credentials.
When this skill is installed from the repository, optional offline details are in `references/schema.md`, `references/runtime.md`, and `references/security.md`. MCP-only clients should use the live Homarr resources instead.
@@ -0,0 +1,16 @@
# Runtime
Templates read `data.requestId`, `status.requestId`, `options.name`, and temporary `inputs.name`. Status contains `loading`, `ok`, `status`, `statusText`, and `error`.
`bind="search"` creates an in-memory input. It is never persisted. Supply invocation values only through `params`, for example:
```jsx
<TextInput bind="search" label="Search" />
<SubFetch requestId="search" params={{ query: inputs.search }}>
{(items) => <Stack>{(items ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
</SubFetch>
```
`SubFetch` with `trigger="manual"` renders its own load button. To make a card or image launch the request, pass that node through `triggerContent` and provide `triggerAriaLabel`; Homarr supplies the click and keyboard behavior. Its optional second callback argument contains `{ ok, status, statusText, loading: false }`; loading and request failures are rendered by `SubFetch` before the callback runs. Do not author `onClick` or a fetch callback.
Use expression callbacks for supported collection methods and trusted slots. Do not use callback blocks, IIFEs, authored recursion, or raw events. Bounded regex literals work only with safe string matching/replacement operations.
@@ -0,0 +1,22 @@
# Schema
```ts
interface HomarrCustomWidgetV2 {
$schema: "homarr-custom-widget-v2";
name: string;
description?: string;
iconUrl?: string;
sources: Record<string, CustomWidgetSource>;
requests: Record<string, CustomWidgetRequest>;
options?: Record<string, CustomWidgetOption>;
template: string;
}
```
Sources require `default`. A request defaults to the default source, query, GET, load, inherited auth, and view permission. An action defaults to manual and modify permission. DELETE uses full permission and confirmation.
Use stable real URLs for public APIs and clear suggested URLs for self-hosted services. Homarr collects the installer's server URL, network scope, and credentials as source setup; credentials remain outside the manifest.
Paths use `{option:name}` and `{param:name}`. Query/body data uses `{ "$option": "name" }` and `{ "$param": "name" }`. Load queries cannot use params. Runtime parameter names and primitive values are inferred from references.
Every option has `label`, `control`, and `default`. Optional fields are `description`, `choices`, `choicesFrom`, `min`, `max`, `step`, `advanced`, and `group`.
@@ -0,0 +1,7 @@
# Security
All requests use Homarr's protected server executor. Source origin, network scope, DNS, redirects, SSRF, rate limits, permissions, size limits, timeouts, and encrypted credential injection remain enforced.
The JSX interpreter blocks imports, hooks, refs, raw event callbacks, browser requests, eval, arbitrary functions, prototype access, unsafe URLs, global CSS escape, arbitrary portals, bigint, statement blocks, IIFEs, and recursion. Regex literals must be bounded and reject backreferences, lookbehind, nested quantifiers, excessive length, and unsupported flags.
Credentials are stored separately and never exported or returned to an agent. A published self-hosted source URL is only a suggestion: installers must confirm or replace private and loopback URLs for their own Homarr deployment. Source origins cannot be controlled through widget options.
@@ -0,0 +1,622 @@
# Migrating Tables from mantine-react-table to mantine-datatable
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
## Migration Candidates
Current `mantine-react-table` consumers that could be migrated:
**Widgets (in `packages/widgets/`):**
- `docker/component.tsx` — Docker container table
- `media-server/component.tsx` — Media server sessions
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
- `tools/docker/docker-table.tsx`
- `tools/tasks/_components/tasks-table.tsx`
- `tools/api/components/api-keys.tsx`
- `users/_components/user-list.tsx`
- `users/invites/_components/invite-list.tsx`
- `tools/kubernetes/` — 8 Kubernetes resource tables
**Modals:**
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
**Shared hook to remove once all migrated:**
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
---
## 1. Core Migration: mantine-react-table → mantine-datatable
### Import changes
```typescript
// BEFORE
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
import type { MRT_ColumnDef } from "mantine-react-table";
// AFTER
import { DataTable, useDataTableColumns } from "mantine-datatable";
import type { DataTableColumn, DataTableSortStatus } from "mantine-datatable";
```
### Column definition shape
```typescript
// BEFORE (MRT)
const columns: MRT_ColumnDef<MyRow>[] = [
{
accessorKey: "name",
header: "Name",
size: 200,
enableSorting: true,
Cell: ({ row }) => <Text>{row.original.name}</Text>,
},
];
// AFTER (mantine-datatable)
const columns: DataTableColumn<MyRow>[] = [
{
accessor: "name", // not accessorKey
title: "Name", // not header
width: 200, // not size
sortable: true, // not enableSorting
render: (record) => <Text>{record.name}</Text>, // not Cell
},
];
```
Key differences:
- `accessorKey` → `accessor`
- `header` → `title`
- `size` → `width`
- `Cell: ({ row }) =>` → `render: (record) =>`
- `enableSorting` → `sortable`
- `enableColumnActions` → does not exist (no column action menus)
- `mantineTableBodyCellProps` → `cellsStyle` in `defaultColumnProps`
### Table component
```typescript
// BEFORE
const table = useTranslatedMantineReactTable({ columns, data, ... });
return <MantineReactTable table={table} />;
// AFTER
return (
<DataTable
records={data}
columns={columns}
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
...
/>
);
```
### Sorting is manual
mantine-datatable does NOT sort for you. You must sort the data yourself:
```typescript
const [sortStatus, setSortStatus] = useState<DataTableSortStatus<MyRow>>({
columnAccessor: "name",
direction: "asc",
});
const sortedData = useMemo(() => {
const mult = sortStatus.direction === "desc" ? -1 : 1;
return [...data].toSorted((a, b) => {
const aVal = a[sortStatus.columnAccessor];
const bVal = b[sortStatus.columnAccessor];
if (typeof aVal === "string") return aVal.localeCompare(bVal as string) * mult;
return ((aVal as number) - (bVal as number)) * mult;
});
}, [data, sortStatus]);
// Pass sortedData to records, not data
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
```
---
## 2. Making Tables Transparent (Widget Context)
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
Create a CSS file alongside the component:
```css
/* styles.css */
.my-table,
.my-table .mantine-datatable-table,
.my-table .mantine-datatable-table thead,
.my-table .mantine-datatable-table tbody,
.my-table .mantine-datatable-table tfoot,
.my-table th,
.my-table td {
background-color: transparent !important;
}
.my-table .mantine-datatable-table tr {
background-color: transparent;
}
/* Hover: translucent instead of opaque */
.my-table .mantine-datatable-table tbody tr:hover {
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
}
/* Headers: frosted glass effect */
.my-table th {
white-space: nowrap;
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
backdrop-filter: blur(8px);
}
/* Cell overflow */
.my-table td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Hide sort icon until hover */
.my-table .mantine-datatable-header-cell-sortable-icon {
opacity: 0;
transition: opacity 150ms;
}
.my-table th:hover .mantine-datatable-header-cell-sortable-icon {
opacity: 1;
}
/* Row expansion area */
.my-table .mantine-datatable-row-expansion-cell {
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
}
```
Import it: `import "./styles.css";` and apply via `className="my-table"`.
---
## 3. Responsive Column Visibility
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
```typescript
interface SizeConfig {
fontSize: "xs" | "sm";
iconSize: number;
cellPadding: number;
showSpeedColumns: boolean;
showTimeColumn: boolean;
showStateColumn: boolean;
}
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
{ maxWidth: 300, config: { fontSize: "xs", iconSize: 12, cellPadding: 2, showSpeedColumns: false, showTimeColumn: false, showStateColumn: false } },
{ maxWidth: 500, config: { fontSize: "xs", iconSize: 14, cellPadding: 4, showSpeedColumns: false, showTimeColumn: true, showStateColumn: false } },
];
const DEFAULT_SIZE_CONFIG: SizeConfig = { fontSize: "sm", iconSize: 14, cellPadding: 4, showSpeedColumns: true, showTimeColumn: true, showStateColumn: true };
function getSizeConfig(width: number): SizeConfig {
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
if (width < maxWidth) return config;
}
return DEFAULT_SIZE_CONFIG;
}
// Usage: memoize it
const size = useMemo(() => getSizeConfig(width), [width]);
```
Then use a visibility check lookup table for columns:
```typescript
const columnVisibilityChecks: Partial<Record<string, (ctx: ColumnContext) => boolean>> = {
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
downSpeed: (ctx) => ctx.size.showSpeedColumns,
time: (ctx) => ctx.size.showTimeColumn,
state: (ctx) => ctx.size.showStateColumn,
};
function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
if (!ctx.optionsColumnSet.has(accessor)) return false;
const check = columnVisibilityChecks[accessor];
if (check && !check(ctx)) return false;
return true;
}
```
---
## 4. Column Persistence (Drag/Resize → Server)
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
### Hidden widget options
In `index.ts`, add hidden text options:
```typescript
columnOrder: factory.text({ defaultValue: "" }),
columnWidths: factory.text({ defaultValue: "" }),
```
With visibility override:
```typescript
{
columnOrder: { shouldHide: () => true },
columnWidths: { shouldHide: () => true },
}
```
### Translation keys required
Even hidden options need translation keys or the translation test fails:
```json
"widget.mywidget.option.columnOrder.label": "Column order",
"widget.mywidget.option.columnWidths.label": "Column widths"
```
### Old-import mappings
Add to `packages/old-import/src/widgets/options.ts`:
```typescript
columnOrder: () => undefined,
columnWidths: () => undefined,
```
### Dynamic storeKey
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
```typescript
const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted().join(",")}`;
```
### Hydration from server → localStorage
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
```typescript
const { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } =
useDataTableColumns<MyRow>({ key: storeKey, columns });
const lastStoreKey = useRef(storeKey);
const hydrated = useRef(false);
useEffect(() => {
if (lastStoreKey.current !== storeKey) {
lastStoreKey.current = storeKey;
hydrated.current = false;
}
if (hydrated.current) return;
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
if (Object.keys(savedWidths).length > 0) {
setMultipleColumnWidths(
Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w })),
);
}
requestAnimationFrame(() => { hydrated.current = true; });
}, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]);
```
### Persist localStorage → server
Watch for changes and write back:
```typescript
const prevOrder = useRef(columnsOrder);
const prevWidths = useRef(columnsWidth);
useEffect(() => {
if (!hydrated.current) return;
const orderChanged = JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
const widthsChanged = JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
prevOrder.current = columnsOrder;
prevWidths.current = columnsWidth;
if (!orderChanged && !widthsChanged) return;
if (orderChanged) persistOption({ columnOrder: JSON.stringify(columnsOrder) });
if (widthsChanged) {
const widthMap: Record<string, number> = {};
for (const entry of columnsWidth) {
const key = Object.keys(entry)[0];
if (!key) continue;
const w = entry[key as keyof typeof entry];
if (typeof w === "number") widthMap[key] = w;
else if (typeof w === "string" && w.endsWith("px")) widthMap[key] = parseInt(w, 10);
}
persistOption({ columnWidths: JSON.stringify(widthMap) });
}
}, [columnsOrder, columnsWidth, persistOption]);
```
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
---
## 5. Context Menu (Right-Click)
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
```typescript
interface ContextMenuState {
x: number;
y: number;
item: MyRow;
}
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
const handleContextMenu = useCallback(
({ record, event }: { record: MyRow; event: React.MouseEvent }) => {
event.preventDefault();
setContextMenu({ x: event.clientX, y: event.clientY, item: record });
},
[],
);
// On DataTable:
<DataTable onRowContextMenu={handleContextMenu} />
// Render:
{contextMenu && <RowContextMenu state={contextMenu} onClose={closeContextMenu} />}
```
The menu component uses `Portal` for correct z-index:
```tsx
function RowContextMenu({ state, onClose }: { state: ContextMenuState; onClose: () => void }) {
return (
<Portal>
<Menu opened onClose={onClose} closeOnItemClick={false} position="right-start" offset={0}>
<Menu.Target>
<Box style={{ position: "fixed", left: state.x, top: state.y, width: 0, height: 0 }} />
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={() => { /* action */ onClose(); }}>Action</Menu.Item>
</Menu.Dropdown>
</Menu>
</Portal>
);
}
```
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
---
## 6. Row Expansion
```typescript
<DataTable
rowExpansion={{
trigger: "click",
allowMultiple: true,
collapseProps: {
transitionDuration: 200,
animateOpacity: true,
transitionTimingFunction: "ease-out",
},
content: ({ record, collapse }) => <ExpandedRow item={record} collapse={collapse} />,
}}
/>
```
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
---
## 7. Common Pitfalls & Fixes
### Status indicator dots
Don't use `<Badge variant="dot" />` without children — it renders a pill with empty space. Use a plain `Box`:
```tsx
<Box
w={8}
h={8}
style={{
borderRadius: "50%",
backgroundColor: `var(--mantine-color-${color}-filled)`,
flexShrink: 0,
}}
/>
```
### Tooltip overflow on long text columns
When a column has `ellipsis: true`, the tooltip target element can still extend beyond the cell bounds if inner elements don't clip. Fix:
```tsx
<Tooltip position="bottom-start" ...>
<Group style={{ overflow: "hidden" }}>
...
</Group>
</Tooltip>
```
Use `position="bottom-start"` instead of `position="right"` so the tooltip never escapes the widget horizontally.
### Dark mode tooltips
Mantine tooltips default to the theme's tooltip color which may be white in light mode and stay white in dark mode. Always set `color="dark"` on informational tooltips inside widgets.
### Division by zero
Guard all ratio calculations:
```typescript
if (item.size > 0) ratio = item.sent / item.size;
if (totalDown > 0) ratio = totalUp / totalDown;
```
### Composite idAccessor
When rows can come from multiple integrations, IDs may collide. Use a composite key:
```typescript
idAccessor={(record) => `${record.integration.id}:${record.id}`}
```
### JSON option parsing
Always wrap in try/catch:
```typescript
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
if (!value) return fallback;
try { return JSON.parse(value) as T; }
catch { return fallback; }
}
```
### Clipboard API
`navigator.clipboard.writeText()` can throw in non-HTTPS contexts. Always catch:
```typescript
void navigator.clipboard.writeText(text).catch(() => {});
```
### Sync sortStatus with widget options
If the widget has configurable default sort, sync it:
```typescript
useEffect(() => {
setSortStatus({ columnAccessor: options.defaultSort, direction: defaultSortDirection });
}, [options.defaultSort, defaultSortDirection]);
```
---
## 8. Accessibility
- All `ActionIcon` components need `aria-label`
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
---
## 9. Error Handling
- Every mutation needs an `onError` callback with `showErrorNotification`
- The `saveItemOptions` mutation (column persistence) also needs `onError`
- Check `isError` on the main query and show a fallback UI
- Use a shared `mutationOptions` object to avoid repeating `onSettled`/`onError`:
```typescript
const mutationOptions = {
onSettled: () => void utils.myQuery.invalidate(),
onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }),
};
```
---
## 10. i18n
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
- Column titles: `t("items.{accessor}.columnTitle")`
- Detail labels: `t("items.{accessor}.detailsTitle")`
- States: `t("states.{state}")`
- Actions: `t("actions.{action}")`
- Errors: `t("errors.{errorKey}")`
- Hidden options still need `.label` translation keys
---
## 11. Edit Mode
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
```typescript
let tablePointerEvents: React.CSSProperties["pointerEvents"];
if (isEditMode) tablePointerEvents = "none";
let rowContextMenuHandler = handleContextMenu;
if (isEditMode) rowContextMenuHandler = undefined;
<DataTable style={{ pointerEvents: tablePointerEvents }} />
```
---
## 12. DataTable Props Reference (Commonly Used)
```typescript
<DataTable
records={sortedData}
columns={effectiveColumns}
storeColumnsKey={storeKey}
withTableBorder={false}
borderRadius={0}
highlightOnHover
striped="odd"
stripedColor={{ dark: "dark.7", light: "gray.0" }}
highlightOnHoverColor={{ dark: "dark.5", light: "gray.1" }}
verticalAlign="center"
fetching={isFetching && data.length === 0}
loaderBackgroundBlur={2}
fz={size.fontSize}
textSelectionDisabled
sortStatus={sortStatus}
onSortStatusChange={setSortStatus}
noRecordsText={t("errors.noItems")}
idAccessor={(record) => record.uniqueKey}
height="100%"
className="my-table"
defaultColumnProps={{
noWrap: true,
draggable: true,
resizable: true,
cellsStyle: () => ({ padding: `${size.cellPadding}px 8px` }),
}}
scrollAreaProps={{ type: "auto", scrollbarSize: 6 }}
rowBackgroundColor={(record) => conditionalBgMap[record.state]}
onRowContextMenu={handleContextMenu}
rowExpansion={{ ... }}
onScroll={() => { if (contextMenu) closeContextMenu(); }}
/>
```
---
## 13. Layout Stability
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
- Give non-name columns explicit `width` values so only the name column flexes
- Use `ellipsis: true` on the name column — it handles `text-overflow: ellipsis` internally
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
---
## 14. Checklist for Each Migration
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
2. Convert column definitions (`accessorKey` → `accessor`, `header` → `title`, `Cell` → `render`, etc.)
3. Implement manual sorting with `useState` + `useMemo`
4. Add CSS file for transparent backgrounds (if widget context)
5. Add responsive column visibility (if widget context)
6. Add hidden options + hydration logic (if column persistence needed)
7. Add old-import mappings for new options
8. Add translation keys (including hidden option labels)
9. Add context menu if row actions exist
10. Add row expansion if detail view exists
11. Add error handling on all mutations
12. Add accessibility attributes
13. Test at multiple widget widths
14. Update docs in `apps/docs/docs/widgets/` if applicable
15. Run `pnpm turbo typecheck` — catches old-import mismatches
16. Run translation spec — catches missing keys
+2 -1
View File
@@ -17,7 +17,8 @@ coverage
dev
.build
e2e
apps/docs
apps/docs/*
!apps/docs/package.json
apps/nextjs/.next
.env
.env.*
+22 -1
View File
@@ -61,9 +61,30 @@ TURBO_TELEMETRY_DISABLED=1
# Enable mock integration
UNSAFE_ENABLE_MOCK_INTEGRATION=true
# Public URL contract. Values must be absolute HTTP(S) URLs without credentials,
# query strings, or fragments. Trailing slashes are normalized at startup.
HOMARR_WEBSITE_URL=https://homarr.dev
# PocketBase API used by Homarr and by the standalone documentation site.
# Defaults to HOMARR_WEBSITE_URL.
# WORKSHOP_API_URL=https://homarr.dev
# Public Workshop page opened from Homarr. Defaults to HOMARR_WEBSITE_URL/workshop.
# WORKSHOP_WEB_URL=https://homarr.dev/workshop
# Documentation and PocketBase deployment settings.
# WORKSHOP_PUBLIC_ORIGIN=https://homarr.dev
# PB_ALLOWED_ORIGINS=*
# Local Workshop stack:
# HOMARR_WEBSITE_URL=http://127.0.0.1:3003
# WORKSHOP_API_URL=http://127.0.0.1:8090
# WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop
# WORKSHOP_PUBLIC_ORIGIN=http://127.0.0.1:3003
# Optional: GITHUB_CLIENT_ID=
# Optional: GITHUB_CLIENT_SECRET=
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
# DEMO_MODE=true
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
# Set to false for writable preview deployments so the demo user can modify the dashboard.
# DEMO_READ_ONLY=false
# DEMO_READ_ONLY=false
+4
View File
@@ -33,6 +33,10 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.74.0
- 1.73.0
- 1.72.0
- 1.71.0
- 1.70.0
- 1.69.2
- 1.69.0
+2 -2
View File
@@ -16,6 +16,6 @@
**When contributing a new integration or widget:**
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
tests (e.g. what happens if it is offline)
tests (e.g. what happens if it is offline)
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
provide support and maintain the integration (e.g. in case of breaking or failure)
provide support and maintain the integration (e.g. in case of breaking or failure)
+2 -2
View File
@@ -1,6 +1,6 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: ["config:recommended"],
extends: ["config:recommended", ":dependencyDashboardApproval"],
packageRules: [
{
matchPackageNames: ["^@homarr/"],
@@ -47,7 +47,7 @@
rangeStrategy: "bump",
automerge: false,
baseBranchPatterns: ["dev"],
dependencyDashboard: false,
dependencyDashboard: true,
// prevent special characters in branch names (otherwise git errors occur on windows)
branchNameStrict: true,
}
+1 -1
View File
@@ -32,4 +32,4 @@ jobs:
run: |
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
| xargs -0 -r pnpm exec oxfmt --check
| xargs -0 -r pnpm exec oxfmt --check --
-35
View File
@@ -1,35 +0,0 @@
name: "[Dependency Updates] Auto Approve"
on:
pull_request:
types: [opened, synchronize]
permissions: {}
jobs:
approve-automatic-prs:
runs-on: ubuntu-latest
timeout-minutes: 2
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175486441: homarr-releases[bot]
# Extracted from https://api.github.com/users/<username>
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to approve pull request
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Approve automatic PRs
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
gh pr review ${{github.event.pull_request.number}} --approve --body "Automatically approved by GitHub Action"
@@ -0,0 +1,43 @@
name: "[Bots] Reconcile Auto-merge"
on:
schedule:
- cron: "*/30 * * * *"
workflow_dispatch:
permissions: {}
jobs:
reconcile:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to enable auto-merge
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Re-enable auto-merge for prepared bot PRs
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175441: homarr-releases[bot]
gh pr list --repo "${{ github.repository }}" --base dev --state open --limit 100 \
--json number,author,reviewDecision,mergeStateStatus,autoMergeRequest \
--jq '.[] | select(.author.login as $login | ["app/homarr-renovate", "app/homarr-crowdin", "app/homarr-update-contributors", "app/homarr-releases"] | index($login)) | select(.reviewDecision == "APPROVED") | select(.mergeStateStatus == "CLEAN") | select(.autoMergeRequest == null) | .number' \
| while read -r pr; do
echo "Re-enabling auto-merge on PR #$pr"
gh pr merge "$pr" --auto --squash
done
+47
View File
@@ -0,0 +1,47 @@
name: "[Bots] Approve and Auto-merge"
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
permissions: {}
jobs:
approve-and-automerge:
runs-on: ubuntu-latest
timeout-minutes: 5
# 158783068: homarr-renovate[bot]
# 190541745: homarr-crowdin[bot]
# 210161987: homarr-update-contributors[bot]
# 175441: homarr-releases[bot]
# Extracted from https://api.github.com/users/<username>
if: |
(github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175441) &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.base.ref == 'dev'
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Obtain token
id: obtainToken
uses: actions/create-github-app-token@v3
with:
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
permission-pull-requests: write # required to approve a pull request and enable auto-merge
- name: Install GitHub CLI
run: sudo apt-get install -y gh
- name: Approve and enable auto-merge
env:
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
run: |
PR_NUMBER="${{ github.event.pull_request.number }}"
if [ "$(gh pr view "$PR_NUMBER" --json reviewDecision --jq .reviewDecision)" != "APPROVED" ]; then
gh pr review "$PR_NUMBER" --approve --body "Automatically approved by Homarr bot policy"
fi
if [ "$(gh pr view "$PR_NUMBER" --json autoMergeRequest --jq '.autoMergeRequest != null')" != "true" ]; then
gh pr merge "$PR_NUMBER" --auto --squash
fi
+67 -7
View File
@@ -8,7 +8,7 @@ on:
types: [closed]
branches: ["*"]
push:
branches: [main, dev, beta, next]
branches: [main, dev, beta, next, v2]
merge_group:
workflow_dispatch:
inputs:
@@ -34,8 +34,10 @@ permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
cancel-in-progress: false
# Every event for a PR shares one group so a close event cancels stale image
# publication and a newer synchronize run cannot be overwritten by an older run.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/main' }}
env:
FORCE_COLOR: 3
@@ -82,11 +84,22 @@ jobs:
run: cp .env.example .env
- name: Lint, typecheck, and build affected workspaces
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=4 --log-order=stream
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=2
- name: Validate workspace configuration
run: pnpm lint:ws
- name: Validate Custom Widget architecture
run: pnpm check:custom-widgets
- name: Validate checked-in OpenAPI schema
run: pnpm check:openapi
- name: Validate Custom Widget bundle budget
run: |
pnpm --filter nextjs build
pnpm check:custom-widget-bundle
- name: Run unit tests
run: pnpm test
@@ -186,8 +199,21 @@ jobs:
path: e2e/test-results/
retention-days: 7
- name: Determine preview tag
id: preview
- name: Log in to GHCR for preview publication
if: |
!env.ACT &&
(
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push amd64 preview image by digest
id: preview-build
if: |
!env.ACT &&
(
@@ -216,7 +242,7 @@ jobs:
needs: fast-gate
if: |
(
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
runs-on: ubuntu-24.04-arm
@@ -273,7 +299,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
packages: write
pull-requests: read
env:
PREVIEW_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
steps:
@@ -289,6 +317,26 @@ jobs:
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Verify PR head before publication
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
env:
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
with:
script: |
const { data: pull } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
});
if (pull.state !== "open") {
core.setFailed(`PR #${pull.number} is ${pull.state}; refusing to publish its preview.`);
return;
}
if (pull.head.sha !== process.env.EXPECTED_HEAD_SHA) {
core.setFailed(`PR #${pull.number} moved from ${process.env.EXPECTED_HEAD_SHA} to ${pull.head.sha}.`);
}
- name: Publish preview manifest
if: "!env.ACT"
env:
@@ -299,6 +347,18 @@ jobs:
"${PREVIEW_IMAGE}@${AMD64_DIGEST}" \
"${PREVIEW_IMAGE}@${ARM64_DIGEST}"
- name: Verify preview manifest platforms
if: "!env.ACT"
run: |
image="${PREVIEW_IMAGE}:${PREVIEW_TAG}"
manifest="$(docker buildx imagetools inspect "$image" --raw)"
for architecture in amd64 arm64; do
jq --exit-status --arg architecture "$architecture" \
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
<<<"$manifest" >/dev/null
done
docker buildx imagetools inspect "$image"
cleanup:
name: Clean up preview image
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
+63
View File
@@ -0,0 +1,63 @@
name: Workshop
on:
pull_request:
push:
branches: [dev, v2, main]
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 24.18.0
- uses: pnpm/action-setup@v6
with:
run_install: false
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @homarr/workshop typecheck
- run: pnpm --filter @homarr/docs typecheck
- run: docker compose --env-file apps/workshop/.env.staging.example -f apps/workshop/docker-compose.staging.yml config --quiet
- run: pnpm test:workshop
- run: pnpm test:workshop-image
publish:
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'main')
needs: validate
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
- uses: docker/setup-qemu-action@v4
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/metadata-action@v6
id: metadata
with:
images: ghcr.io/homarr-labs/workshop
tags: |
type=sha,prefix=sha-,format=long
type=raw,value=v2,enable=${{ github.ref_name == 'v2' }}
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
- uses: docker/build-push-action@v7
with:
context: .
file: apps/workshop/Dockerfile
target: production
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
+7 -1
View File
@@ -16,6 +16,7 @@ out/
next-env.d.ts
# artifacts
.ai-evaluations/
packages/db/migrations/*/migrate.cjs
# nitro
@@ -71,10 +72,15 @@ apps/nextjs/public/images/background.png
# personal agent config
.opencode
.agents/
.agents/*
!.agents/skills/
.agents/skills/*
!.agents/skills/homarr-custom-widget/
!.agents/skills/homarr-custom-widget/**
skills-lock.json
# docusaurus
apps/docs/.docusaurus
apps/docs/build
.pnpm-store
benchmark-results
@@ -0,0 +1,116 @@
---
target: Workshop store in the docs
total_score: 20
p0_count: 1
p1_count: 3
timestamp: 2026-07-21T11-32-50Z
slug: apps-docs-src-components-workshop
---
# Workshop Store Critique
## Design Health Score
| # | Heuristic | Score | Key issue |
| --------- | ----------------------------------- | --------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Visibility of system status | 1/4 | Fetch failure can show beside an empty-store message, so the system communicates two incompatible states. |
| 2 | Match between system and real world | 3/4 | Widget, CSS, voting, revisions, and comments use familiar language. |
| 3 | User control and freedom | 2/4 | Filters are reversible, but the crashed route and failed fetch state offer no useful recovery. |
| 4 | Consistency and standards | 2/4 | The submission editor uses CodeMirror, while revision editing falls back to a textarea; several tiny custom controls do not follow the shared button vocabulary. |
| 5 | Error prevention | 2/4 | Submission validation exists, but disabled progression does not explain what is missing and destructive actions use a brief inline confirmation. |
| 6 | Recognition rather than recall | 3/4 | Labels, type choices, and the submission stepper are understandable without documentation. |
| 7 | Flexibility and efficiency | 2/4 | Search covers titles only, signed-out users can select Yours, and installation is reduced to ambiguous Copy and Download actions. |
| 8 | Aesthetic and minimalist design | 3/4 | The restrained visual system is clean, but the browse toolbar and card action rows become dense. |
| 9 | Help users recover from errors | 0/4 | The production route crashes with React error 130; the fetch error has no retry action or useful context. |
| 10 | Help and documentation | 2/4 | The editor explains its fields, but the store does not teach the full inspect, trust, download, and install journey. |
| **Total** | | **20/40** | **Needs work before release** |
## Anti-Patterns Verdict
The surface does not look like obvious AI-generated decoration. Its visual restraint, familiar controls, and practical editor are credible product UI. The weakness is product behavior rather than styling: contradictory states, tiny action controls, an overloaded responsive toolbar, and a primary installation journey that is not explicit.
The deterministic Impeccable scan returned zero findings for `apps/docs/src/components/workshop`. This is useful but incomplete. The detector did not catch runtime failures, semantic state conflicts, touch-target sizing, or responsive grouping. Browser evidence found a full route crash, React error 130, and an earlier state that rendered both `Something went wrong.` and `No submissions yet`.
No visual overlay was injected because the available browser evaluation surface is read-only. Screenshots, DOM snapshots, console logs, and source inspection were used instead.
## Overall Impression
The submission modal is now one of the stronger parts of the Workshop. The store around it is not release-ready. The single biggest opportunity is to make discovery and installation a dependable, explicit path: load reliably, show one truthful state, help users evaluate a submission, and give them one obvious next action.
## What Is Working
1. The visual system is restrained and consistent with the Homarr documentation. Typography, color, radii, and spacing do not compete with the content.
2. The submission flow chunks a complex task into Type, Details, and Media. The shared JSON/CSS editor, drag-and-drop import, metadata autofill, and capped media previews reduce creator effort.
3. The detail design exposes revision, author, reports, changelog, source content, and comments. This supports inspection rather than treating community code as a blind install.
## Priority Issues
### P0: The Workshop route crashes in the production docs build
**Why it matters:** No visual polish matters if the store becomes a Docusaurus error page. A fresh independent tab consistently reached `This page crashed` with React error 130. The console attributed the thrown error to the Kapa widget bundle.
**Fix:** Isolate or disable the Kapa script on Workshop routes, prevent third-party widget failures from reaching the Docusaurus route boundary, and add a route-level Workshop boundary that preserves navigation and retry. Reproduce against the built Docker docs image, not only the development server.
**Suggested command:** `$impeccable harden`
### P1: Failure and empty states contradict each other
**Why it matters:** Showing `Something went wrong.` followed by `No submissions yet` makes users believe the store is both broken and empty. The generic message gives no cause and no recovery.
**Fix:** Make loading, failure, empty, and results mutually exclusive. The failure state should say that listings could not be loaded, preserve the current filters, and provide `Try loading again`. Do not render filters or an empty store as authoritative data after the initial request fails.
**Suggested command:** `$impeccable harden`
### P1: Mobile discovery controls are too dense and do not form a responsive hierarchy
**Why it matters:** The toolbar's inner row contains four type choices, sorting, and outdated visibility without wrapping. Most controls are 24 to 28 pixels tall. At narrow widths this is difficult to scan, easy to clip, and below a comfortable touch target.
**Fix:** Put a full-width search first on mobile, keep All, Widgets, and CSS as the primary segmented choice, move sorting and outdated visibility into a compact secondary row or popover, and hide Yours when signed out or make it a sign-in affordance. Use at least 40 to 44 pixel touch targets. Add `aria-pressed` or proper tab semantics to the type filter.
**Suggested command:** `$impeccable adapt`
### P1: The store does not make the install journey obvious
**Why it matters:** A first-time user sees Copy and Download without knowing which is recommended, what will be copied, how to inspect safety, or where the file goes in Homarr. This weakens the store's main purpose.
**Fix:** Make the detail page the decision surface. Use one primary action named for its result, such as `Download widget JSON` or `Copy CSS`, with a short `Install in Homarr` explanation linked to the correct in-app flow. Cards should prioritize title, preview, compatibility, score, and `View details`; secondary utilities should not compete with evaluation.
**Suggested command:** `$impeccable clarify`
### P2: Card and moderation actions are too small and too numerous
**Why it matters:** Vote, copy, download, comment, delete, and report controls compete in a compact footer. Low-contrast icon buttons are hard to discover and hard to use by touch. CSS columns also create a top-to-bottom visual reading order that can differ from the expected row order.
**Fix:** Use a regular responsive grid, remove hover scaling, keep one or two visible card actions, and move owner/moderation actions to a menu. Keep voting visible but increase its target size. Correct the report copy: reports and reporter identities are moderation data, not public social proof.
**Suggested command:** `$impeccable distill`
## Persona Red Flags
### Jordan, first-time self-hoster
Jordan opens Workshop to install a widget. The route can crash before any listing appears. In the partial failure state, Jordan sees both an error and an empty store. If listings load, Copy and Download do not explain the Homarr destination or recommended path. Jordan is likely to leave before installing anything.
### Alex, experienced Homarr administrator
Alex can understand the JSON and appreciates the source viewer, but title-only search and limited filtering make a larger catalog inefficient. Compatibility and required secret information are not prominent at browse time. Tiny moderation and owner controls slow repeated management tasks.
### Morgan, mobile user
Morgan encounters a toolbar with more controls than fit comfortably in one decision group, 24 to 28 pixel targets, and a signed-out Yours filter that leads to an unhelpful no-results state. Submission cards add another dense row of small actions.
## Minor Observations
- Future submission steps are focusable buttons even when clicking them does nothing. Disable them or render non-interactive progress markers.
- The report textarea relies on placeholder text and needs a persistent accessible label.
- The revision editor should reuse the same JSON/CSS CodeMirror component as creation.
- Search should include title, description, and author at minimum.
- The timed inline `Confirm?` deletion pattern is easy to miss and weak for keyboard and touch users.
- The detail-page changelog uses an uppercase tracked label, which is unnecessary in this otherwise restrained interface.
- Loading comments should use the same skeleton vocabulary as listings rather than centered status text.
## Questions to Consider
1. Is Workshop primarily a catalog for discovery, or a technical exchange for copying source? The primary card and detail actions should commit to one answer.
2. Should users evaluate compatibility and required secrets before opening a listing, or only on the detail page?
3. Does Workshop need every moderation and ownership action visible inline, or should those actions live behind a menu?
@@ -0,0 +1,64 @@
---
target: Workshop store in the docs
total_score: 38
p0_count: 0
p1_count: 0
timestamp: 2026-07-21T12-29-41Z
slug: apps-docs-src-components-workshop
---
# Workshop Store Critique — Post-Polish
## Design Health Score
| # | Heuristic | Score | Evidence |
| --------- | ----------------------------------- | --------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Visibility of system status | 4/4 | Loading, initial failure, stale refresh failure, empty, and results states are mutually exclusive; recovery actions are visible. |
| 2 | Match between system and real world | 4/4 | Actions describe their result: Download widget JSON, Copy CSS, View details, and Install in Homarr. |
| 3 | User control and freedom | 4/4 | Filters can be cleared, errors retried, dialogs cancelled, and destructive submission deletion requires explicit confirmation. |
| 4 | Consistency and standards | 4/4 | Creation and revision editing share CodeMirror, buttons follow the shared system, and cards use a predictable responsive grid. |
| 5 | Error prevention | 3/4 | Validation, disabled progression, pending states, RLS, and confirmations are strong; disabled form progression could explain unmet requirements more explicitly. |
| 6 | Recognition rather than recall | 4/4 | Persistent labels, type descriptions, step status, source previews, and installation instructions minimize recall. |
| 7 | Flexibility and efficiency | 4/4 | Search spans title, description, and author; signed-in filtering, sorting, responsive controls, and direct detail actions support repeat use. |
| 8 | Aesthetic and minimalist design | 3/4 | The catalog is restrained and scannable; long source documents and the complete creator form necessarily remain dense on small screens. |
| 9 | Help users recover from errors | 4/4 | Route, listing, edit, report, copy, OAuth, and detail failures remain local and provide a clear next action. |
| 10 | Help and documentation | 4/4 | The detail page explains the exact Homarr destination and links to updated Workshop documentation. |
| **Total** | | **38/40** | **Release-quality; minor polish remains** |
## Anti-Patterns Verdict
The surface is product-led rather than decorative. The redesign removes the overloaded card footer, hover scaling, tiny touch controls, public moderation details, contradictory states, and ambiguous install actions. The deterministic Impeccable detector reports zero findings.
## What Improved
1. The production route no longer collapses into the Docusaurus crash screen without recovery. A route-local boundary protects navigation, and the fresh-volume production path renders without console errors.
2. Loading, failure, empty, stale-results, and populated states are truthful and mutually exclusive.
3. Search and filters form a clear responsive hierarchy. Mobile targets are 40–44 pixels, signed-out users do not see Yours, and cards no longer overflow at 390 pixels.
4. Cards prioritize evaluation: title, author, type, score, description, comments, and View details. Copy, download, reporting, editing, and deletion moved to the detail page.
5. The detail page now teaches the installation journey and names its primary action precisely.
6. Reports and reporter identities are admin-only through PocketBase rules. Public catalog UI no longer turns reports into social proof.
7. The creator and revision dialogs share the CodeMirror editor, use bounded internal scrolling, expose pending/error states, and avoid step-selection layout shifts.
## Remaining Minor Opportunities
### P2: Explain disabled progression inline
The Type and Details steps are understandable, but a disabled Next button could state the missing requirement for keyboard and first-time users. This is a refinement, not a blocker, because required fields and validation messages are already present.
### P2: Consider a compact source summary on very small screens
The read-only source viewer is intentionally complete and horizontally scrollable. A future collapse/expand summary could reduce page length for nontechnical visitors without hiding inspection entirely.
## Verification Evidence
- Fresh Docker Compose PocketBase volume and production docs image.
- Populated catalog with widget, CSS, and outdated examples.
- Desktop and 390px mobile layouts in light and dark themes.
- Widget detail, installation instructions, CodeMirror source view, and comments.
- Signed-in submission wizard through Type and Details, including the shared editor.
- Zero browser console errors after the final fixes.
- Impeccable deterministic detector: zero findings.
## Compatibility Note
An existing local development volume created from an earlier unreleased version of the reset migration still has a stale Workshop view. It is intentionally not migrated or deleted. A fresh volume created from the current unreleased migration is the supported validation target and works correctly.
@@ -0,0 +1,106 @@
---
target: Homarr Workshop docs UI
total_score: 27
p0_count: 0
p1_count: 2
timestamp: 2026-07-22T15-58-15Z
slug: apps-docs-src-components-workshop
---
## Design Health Score
| # | Heuristic | Score | Key Issue |
| --------- | ------------------------------- | --------: | --------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Visibility of System Status | 3 | Strong loading, retry, stale-data, pending, and copy states; successful report/edit actions still end silently. |
| 2 | Match System / Real World | 3 | Core Workshop language is clear, but `networkScope`, query/action, and schema terminology assumes technical fluency. |
| 3 | User Control and Freedom | 3 | Back, cancel, clear filters, vote toggling, and destructive confirmations are present; destructive actions have no undo. |
| 4 | Consistency and Standards | 3 | Shared primitives are coherent, though Docusaurus chrome and the denser app-store vocabulary still feel slightly stitched together. |
| 5 | Error Prevention | 2 | Validation and confirmations are good, but install safety and credential context appears after download/copy actions on small screens. |
| 6 | Recognition Rather Than Recall | 3 | Author, revision, source, filters, and capabilities are visible; installation still asks users to remember a separate Homarr navigation path. |
| 7 | Flexibility and Efficiency | 2 | Search, filters, sort, drag-and-drop, autofill, and comment shortcuts help; the install handoff remains manual. |
| 8 | Aesthetic and Minimalist Design | 2 | Hierarchy is clear, but repeated panels, badges, uppercase metadata labels, and overlapping install actions add noise. |
| 9 | Error Recognition and Recovery | 3 | Errors are plain-language and usually preserve work; the global action error can be detached from the failed control. |
| 10 | Help and Documentation | 3 | The installation guide and source explanation are useful; security terminology lacks inline definitions. |
| **Total** | | **27/40** | **Acceptable, significant improvements needed** |
## Anti-Patterns Verdict
**LLM assessment:** Moderate AI-assisted feel, not outright slop. Domain-specific copy, extensive state coverage, and transparent source/capability information make the feature credible. The generic card grid, repeated rounded panels, icon tiles, uppercase tracked labels, and decorative changelog stripe still expose a component-recipe grammar. It looks competent but not fully edited into a distinctive Homarr store.
**Deterministic scan:** `detect.mjs --json apps/docs/src/components/workshop` exited 0 with exact output `[]`: zero findings, zero triggered rules, and no false positives. The scan found none of Impeccable's deterministic anti-patterns.
**Visual overlays:** No reliable user-visible overlay is available. The independent detector agent had no browser backend. The parent fallback opened a fresh browser tab and visually inspected the listing, but the browser API did not expose a mutable evaluation method for the required preflight, so injection and `[Human]` visibility were correctly skipped. Browser/source evidence was used instead.
## Overall Impression
The Workshop is already substantially better than a generic gallery: it communicates authorship, source code, API capabilities, secrets, revision history, reports, and discussion. The biggest opportunity is to turn that information into a deliberate trust sequence. Today the interface lets the user act first and understand safety later, especially on mobile.
## What's Working
- **State coverage is unusually strong.** Listing failures, stale data, loading skeletons, empty results, filter recovery, comment errors, pending actions, and destructive confirmations have explicit UI.
- **Pre-install transparency is excellent.** API hosts, methods, configurable options, network scope, and credential requirements are visible instead of hidden behind a download.
- **The new primitive layer is coherent.** Search, filters, menus, switches, dialogs, and code presentation share one restrained interaction vocabulary across light and dark themes.
## Priority Issues
### [P1] Safety context arrives after the install action on mobile
**Why it matters:** Download and copy actions appear in the header, while source capabilities, credential requirements, and installation guidance live in an aside that drops below the long source and discussion sections under the desktop breakpoint. A first-time mobile user can commit before understanding what the widget contacts or what secrets it needs.
**Fix:** Put a compact “Before you install” summary immediately below the header on small screens: API hosts, network scopes, credential count, and action/query counts. Keep the full sticky details panel on desktop.
**Suggested command:** `$impeccable layout`
### [P1] Primary button and own-comment contrast misses AA
**Why it matters:** The requested theme's primary red with white foreground is about 3.4:1, below 4.5:1 for small text. The same pairing affects primary buttons and the author's comment bubbles.
**Fix:** Darken the primary red or use a dark foreground in light mode, then verify default buttons and comment bubbles in both themes.
**Suggested command:** `$impeccable colorize`
### [P2] The listing's server-rendered fallback is blank
**Why it matters:** Before hydration, the listing renders an empty block instead of preserving the page's hierarchy and indicating progress. This creates a visible dead zone on slower self-hosted deployments.
**Fix:** Render the listing heading, toolbar silhouette, and shared skeleton cards as the `BrowserOnly` fallback, matching the detail page's loading treatment.
**Suggested command:** `$impeccable harden`
### [P2] Download, copy, source, and install compete for the same task
**Why it matters:** “Download widget,” “Copy JSON,” a full source viewer, and a separate installation panel all appear as parallel paths. Users must infer the recommended workflow.
**Fix:** Make “Download widget JSON” the single primary header action. Keep copy beside the source viewer and place the next import step directly beside or immediately after download.
**Suggested command:** `$impeccable clarify`
### [P2] Repeated metadata scaffolding creates visual noise
**Why it matters:** Card shells, badges, icon tiles, uppercase tracked labels, and the changelog stripe make the detail page feel assembled from recipes and slow scanning.
**Fix:** Flatten the widget details into a compact definition list, reserve badges for status, and remove the decorative changelog stripe.
**Suggested command:** `$impeccable distill`
## Persona Red Flags
**Jordan, first-timer:** “Network scope,” queries, actions, options, and the manifest schema appear without inline explanation. Download versus copy has no stated recommendation. On mobile, credential and installation context arrives after source and discussion.
**Sam, accessibility-dependent user:** Primary controls and author comment bubbles miss small-text contrast. Copy, optimistic votes, filter result changes, and successful report/edit completion do not expose explicit live-region behavior. Gallery controls and dots are below comfortable touch-target sizes.
**Casey, distracted mobile user:** The most important safety panel falls to the bottom, while primary actions stay at the top. Long source and discussion content separate the decision from its explanation. The submit type selector remains dense at narrow widths.
## Minor Observations
- The internal installation guide uses an external-link icon.
- The final publication action says “Submit” rather than “Publish submission.”
- Dark-theme tokens are declared in two blocks, increasing drift risk.
- Screenshot dots rely on opacity alone and lack `aria-current`.
- Successful report and edit actions deserve a small confirmation instead of silent dismissal.
## Questions to Consider
- If source review is important enough to say “Review exactly what will be installed,” why can users download before seeing the capability and credential summary?
- Is Workshop primarily a code catalog or a safety-conscious app store? The current action hierarchy tries to be both.
- Should the successful end state be a downloaded file, or a confident handoff into Homarr with the next step already visible?
+11 -1
View File
@@ -3,5 +3,15 @@
"printWidth": 120,
"endOfLine": "lf",
"sortPackageJson": true,
"ignorePatterns": ["**/*.cjs", "**/lang/**", "static-data/**", "CHANGELOG.md", "apps/docs/.docusaurus/**", "apps/docs/build/**", "apps/docs/blog/**"]
"ignorePatterns": [
"**/*.cjs",
"**/lang/**",
"static-data/**",
"CHANGELOG.md",
"apps/docs/.docusaurus/**",
"apps/docs/build/**",
"apps/docs/blog/**",
"packages/custom-widgets/src/core/component-catalog.generated.json",
"apps/docs/static/custom-widgets/component-catalog-v1.json"
]
}
+9 -1
View File
@@ -30,7 +30,15 @@
"options": {
"reportUnusedDisableDirectives": "off"
},
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", "**/lang/*.d.json.ts", ".next/**", "apps/docs/.docusaurus/**", "apps/docs/build/**"],
"ignorePatterns": [
"**/*.config.js",
"**/*.config.mjs",
"**/*.cjs",
"**/lang/*.d.json.ts",
".next/**",
"apps/docs/.docusaurus/**",
"apps/docs/build/**"
],
"settings": {
"react": { "version": "19" }
}
+9
View File
@@ -46,6 +46,8 @@ homarr/
├── tooling/
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── tools/
│ └── homarr-dev/ # Go CLI for local and PR Docker images
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
@@ -68,6 +70,9 @@ When modifying user-facing code, you MUST also update the corresponding document
## Monorepo Commands
- `pnpm dev` — Next.js app only
- `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary
- `pnpm db:seed` — seed default database data explicitly
- `pnpm docker:dev:up` — start the Redis development service in the background
- `pnpm dev:docs` — Docusaurus docs site only
- `pnpm turbo build` — build all packages
- `pnpm turbo build --filter=@homarr/docs` — build docs only
@@ -81,3 +86,7 @@ When modifying user-facing code, you MUST also update the corresponding document
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
- Icons: @tabler/icons-react
- Docs app can import from `@homarr/definitions` for shared types
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
- Run `pnpm dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
- Run `pnpm dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
- Run Go checks from `tools/homarr-dev` with `go test ./...` and `go vet ./...`.
-535
View File
@@ -1,535 +0,0 @@
# Custom Widgets Architecture Audit and North Star
> Status: implemented in the current working tree; baseline retained below for traceability
> Evidence snapshot: `c5703e7e5` on PR #6304
> Scope: built-in custom-widget displays, Custom JSX, network execution, authoring, preview, import/export, AI prompts, and MCP tools
## Implementation outcome
The migration described by this audit is implemented. `@homarr/custom-widgets` now owns the domain through explicit `core`, `jsx`, `runtime`, `server`, and `workbench` exports. API, Widgets, and Next.js retain only authorization, persistence, transport, board registration, navigation, and localized adapter concerns.
Key outcomes:
- One typed configuration and display-descriptor model drives schemas, defaults, form hydration, serialization, import/export, preview, examples, diagnostics, generated references, and AI prompts.
- Display-data extraction moved out of API and Widgets into the domain package; the former API-to-Widgets re-export chain no longer exists.
- The interpreter, shared security policy, registry, wrappers, named-request runtime, hardened executor, preview sessions, rate limits, and workbench UI have focused package-owned modules and tests.
- Runtime and workbench network behavior is injected through typed ports. The domain package has no dependency on API, Widgets, Next.js routes, notification infrastructure, or modal infrastructure.
- Public tRPC and MCP procedures are characterized, v2 and v3 import envelopes remain accepted, display-only v1 remains covered, and MCP offers iterative schema, validation, template read/write/patch, create/import/update, preview query, simulation, and journal tools.
- The AI prompt uses a multiline two-block authoring bundle, documents the `SubFetch` render-child contract, and is generated from the canonical registry and tested examples.
- Architecture enforcement currently scans 110 production modules for dependency direction, cycles, file budgets, dynamic loading, and untranslated JSX literals. Generated-reference drift and every Mantine export classification are test-enforced.
- The latest production build measured the largest Custom JSX route payload at 99,852 gzip bytes across two chunks, below the 768 KiB budget.
Focused verification is green: 278 package tests, 35 API/MCP tests, 52 Widgets renderer tests, 7 E2E tests, affected typecheck, affected lint, docs build, production Next.js build, architecture checks, generated drift, and the bundle budget. The root `pnpm test` command is presently blocked on this workstation by unrelated Docker-backed integration suites timing out while the shared Docker daemon is under external load; no Custom Widget test failed in those runs.
## Executive summary
The feature has a strong security direction and a capable authoring experience, but its implementation has outgrown its current ownership model. PR #6304 changes 56 files with 13,206 additions. The primary feature directories contain 10,437 lines, spread across `@homarr/widgets`, `@homarr/api`, `@homarr/definitions`, `@homarr/validation`, and the Next.js management route.
The main problem is not the absolute line count. It is that domain policy, transport, persistence, rendering, form state, compatibility, and documentation generation are interleaved across package boundaries. A change to one display type can require synchronized edits in schemas, form defaults, import conversion, preview extraction, runtime rendering, documentation, and AI instructions. Several of those paths use untyped records, duplicated conversion logic, or direct API dependencies.
The north star is a single `@homarr/custom-widgets` domain package with explicit `core`, `jsx`, `runtime`, `server`, and `workbench` entry points. Existing applications and packages become thin adapters. The extraction must preserve public APIs, import formats, permission behavior, and the current UI while improving internal cohesion.
This audit does not authorize a big-bang rewrite. The migration should be a sequence of behavior-preserving PRs, beginning with characterization tests and ending with enforced dependency and complexity budgets.
## Measured baseline
### Change footprint
| Measure | Current value |
| ------------------------------- | ------------: |
| PR files changed | 56 |
| PR additions | 13,206 |
| PR deletions | 1,385 |
| Primary feature-directory lines | 10,437 |
Generated component-reference JSON accounts for 2,491 added lines and is not treated as source complexity.
### Largest production files
| File | Lines | Responsibilities currently combined |
| ------------------------------------------------------------------------------- | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx` | 1,911 | Schema refinement, defaults, server-error mapping, form conversion, submission, preview state, focus management, display picker, connection fields, and every display-specific editor |
| `packages/api/src/router/custom-widget/custom-widget-router.ts` | 978 | CRUD, import/export, template patching, MCP descriptions, preview sessions, preview execution, journals, and action simulation/execution |
| `packages/widgets/src/custom-api/safe-jsx-interpreter.tsx` | 944 | AST model, parsing, environments, budgets, sanitization, expression evaluation, safe methods, callbacks, and React rendering |
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview.tsx` | 834 | Preview orchestration, error boundary, tabs, response tree, diagnostics, request journal, sample data, and renderer selection |
| `packages/widgets/src/custom-api/jsx-whitelist.ts` | 775 | Mantine imports, wrappers, icon mapping, runtime component map, registry filtering, data sanitization, and safe bindings |
| `packages/widgets/src/custom-api/jsx-sub-fetch.tsx` | 599 | Query rendering, render props, path display, action execution, buttons, toggles, refresh, notifications, and confirmations |
These files exceed the proposed 300-line production-module target. The interpreter is a reasonable candidate for a documented 400-line exception after its independent policies, evaluators, and renderer are extracted.
### Current ownership
```mermaid
flowchart LR
D["@homarr/definitions<br/>registry and examples"]
V["@homarr/validation<br/>schemas and AST diagnostics"]
W["@homarr/widgets<br/>renderers and interpreter"]
A["@homarr/api<br/>CRUD, permissions, execution and MCP"]
N["Next.js route<br/>workbench, preview and prompt"]
X["Docs<br/>generated reference and guides"]
N --> A
N --> V
N --> D
N --> W
W --> A
W --> D
A --> V
X --> D
```
The `@homarr/widgets → @homarr/api` edge is particularly costly. Runtime request components call the generated tRPC client directly, so moving the domain into a package that is also consumed by the API would create a dependency cycle unless request execution is inverted behind a typed port.
## What is working well
- Custom JSX v2 replaces raw runtime URLs and methods with named, typed requests.
- Board permissions, network scope, DNS validation, redirect policy, response limits, caching, concurrency, and action rules are server-enforced.
- The safe interpreter no longer treats an upstream JSX parser as a sandbox.
- Component metadata drives runtime classification, editor completion, docs generation, and part of the AI prompt.
- The workbench supports multiline JSX, diagnostics, real preview sizes, response inspection, request journals, import review, undo/redo, and paste-to-import.
- MCP exposes schema, validation, preview, journal, template read/write/patch, import, and update workflows without exposing stored secrets.
- Shared examples cover simple metrics, nested queries, charts, actions, and richer interactive layouts.
The migration must preserve these capabilities. Package extraction is not a reason to weaken the security boundary or remove authoring features.
## Findings
### P1 — Domain ownership is fragmented
Custom-widget behavior is implemented in five packages/applications. None of them is the clear source of truth for the complete lifecycle. `@homarr/definitions` owns component metadata and examples, `@homarr/validation` owns schemas and template diagnostics, `@homarr/widgets` owns runtime evaluation, `@homarr/api` owns network and persistence flows, and Next.js owns authoring transformations.
Impact:
- Maintainers must search unrelated packages to understand one behavior.
- Changes have a large synchronization surface and can drift silently.
- The domain cannot be tested or reused as a coherent unit.
- Package names describe infrastructure location rather than feature ownership.
### P1 — Giant modules mix unrelated reasons to change
The largest files are not merely long; they combine policy, orchestration, state, and presentation. The form contains all display editors. The router contains every management and preview procedure. The interpreter contains parsing through React emission. The request component file contains both query and mutation interaction models.
Impact:
- Reviews are broad and regression-prone.
- Unit-test seams are difficult to identify.
- Merge conflicts concentrate in a small number of files.
- Small feature changes require understanding thousands of lines of adjacent behavior.
### P1 — Runtime code depends directly on the API client
`packages/widgets/src/custom-api/component.tsx` and `jsx-sub-fetch.tsx` import `@homarr/api/client`. They also directly invoke notifications and confirmation modals.
Impact:
- The renderer cannot be used or tested without application infrastructure.
- A future `@homarr/custom-widgets` package would cycle with `@homarr/api` if both retained these imports.
- Preview and board runtime cannot share the same renderer through different request implementations without branching inside the components.
Required direction: a runtime provider must receive typed query, action, confirmation, notification, and invalidation ports. The board and preview layers supply adapters.
### P1 — Configuration transformations are duplicated
Create defaults and builders live in the form, edit hydration lives in the edit page, and import hydration lives in `_custom-widget-import-utils.ts`. These paths independently translate between stored display configs and form values. They rely on display-type switches, record casts, and JSON text fields such as the request manifest.
Impact:
- Create, edit, paste replacement, import, export, and MCP can disagree.
- Adding a field requires updating multiple transforms.
- Round trips are difficult to prove lossless.
Required direction: each display type supplies one typed descriptor containing schema, defaults, `toForm`, `fromForm`, preview projection, and migration behavior.
### P1 — Type safety degrades at domain boundaries
`Record<string, unknown>`, `as never`, and `as unknown as` are frequent in form conversion, display extraction, preview state, imports, registry wrappers, and API parsing. Some casts are necessary at the interpreter/React boundary, but most domain-level casts indicate missing discriminated unions or validated adapters.
Impact:
- Invalid combinations survive until runtime.
- Refactors do not receive complete compiler guidance.
- Server and client transformations can diverge while continuing to compile.
Required direction: parse unknown values once at ingress and use typed domain objects afterward. Unsafe casts must be isolated to named boundary modules and justified by tests.
### P1 — Security policy is represented more than once
The template validator and interpreter both parse JSX and maintain concepts such as blocked properties, blocked props, AST depth, and operation limits. Runtime component filtering adds another policy layer.
The analyzer and interpreter have different jobs, so they should not be collapsed into one evaluator. They must, however, consume one immutable policy definition and shared AST normalization utilities.
Impact:
- A property can be rejected by one layer and missed by another.
- Security changes require parallel edits and duplicate tests.
- Diagnostics can describe behavior that differs from runtime enforcement.
### P1 — The API router combines transport and application behavior
The router owns tRPC metadata, Zod inputs, database writes, encryption, import/export, template revisions, preview execution, rate-limit acquisition, journal recording, and MCP documentation.
Impact:
- Domain logic is only testable through tRPC context setup.
- MCP and browser endpoints can accidentally implement different workflows.
- Framework-specific `TRPCError` values leak into lower-level executor and manifest code.
Required direction: tRPC procedures authorize, load persistence models, call a domain service, and map domain errors. They do not implement request rendering, template patching, preview execution, or import conversion inline.
### P2 — Metadata generation is only partially centralized
The component registry and examples are shared, but runtime maps, wrappers, safe bindings, display-type metadata, AI prompt prose, form choices, and some docs remain separately assembled. The AI prompt is built inside a client button component.
Impact:
- Agent instructions can drift from validation and runtime behavior.
- Prompt generation requires UI and network context.
- Display types lack the same single-registry guarantee as JSX components.
Required direction: prompt generation is a pure function in `core`, using the schema, display descriptors, component registry, and tested fixtures. The UI only fetches context and copies the result.
### P2 — Compatibility behavior is mixed into primary flows
V1 detection, migration warnings, legacy network diagnostics, v2 imports, v3 exports, and fallback display parsing are embedded beside current authoring/runtime logic.
Impact:
- Happy paths are harder to read.
- Legacy behavior can accidentally influence v2 code.
- Compatibility retirement has no obvious deletion boundary.
Required direction: version detection and migrations live under `core/migrations`. Runtime and workbench receive a normalized current model plus explicit review state.
### P2 — Test topology does not mirror the architecture
Interpreter coverage is substantial, and request-executor/router tests exist, but tests are colocated under their current infrastructure owners instead of a feature package. A focused renderer run passed 52 tests during review, while the selected API suites failed before collecting tests because the root Vitest client environment rejected server-only environment access.
Impact:
- The complete feature cannot be verified with one package command.
- Server test failures can be configuration failures rather than behavioral failures.
- Form conversion and workbench orchestration have limited direct characterization coverage relative to their size.
Required direction: the package owns focused test projects or environment-specific configs for core, DOM runtime/workbench, and Node server tests. API adapter tests remain in `@homarr/api`.
### P2 — A small amount of user-facing copy bypasses translation
Examples found during the snapshot include the raw-response tooltip and a default empty template message. Dynamic translation keys also require repeated `as never` casts, reducing translation-key safety.
Impact:
- Locale completeness gates are unreliable.
- Refactoring copy is harder than changing typed translation keys.
Required direction: all package UI receives a typed message catalog or uses typed scoped translations. No visible fallback literals are allowed in production components.
## North-star package
```text
packages/custom-widgets/
├── src/core/
│ ├── config/ # discriminated display configurations and form models
│ ├── displays/ # typed display descriptors and built-in display metadata
│ ├── import/ # v2/v3 parsing, export, review, and authoring bundles
│ ├── migrations/ # v1 detection, normalization, and review requirements
│ ├── prompts/ # pure AI prompt generation
│ └── examples/ # typed fixtures shared by tests, docs, editor, and prompts
├── src/jsx/
│ ├── ast/ # parser adapter, AST types, traversal, and normalization
│ ├── policy/ # blocked properties, props, URLs, and budgets
│ ├── analyzer/ # authoring diagnostics
│ ├── interpreter/ # expression and collection evaluation
│ ├── registry/ # component descriptors, wrappers, and export classification
│ └── bindings/ # branded safe helpers and sanitized data
├── src/runtime/
│ ├── displays/ # built-in renderers
│ ├── jsx/ # Custom JSX renderer and error containment
│ ├── requests/ # SubFetch, actions, toggles, refresh, and state handling
│ └── provider/ # injected runtime capabilities
├── src/server/
│ ├── network/ # hardened executor and destination policy
│ ├── requests/ # manifest resolution and parameter substitution
│ ├── limits/ # rate and concurrency limits
│ └── preview/ # sessions, simulation, and journals
└── src/workbench/
├── form/ # shared state and connection/display sections
├── editor/ # CodeMirror and registry-driven diagnostics
├── preview/ # widget, response, journal, and diagnostic views
└── import/ # clipboard parsing and review UI
```
Public exports are explicit:
```json
{
"exports": {
"./core": "./src/core/index.ts",
"./jsx": "./src/jsx/index.ts",
"./runtime": "./src/runtime/index.ts",
"./server": "./src/server/index.ts",
"./workbench": "./src/workbench/index.ts"
}
}
```
There is no broad root barrel. Internal folders cannot be imported by consumers.
### Dependency rules
```mermaid
flowchart LR
C["custom-widgets/core"]
J["custom-widgets/jsx"]
R["custom-widgets/runtime"]
S["custom-widgets/server"]
B["custom-widgets/workbench"]
API["@homarr/api adapter"]
W["@homarr/widgets adapter"]
N["Next.js route adapter"]
DOC["Docs generator"]
J --> C
R --> C
R --> J
S --> C
B --> C
B --> R
API --> C
API --> S
W --> C
W --> R
N --> C
N --> B
DOC --> C
DOC --> J
```
Hard rules:
1. `@homarr/custom-widgets` never imports `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
2. `core` has no React, Mantine, tRPC, database, Redis, or browser dependency.
3. `jsx` has no API, persistence, Redis, notification, or modal dependency.
4. `runtime` performs no direct tRPC calls. It consumes `CustomWidgetRuntimePort`.
5. `workbench` performs no direct tRPC calls. It consumes `CustomWidgetWorkbenchPort` callbacks.
6. `server` has no tRPC or database-query dependency. API adapters load authorized records and map errors.
7. Cross-entry imports follow the arrows above; reverse imports fail CI.
### Runtime ports
The exact transport remains owned by adapters. The domain contract should cover behavior rather than tRPC details:
```ts
interface CustomWidgetRuntimePort {
getData(input: { itemId: string; signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
query(input: NamedRequestInvocation & { signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
action(input: NamedRequestInvocation & { confirmed?: boolean }): Promise<CustomWidgetRequestResult>;
confirm(input: ActionConfirmation): Promise<boolean>;
notify(input: RuntimeNotification): void;
invalidate(input: RuntimeInvalidation): Promise<void>;
}
```
Board runtime and preview runtime implement the same port. Preview simulation is an adapter capability, not a conditional tRPC import inside `ActionButton`.
### Display descriptors
Every display type is represented once:
```ts
interface CustomWidgetDisplayDescriptor<TConfig, TForm> {
type: CustomWidgetDisplayType;
configSchema: ZodType<TConfig>;
formSchema: ZodType<TForm>;
createDefault(): TConfig;
toForm(config: TConfig): TForm;
fromForm(form: TForm): TConfig;
migrate(input: unknown): DisplayMigrationResult<TConfig>;
extract(response: unknown, config: TConfig): DisplayModel;
capabilities(config: TConfig): CustomWidgetCapability[];
}
```
UI fields and renderer components may be registered in runtime/workbench companion maps keyed by the same typed display identifier. Domain descriptors remain serializable and framework-independent.
## Developer-experience north star
### Maintainers
- Start at `packages/custom-widgets` for every feature behavior.
- Find one module per concept and one descriptor per display type.
- Add a display type without editing central switches in multiple packages.
- Run one package command for core, DOM, and server tests.
- Receive compile-time failures when a configuration field is not handled by forms, preview, migration, or export.
- Review PRs that move one domain boundary at a time.
### Administrators and template authors
- Preserve the current continuous form and live workbench.
- Keep preview usable before a live response exists through schema-valid defaults and editable sample data.
- Keep diagnostics actionable, localized, clickable, and consistent with runtime policy.
- Keep imports reviewable and secrets excluded.
- Keep query and action capabilities visible without noisy permanent warnings.
### AI agents
- Obtain schema, registry, limits, examples, and prompt rules from the same typed sources.
- Use readable multiline templates through authoring bundles and line-based template tools.
- Validate, preview, inspect journals, patch, and revalidate without rewriting unrelated configuration.
- Receive stable machine-actionable issue codes in addition to localized or human-readable messages.
- Never need to infer runtime behavior from UI-only prompt strings.
## Fragmentation rules
- Production modules target at most 300 lines, excluding generated files, fixtures, and type-only declarations.
- Algorithmic modules can reach 400 lines only with a file-level rationale and focused tests.
- A module has one primary reason to change; moving lines without separating policy from orchestration does not satisfy the target.
- React components do not contain schema definitions or persistence transforms.
- Routers do not contain reusable domain algorithms.
- Domain functions do not throw framework-specific errors.
- Unknown input is validated once. Parsed values are not carried as `Record<string, unknown>` through internal layers.
- Compatibility code lives in versioned migration modules and has explicit removal criteria.
- Generated artifacts are reproducible and checked for drift, but excluded from line budgets.
## Migration roadmap
### PR 1 — Characterize contracts
- Add round-trip tests for every display descriptor: default → form → config, stored config → form → config, and import → export.
- Add fixtures for v1 display-only, v1 network-review-required, v2 import, and v3 export.
- Add prompt snapshots sourced from the component registry and examples.
- Split Node and DOM Vitest projects so server suites collect and run reliably.
- Record bundle and render baselines before moving modules.
Gate: all existing behavior is represented by tests, with no production moves yet.
### PR 2 — Establish `core`
- Create `@homarr/custom-widgets` and explicit exports.
- Move custom-widget schemas, typed configs, display descriptors, examples, import/export, migration, capability analysis, and prompt generation into `core`.
- Replace form/edit/import conversion duplication with descriptor calls.
- Temporarily re-export legacy imports from definitions and validation to keep consumers stable.
Gate: create, edit, import, export, MCP validation, and AI prompt generation use the same domain contracts.
### PR 3 — Extract JSX policy and interpreter
- Move AST types, parser adapter, shared security policy, analyzer, evaluator, budgets, bindings, and registry into `jsx`.
- Separate evaluator modules by expression family and React emission.
- Generate diagnostics and runtime enforcement from the same policy constants.
- Keep the Mantine export-classification CI check.
Gate: the security suite passes unchanged, analyzer/runtime policy drift tests pass, and no consumer imports JSX internals.
### PR 4 — Extract runtime
- Move built-in renderers, Custom JSX display, SubFetch, actions, toggles, refresh, and error containment into `runtime`.
- Introduce the runtime port and adapters for board and preview contexts.
- Remove direct imports of the API client, modals, and notifications from domain components.
- Split request components into query, action, toggle, refresh, feedback, and shared-state modules.
Gate: board rendering and workbench preview use the same runtime components through different adapters; actions retain confirmation, rollback, invalidation, and permission behavior.
### PR 5 — Extract server services
- Move executor, manifests, network policy, limits, caching/single-flight, and preview-session logic into `server`.
- Replace `TRPCError` below the adapter with typed domain errors.
- Split API procedures into management, templates, preview, and runtime routers that delegate to package services.
- Keep authorization, database lookup, encryption, and tRPC/MCP metadata in `@homarr/api`.
Gate: browser and MCP flows call the same services; request security tests run as Node package tests; public procedure names and inputs remain compatible.
### PR 6 — Extract and fragment workbench
- Move reusable editor, preview, response tree, import review, connection section, display picker, and display-specific fields into `workbench`.
- Introduce a workbench port for validate, preview, query, simulate action, journal, and save callbacks.
- Keep Next.js pages responsible only for routing, loading initial data, and constructing adapters.
- Replace the monolithic form with small sections and descriptor-driven display fields while preserving the continuous form, two-pane scrolling, autofocus, and mobile modes.
Gate: no workbench source file exceeds the agreed budget, and create/edit/paste replacement share one state model.
### PR 7 — Remove scaffolding and enforce boundaries
- Remove compatibility re-exports and old implementations after all consumers move.
- Add CI checks for forbidden imports, dependency cycles, source-size budgets, untranslated literals, generated-artifact drift, component classification, and custom-JSX chunk size.
- Update contributor and architecture documentation with package entry points and extension recipes.
Gate: old ownership paths contain only thin adapters or are deleted.
## Verification strategy
### Core
- Every display descriptor accepts valid data and rejects mismatched configuration.
- All create/edit/import/export transformations are lossless where expected.
- V1/v2/v3 compatibility and review states are deterministic.
- Prompt output, authoring bundles, fixtures, docs data, and completion data are generated from shared sources.
### JSX
- Computed and Unicode-obfuscated reflective access remains blocked.
- Analyzer and interpreter share property, prop, URL, and budget policy.
- Own-property resolution, branded calls, callback accounting, and node/operation limits remain enforced.
- Every upstream Mantine export is classified and every enabled runtime component resolves.
### Runtime and workbench
- Query cancellation, stale results, retries, render children, action locks, DELETE confirmation, toggle rollback, and invalidation behave identically in preview and boards.
- Error boundaries isolate template and display failures.
- Empty, sample, stale, loading, failure, and success states are localized and accessible.
- Keyboard, focus, responsive, reduced-motion, and narrow-widget behavior remain covered.
### Server and adapters
- Board IDOR, permission levels, anonymous actions, named-request kinds, substitutions, DNS classes, redirects, headers, response limits, caching, and rate limits remain covered.
- Server tests run under an explicit Node environment rather than failing during client-environment initialization.
- MCP schemas and descriptions remain discoverable, stable, and absent from unsafe runtime procedures.
### Required commands
At the end of the migration, the package and repository must pass:
```sh
pnpm --filter @homarr/custom-widgets test
pnpm test
pnpm test:e2e
pnpm turbo typecheck --affected
pnpm turbo lint --affected
pnpm turbo build --filter=@homarr/docs
pnpm check:custom-widget-bundle # after the Next.js production build
```
CI additionally checks dependency direction, cycles, file budgets, generated references, translations, and client bundle size.
## Completion criteria
- `@homarr/custom-widgets` owns every reusable custom-widget domain behavior.
- No circular dependencies exist.
- The package does not import `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
- Runtime and workbench networking uses injected typed ports.
- One typed descriptor is the source of truth for each display type.
- One shared JSX policy drives authoring diagnostics and runtime enforcement.
- Create, edit, import, export, preview, MCP, and board runtime use the same typed transformations.
- Public tRPC/MCP names, import formats, stored configuration, and permission behavior remain compatible.
- V1 display-only migration and network-review states remain tested.
- Production modules satisfy the 300/400-line budgets.
- No untranslated user-facing strings remain.
- Core, JSX, runtime, server, workbench, integration, E2E, accessibility, docs, and bundle checks pass.
- A maintainer can locate a custom-widget behavior from the package structure without searching unrelated packages.
## Explicit non-goals
- Replacing Mantine or Homarr's visual language.
- Allowing arbitrary cross-origin requests.
- Replacing named requests with general JavaScript execution.
- Changing stored secrets or exposing them through export or MCP.
- Redesigning the current management UX during the package extraction.
- Removing compatibility formats before migration and review flows are proven.
## Decision record
- The package owns all Custom Widget behavior, not only Custom JSX.
- Security and correctness are hard constraints.
- Maintainer simplicity is the primary tie-breaker after those constraints.
- Current user and agent workflows are preserved during extraction.
- Generated artifacts do not count toward source-size budgets.
- Migration occurs through reviewable, behavior-preserving PRs rather than a single rewrite.
+6
View File
@@ -1,3 +1,9 @@
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
## [1.73.0](https://github.com/homarr-labs/homarr/compare/v1.72.0...v1.73.0) (2026-07-31)
## [1.72.0](https://github.com/homarr-labs/homarr/compare/v1.71.0...v1.72.0) (2026-07-24)
## [1.71.0](https://github.com/homarr-labs/homarr/compare/v1.70.0...v1.71.0) (2026-07-17)
## [1.70.0](https://github.com/homarr-labs/homarr/compare/v1.69.2...v1.70.0) (2026-07-10)
+8
View File
@@ -206,6 +206,14 @@ packages/integrations/src/unraid @manuel-rw
apps/docs/docs/integrations/uptime-kuma @ajnart
packages/integrations/src/uptime-kuma @ajnart
# What's Up Docker (WUD) - introduced in https://github.com/homarr-labs/homarr/pull/6523 by @tiimonn
apps/docs/docs/integrations/whats-up-docker @tiimonn
apps/docs/docs/widgets/whats-up-docker @tiimonn
packages/api/src/router/widgets/wud.ts @tiimonn
packages/integrations/src/wud @tiimonn
packages/request-handler/src/wud.ts @tiimonn
packages/widgets/src/wud @tiimonn
# Traefik - by @Nerdy-Technician
apps/docs/docs/integrations/traefik @Nerdy-Technician
apps/docs/docs/widgets/traefik @Nerdy-Technician
+20 -9
View File
@@ -3,12 +3,18 @@
FROM node:24.18.0-alpine AS base
FROM base AS builder
ARG TARGETPLATFORM
WORKDIR /app
RUN apk add --no-cache libc6-compat curl bash && apk update
# Native dependencies normally use prebuilds, but the build must remain
# reproducible when a prebuild download times out and compilation is required.
RUN apk add --no-cache libc6-compat curl bash python3 make g++
RUN corepack enable pnpm
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY patches ./patches
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
# @homarr/definitions generates documentation types during install.
COPY --parents ./packages/definitions/src ./
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
@@ -16,17 +22,19 @@ COPY patches ./patches
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
# which IS created by pnpm fetch. The original hoisted linker is restored
# before the install step so the final node_modules layout stays flat.
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
pnpm config set store-dir /pnpm/store && \
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
pnpm fetch && \
pnpm fetch --ignore-scripts && \
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
# Install only from the fetched, committed lockfile so local and Docker builds
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
COPY . .
# Follow the pnpm fetch pattern from https://pnpm.io/cli/fetch
# --frozen-lockfile is omitted as recommended by the pnpm fetch docs
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
pnpm install --recursive
ARG SKIP_ENV_VALIDATION='true'
ARG CI='true'
@@ -37,7 +45,9 @@ RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" pnpm build
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
FROM base AS runner
WORKDIR /app
@@ -85,5 +95,6 @@ ENV AUTH_PROVIDERS='credentials'
ENV REDIS_IS_EXTERNAL='false'
ENV NODE_ENV='production'
EXPOSE 7575
ENTRYPOINT [ "/app/entrypoint.sh" ]
CMD ["sh", "run.sh"]
+475
View File
@@ -0,0 +1,475 @@
# Mantine UI - LLM Documentation
This index lists Mantine documentation pages formatted for LLMs.
Each link points to a standalone Markdown file under the /llms path.
For a single consolidated file with all content, use:
- https://mantine.dev/llms-full.txt
## FAQ
- [Are Mantine components accessible?](https://mantine.dev/llms/q-are-mantine-components-accessible.md): Learn about Mantine components accessibility features
- [Browser zooms in when input is focused. What should I do?](https://mantine.dev/llms/q-browser-zooms-on-focus.md): Use meta tag to disable browser scaling or increase input size
- [Can I have color schemes other than light and dark?](https://mantine.dev/llms/q-light-dark-is-not-enough.md): Learn about the difference between color scheme and theme
- [Can I have different primary color for light and dark color schemes?](https://mantine.dev/llms/q-primary-virtual-color.md): Learn how to use virtual color with primary color in theme object
- [Can I remove MultiSelect placeholder when the component has selected values?](https://mantine.dev/llms/q-multiselect-value-placeholder.md): Learn why MultiSelect placeholder is not removed when values are selected and how to remove it with CSS
- [Can I use an array of strings as a list in use-form?](https://mantine.dev/llms/q-list-of-strings-in-use-form.md): Learn about use-form lists limitations
- [Can I use Mantine components as server components?](https://mantine.dev/llms/q-server-components.md): Learn about use client directive and server components usage
- [Can I use Mantine with Astro?](https://mantine.dev/llms/q-can-i-use-mantine-with-astro.md): No, Astro does not support React context
- [Can I use Mantine with Create React App (CRA)?](https://mantine.dev/llms/q-can-i-use-mantine-with-cra.md): Learn how to use Mantine without postcss-preset-mantine or how to eject CRA and customize webpack.config.js and use postcss-preset-mantine
- [Can I use Mantine with Emotion/styled-components/tailwindcss?](https://mantine.dev/llms/q-third-party-styles.md): Learn about limitations of third-party styles
- [Can I use Mantine with Vue/Svelte/Angular/etc.?](https://mantine.dev/llms/q-vue-svelte-angular.md): No, Mantine is a React library and does not support other frameworks/libraries
- [Can I use nested inline styles with Mantine components?](https://mantine.dev/llms/q-nested-inline-styles.md): Nested styles are supported only in CSS files
- [Can I use PostCSS function in inline styles?](https://mantine.dev/llms/q-postcss-fns-inline.md): Learn where PostCSS functions can be used in Mantine
- [Can I use private CSS variables to style components?](https://mantine.dev/llms/q-private-css-variables.md): No, it is not safe and will not work with future versions of Mantine.
- [Can I use SegmentedControl with empty value?](https://mantine.dev/llms/q-segmented-control-no-value.md): SegmentedControl cannot be used without a value
- [How can I add dynamic CSS styles?](https://mantine.dev/llms/q-dynamic-css-styles.md): Use data attributes, CSS variables or inline styles
- [How can I add fuzzy search to Select component?](https://mantine.dev/llms/q-select-fuzzy.md): Learn how to integrate third-party fuzzy search libraries with Mantine Select component
- [How can I add hover styles to an element?](https://mantine.dev/llms/q-how-to-add-hover-styles.md): Learn how to add hover classes to an element with CSS modules, &:hover or @mixin hover
- [How can I apply styles to all Mantine components?](https://mantine.dev/llms/q-apply-styles-to-all.md): Learn how to use attribute selector to apply styles to all Mantine components
- [How can I change body background color?](https://mantine.dev/llms/q-body-background.md): Use CSS to change body background color
- [How can I change component color prop value depending on the color scheme?](https://mantine.dev/llms/q-color-scheme-color.md): Learn how to use CSS variables resolver to change color value depending on the color scheme
- [How can I change input focus styles?](https://mantine.dev/llms/q-input-focus-styles.md): Learn how to use Styles API with Mantine inputs
- [How can I change Tabs border color?](https://mantine.dev/llms/q-tabs-border-color.md): Learn how to use Styles API with Tabs component
- [How can I contribute to the library?](https://mantine.dev/llms/q-how-can-i-contribute.md): There are multiple ways to contribute even without writing code
- [How can I disable all inputs/input groups inside a form?](https://mantine.dev/llms/q-disable-all-inputs-in-form.md): Learn how to disable all inputs/input groups inside a form with the unstyled Fieldset component
- [How can I display different elements in light and dark color schemes?](https://mantine.dev/llms/q-light-dark-elements.md): Learn how to hide/show elements based on color scheme
- [How can I focus the first input with error with use-form?](https://mantine.dev/llms/q-focus-first-input-with-error.md): Learn how to handle focus with use-form hook
- [How can I get current color scheme value in JavaScript?](https://mantine.dev/llms/q-how-to-get-color-scheme-value-in-js.md): How to use useMantineColorScheme and useComputedColorScheme hooks to get current color scheme value in JavaScript
- [How can I lint CSS files?](https://mantine.dev/llms/q-how-to-setup-stylelint.md): Learn how to set up Stylelint for Mantine postcss syntax
- [How can I load fonts in Next.js?](https://mantine.dev/llms/q-next-load-fonts.md): A guide to load custom fonts in Next.js with CSS and next/font package
- [How can I load fonts in React Router?](https://mantine.dev/llms/q-react-router-load-fonts.md): A guide to load custom fonts in React Router
- [How can I load fonts in Vite?](https://mantine.dev/llms/q-vite-load-fonts.md): A guide to load custom fonts in Vite
- [How can I lock scroll in my application?](https://mantine.dev/llms/q-how-to-lock-scroll.md): Use react-remove-scroll library to lock scroll in your application
- [How can I submit a template to Mantine documentation?](https://mantine.dev/llms/q-submit-template.md): Learn how to create and submit a template to Mantine documentation
- [How can I test Modal/Drawer/Popover components?](https://mantine.dev/llms/q-portals-testing.md): Learn how to use react-testing-library to test components that use portals and transitions.
- [How can I test Select/MultiSelect components?](https://mantine.dev/llms/q-combobox-testing.md): Learn how to use react-testing-library to test Select and MultiSelect components.
- [How can I upload files from Dropzone component?](https://mantine.dev/llms/q-dropzone-upload.md): Learn how to process files dropped into Dropzone component
- [How is that thing done on the mantine.dev website?](https://mantine.dev/llms/q-how-that-thing-is-done.md): Learn how various elements are implemented on the mantine.dev website
- [How to align input with a button in a flex container?](https://mantine.dev/llms/q-align-input-button.md): Learn how to align Mantine inputs with buttons using Group component
- [How to call a function when Modal/Drawer closes and animation completes?](https://mantine.dev/llms/q-how-to-call-function-when-modal-closes.md): How to use transitionProps in Modal/Drawer components
- [How to change background color for FiveM?](https://mantine.dev/llms/q-fivem-background-color.md): Unset body background color and color-scheme styles for FiveM
- [How to change input placeholder color?](https://mantine.dev/llms/q-inputs-placeholder-color.md): Learn how to change placeholder color with Styles API
- [How to integrate custom input with use-form hook?](https://mantine.dev/llms/q-custom-input-use-form.md): Learn how to add use-form support for custom inputs
- [How to prevent Modal from closing?](https://mantine.dev/llms/q-how-to-prevent-modal-from-closing.md): Learn how to prevent Modal from closing when the user clicks outside of it or presses the Escape key
- [How to scroll to the top of the form if the form is submitted with errors?](https://mantine.dev/llms/q-scroll-to-the-top-of-the-form.md): Learn how to call a function when the form is submitted with errors
- [How to update Mantine dependencies?](https://mantine.dev/llms/q-how-to-update-dependencies.md): Learn how to update @mantine/_, @mantinex/_ and postcss-preset-mantine dependencies manually or with ncu script
- [How to use Dropzone with @mantine/form?](https://mantine.dev/llms/q-how-to-use-dropzone-with-form.md): Learn how to use Dropzone with @mantine/form to handle file selection state
- [How to use Mantine template on GitHub?](https://mantine.dev/llms/q-templates-usage.md): Learn how to create a new repository based on Mantine template
- [I get hydration warning about data-mantine-color-scheme attribute, what does it mean?](https://mantine.dev/llms/q-color-scheme-hydration-warning.md): Learn how hydration works and how to fix warnings
- [Is there a comparison with other libraries?](https://mantine.dev/llms/q-other-libs.md): Comparison with other libraries is not provided, you are trusted to make your own decision based on documentation examples
- [Is there a floating action button component?](https://mantine.dev/llms/q-floating-action-button.md): No, but you can build it with Affix and ActionIcon components
- [Is there DataGrid component that I can use with Mantine?](https://mantine.dev/llms/q-data-grid-i-need.md): A list of community packages that provide DataGrid component that can be used with Mantine.
- [It is not possible to pinch to zoom when a Modal is opened. What should I do?](https://mantine.dev/llms/q-pinch-to-zoom-modal.md): Use removeScrollProps to configure react-remove-scroll options
- [MantineProvider was not found in component tree. What should I do?](https://mantine.dev/llms/q-mantine-provider-missing.md): Learn how to resolve MantineProvider issues
- [My buttons are transparent and the background is visible only on hover, what is wrong?](https://mantine.dev/llms/q-transparent-buttons.md): You have installed a third-party library that overrides Mantine styles
- [My Popover dropdown closes when I click on the dropdown of a nested Popover](https://mantine.dev/llms/q-nested-popover-closes.md): Popover dropdown is closed when it detects click-outside events
- [My styles are broken with disabled JavaScript. What should I do?](https://mantine.dev/llms/q-disabled-js.md): Learn how to use Mantine without JavaScript enabled
- [My styles are overridden by Mantine components styles, what should I do?](https://mantine.dev/llms/q-styles-order.md): Learn how to use CSS layers to control styles order and prevent Mantine components from overriding your styles
- [Native browser validation does not work in some components, what should I do?](https://mantine.dev/llms/q-native-required.md): Learn why native browser validation does not work in some components
- [The use-local-storage hook returns the real value only after mounting. Is it a bug?](https://mantine.dev/llms/q-local-storage-effect.md): Learn how Mantine retrieves local storage value
- [What is the difference between searchable Select and Autocomplete?](https://mantine.dev/llms/q-select-autocomplete-difference.md): Searchable Select and Autocomplete are similar components, but they serve different purposes.
- [Where can I find the roadmap?](https://mantine.dev/llms/q-roadmap.md): The roadmap is private and only available to the team.
- [Why are my Carousel slides in vertical orientation?](https://mantine.dev/llms/q-carousel-missing-styles.md): You forgot to import carousel styles
- [Why are my notifications displayed at the wrong position?](https://mantine.dev/llms/q-notifications-missing-styles.md): Because you did not import styles
- [Why can I not use one polymorphic component in the component prop of another polymorphic component?](https://mantine.dev/llms/q-polymorphic-in-polymorphic.md): Learn how polymorphic components types work
- [Why can I not use value/label data structure with Autocomplete/TagsInput?](https://mantine.dev/llms/q-autocomplete-value-label.md): Learn the difference between Autocomplete and Select components
- [Why can VSCode not autoimport the Text component?](https://mantine.dev/llms/q-why-vscode-cannot-autoimport-text.md): It is confused by the native Text constructor
- [Why do I see color scheme flickering on page load?](https://mantine.dev/llms/q-color-scheme-flickering.md): Color scheme flickering is caused by incorrect usage of ColorSchemeScript
- [Why do my dates components look broken?](https://mantine.dev/llms/q-dates-missing-styles.md): Because you did not import styles
- [Why does FileButton not work in Menu?](https://mantine.dev/llms/q-file-button-in-menu.md): Learn how to use FileButton in Menu component
- [Why is it required to have 10 shades per color?](https://mantine.dev/llms/q-ten-shades-per-color.md): Learn how Mantine uses theme colors
- [Why is my screen completely empty after I](https://mantine.dev/llms/q-notifications-empty-screen.md): You have used Notifications component incorrectly
## Core
- [Accordion](https://mantine.dev/llms/core-accordion.md): Divide content into collapsible sections
- [ActionIcon](https://mantine.dev/llms/core-action-icon.md): Icon button
- [Affix](https://mantine.dev/llms/core-affix.md): Renders children inside portal at fixed position
- [Alert](https://mantine.dev/llms/core-alert.md): Attract user attention with important static message
- [AlphaSlider](https://mantine.dev/llms/core-alpha-slider.md): Slider component for selecting alpha channel in color pickers (0 – 1)
- [Anchor](https://mantine.dev/llms/core-anchor.md): Display link with theme styles
- [AngleSlider](https://mantine.dev/llms/core-angle-slider.md): Pick angle value between 0 and 360
- [AppShell](https://mantine.dev/llms/core-app-shell.md): Responsive shell for your application with header, navbar, aside and footer
- [AspectRatio](https://mantine.dev/llms/core-aspect-ratio.md): Maintain responsive consistent width/height ratio
- [Autocomplete](https://mantine.dev/llms/core-autocomplete.md): Autocomplete user input with any list of options
- [Avatar](https://mantine.dev/llms/core-avatar.md): Display user profile image, initials or fallback icon
- [BackgroundImage](https://mantine.dev/llms/core-background-image.md): Displays image as background
- [Badge](https://mantine.dev/llms/core-badge.md): Display badge, pill or tag
- [Blockquote](https://mantine.dev/llms/core-blockquote.md): Blockquote with optional cite
- [Box](https://mantine.dev/llms/core-box.md): Base component for all Mantine components
- [Breadcrumbs](https://mantine.dev/llms/core-breadcrumbs.md): Separates list of react nodes with given separator
- [Burger](https://mantine.dev/llms/core-burger.md): Open/close navigation button
- [Button](https://mantine.dev/llms/core-button.md): Button component to render button or link
- [Card](https://mantine.dev/llms/core-card.md): Card with sections
- [Cascader](https://mantine.dev/llms/core-cascader.md): Select a path from hierarchical data with cascading columns
- [Center](https://mantine.dev/llms/core-center.md): Centers content vertically and horizontally
- [Checkbox](https://mantine.dev/llms/core-checkbox.md): Capture boolean input from user
- [Chip](https://mantine.dev/llms/core-chip.md): Pick one or multiple values with inline controls
- [CloseButton](https://mantine.dev/llms/core-close-button.md): Button with close icon
- [Code](https://mantine.dev/llms/core-code.md): Inline and block code
- [Collapse](https://mantine.dev/llms/core-collapse.md): Animate presence with slide down/up transition
- [ColorInput](https://mantine.dev/llms/core-color-input.md): Capture color from user
- [ColorPicker](https://mantine.dev/llms/core-color-picker.md): Pick colors in hex(a), rgb(a), hsl(a) and hsv(a) formats
- [ColorSwatch](https://mantine.dev/llms/core-color-swatch.md): Displays color
- [Combobox](https://mantine.dev/llms/core-combobox.md): Create custom select, autocomplete or multiselect inputs
- [ComboboxPopover](https://mantine.dev/llms/core-combobox-popover.md): Add combobox dropdown to any button element
- [Container](https://mantine.dev/llms/core-container.md): Center content with padding and max-width
- [CopyButton](https://mantine.dev/llms/core-copy-button.md): Copies given text to clipboard
- [CorePackage](https://mantine.dev/llms/core-package.md)
- [DataList](https://mantine.dev/llms/core-data-list.md): Display label-value pairs as a description list
- [Dialog](https://mantine.dev/llms/core-dialog.md): Display a fixed overlay dialog at any side of the screen
- [Divider](https://mantine.dev/llms/core-divider.md): Horizontal line with optional label or vertical divider
- [Drawer](https://mantine.dev/llms/core-drawer.md): Display overlay area at any side of the screen
- [EmptyState](https://mantine.dev/llms/core-empty-state.md): Display a placeholder for no data, empty results and first-run states
- [Fieldset](https://mantine.dev/llms/core-fieldset.md): Group related elements in a form
- [FileButton](https://mantine.dev/llms/core-file-button.md): Open file picker with a button click
- [FileInput](https://mantine.dev/llms/core-file-input.md): Capture files from user
- [Flex](https://mantine.dev/llms/core-flex.md): Compose elements in a flex container
- [FloatingIndicator](https://mantine.dev/llms/core-floating-indicator.md): Display a floating indicator over a group of elements
- [FloatingWindow](https://mantine.dev/llms/core-floating-window.md): Draggable floating area
- [FocusTrap](https://mantine.dev/llms/core-focus-trap.md): Trap focus at child node
- [Grid](https://mantine.dev/llms/core-grid.md): Responsive 12 columns grid system
- [Group](https://mantine.dev/llms/core-group.md): Compose elements and components in a horizontal flex container
- [Highlight](https://mantine.dev/llms/core-highlight.md): Highlight given part of a string with mark
- [HoverCard](https://mantine.dev/llms/core-hover-card.md): Display popover section when target element is hovered
- [HueSlider](https://mantine.dev/llms/core-hue-slider.md): Slider component for selecting hue channel in color pickers (0 – 360 degrees)
- [Image](https://mantine.dev/llms/core-image.md): Image with optional fallback
- [Indicator](https://mantine.dev/llms/core-indicator.md): Display element at the corner of another element
- [Input](https://mantine.dev/llms/core-input.md): Base component to create custom inputs
- [JsonInput](https://mantine.dev/llms/core-json-input.md): Capture json data from user
- [Kbd](https://mantine.dev/llms/core-kbd.md): Display keyboard key
- [List](https://mantine.dev/llms/core-list.md): Display ordered or unordered list
- [Loader](https://mantine.dev/llms/core-loader.md): Indicate loading state
- [LoadingOverlay](https://mantine.dev/llms/core-loading-overlay.md): An overlay with centered loader
- [Mark](https://mantine.dev/llms/core-mark.md): Highlight part of the text
- [Marquee](https://mantine.dev/llms/core-marquee.md): Create continuous scrolling animation for content
- [MaskInput](https://mantine.dev/llms/core-mask-input.md): Input with mask pattern for formatted text entry
- [Menu](https://mantine.dev/llms/core-menu.md): Combine a list of secondary actions into single interactive area
- [Menubar](https://mantine.dev/llms/core-menubar.md): Desktop application style menubar with a row of menu triggers
- [Modal](https://mantine.dev/llms/core-modal.md): An accessible overlay dialog
- [MultiSelect](https://mantine.dev/llms/core-multi-select.md): Custom searchable multi select
- [NativeSelect](https://mantine.dev/llms/core-native-select.md): Native select element based on Input
- [NavLink](https://mantine.dev/llms/core-nav-link.md): Navigation link
- [Notification](https://mantine.dev/llms/core-notification.md): Show dynamic notifications and alerts to user, part of notifications system
- [NumberFormatter](https://mantine.dev/llms/core-number-formatter.md): Format number with thousands/decimal separators and suffix/prefix
- [NumberInput](https://mantine.dev/llms/core-number-input.md): Capture number from user
- [OverflowList](https://mantine.dev/llms/core-overflow-list.md): Hide items that do not fit in a container and display them as a single collapsed item
- [Overlay](https://mantine.dev/llms/core-overlay.md): Overlays parent element with div element with any color and opacity
- [Pagination](https://mantine.dev/llms/core-pagination.md): Display active page and navigate between multiple pages
- [Paper](https://mantine.dev/llms/core-paper.md): Renders white or dark background depending on color scheme
- [PasswordInput](https://mantine.dev/llms/core-password-input.md): Capture password data from user
- [Pill](https://mantine.dev/llms/core-pill.md): Removable and non-removable tags
- [PillsInput](https://mantine.dev/llms/core-pills-input.md): Base component for custom tags inputs and multi selects
- [PinInput](https://mantine.dev/llms/core-pin-input.md): Capture pin code or one time password from the user
- [Popover](https://mantine.dev/llms/core-popover.md): Display popover section relative to given target element
- [Portal](https://mantine.dev/llms/core-portal.md): Renders component outside of parent element tree
- [Progress](https://mantine.dev/llms/core-progress.md): Give user feedback for status of the task
- [Radio](https://mantine.dev/llms/core-radio.md): Wrapper for input type radio
- [RangeSlider](https://mantine.dev/llms/core-range-slider.md): RangeSlider component
- [Rating](https://mantine.dev/llms/core-rating.md): Pick and display rating
- [RingProgress](https://mantine.dev/llms/core-ring-progress.md): Give user feedback for status of the task with circle diagram
- [RollingNumber](https://mantine.dev/llms/core-rolling-number.md): Animate value changes with rolling digits
- [ScrollArea](https://mantine.dev/llms/core-scroll-area.md): Area with custom scrollbars
- [Scroller](https://mantine.dev/llms/core-scroller.md): Horizontal scroll container with navigation controls
- [SegmentedControl](https://mantine.dev/llms/core-segmented-control.md): A linear set of two or more segments
- [Select](https://mantine.dev/llms/core-select.md): Custom searchable select
- [SemiCircleProgress](https://mantine.dev/llms/core-semi-circle-progress.md): Represent progress with semi circle diagram
- [SimpleGrid](https://mantine.dev/llms/core-simple-grid.md): Responsive grid in which each item takes equal amount of space
- [Skeleton](https://mantine.dev/llms/core-skeleton.md): Indicate content loading state
- [Slider](https://mantine.dev/llms/core-slider.md): Slider component
- [Space](https://mantine.dev/llms/core-space.md): Add horizontal or vertical spacing from theme
- [Splitter](https://mantine.dev/llms/core-splitter.md): Resizable split pane layout
- [Spoiler](https://mantine.dev/llms/core-spoiler.md): Hide long sections of content under a spoiler
- [Stack](https://mantine.dev/llms/core-stack.md): Compose elements and components in a vertical flex container
- [Stepper](https://mantine.dev/llms/core-stepper.md): Display content divided into a steps sequence
- [Switch](https://mantine.dev/llms/core-switch.md): Capture boolean input from user
- [Table](https://mantine.dev/llms/core-table.md): Render table with theme styles
- [TableOfContents](https://mantine.dev/llms/core-table-of-contents.md): Renders a list of headings on the page and tracks current heading visible in the viewport
- [Tabs](https://mantine.dev/llms/core-tabs.md): Switch between different views
- [TagsInput](https://mantine.dev/llms/core-tags-input.md): Capture a list of values from user with free input and suggestions
- [Text](https://mantine.dev/llms/core-text.md): Display text
- [Textarea](https://mantine.dev/llms/core-textarea.md): Autosize or regular textarea
- [TextInput](https://mantine.dev/llms/core-text-input.md): Capture string input from user
- [ThemeIcon](https://mantine.dev/llms/core-theme-icon.md): Render icon inside element with theme colors
- [Timeline](https://mantine.dev/llms/core-timeline.md): Display list of events in chronological order
- [Title](https://mantine.dev/llms/core-title.md): h1-h6 heading
- [Tooltip](https://mantine.dev/llms/core-tooltip.md): Renders tooltip at given element on mouse over or other event
- [Transition](https://mantine.dev/llms/core-transition.md): Animate presence of component with pre-made animations
- [Tree](https://mantine.dev/llms/core-tree.md): Display a Tree structure
- [TreeSelect](https://mantine.dev/llms/core-tree-select.md): Select with hierarchical tree data
- [Typography](https://mantine.dev/llms/core-typography.md): Styles provider for html content
- [UnstyledButton](https://mantine.dev/llms/core-unstyled-button.md): Unstyled polymorphic button
- [VisuallyHidden](https://mantine.dev/llms/core-visually-hidden.md): Hide element visually but keep it accessible for screen readers
## Hooks
- [HooksPackage](https://mantine.dev/llms/hooks-package.md)
- [use-element-size](https://mantine.dev/llms/hooks-use-element-size.md): Returns element width and height and observes changes with ResizeObserver
- [useClickOutside](https://mantine.dev/llms/hooks-use-click-outside.md)
- [useClipboard](https://mantine.dev/llms/hooks-use-clipboard.md)
- [useCollapse](https://mantine.dev/llms/hooks-use-collapse.md)
- [useColorScheme](https://mantine.dev/llms/hooks-use-color-scheme.md)
- [useCounter](https://mantine.dev/llms/hooks-use-counter.md)
- [useDebouncedCallback](https://mantine.dev/llms/hooks-use-debounced-callback.md)
- [useDebouncedState](https://mantine.dev/llms/hooks-use-debounced-state.md)
- [useDebouncedValue](https://mantine.dev/llms/hooks-use-debounced-value.md)
- [useDidUpdate](https://mantine.dev/llms/hooks-use-did-update.md)
- [useDisclosure](https://mantine.dev/llms/hooks-use-disclosure.md)
- [useDocumentTitle](https://mantine.dev/llms/hooks-use-document-title.md)
- [useDocumentVisibility](https://mantine.dev/llms/hooks-use-document-visibility.md)
- [useDrag](https://mantine.dev/llms/hooks-use-drag.md)
- [useEventListener](https://mantine.dev/llms/hooks-use-event-listener.md)
- [useEyeDropper](https://mantine.dev/llms/hooks-use-eye-dropper.md)
- [useFavicon](https://mantine.dev/llms/hooks-use-favicon.md)
- [useFetch](https://mantine.dev/llms/hooks-use-fetch.md)
- [useFileDialog](https://mantine.dev/llms/hooks-use-file-dialog.md)
- [useFloatingWindow](https://mantine.dev/llms/hooks-use-floating-window.md)
- [useFocusReturn](https://mantine.dev/llms/hooks-use-focus-return.md)
- [useFocusTrap](https://mantine.dev/llms/hooks-use-focus-trap.md)
- [useFocusWithin](https://mantine.dev/llms/hooks-use-focus-within.md)
- [useForceUpdate](https://mantine.dev/llms/hooks-use-force-update.md)
- [useFullscreen](https://mantine.dev/llms/hooks-use-fullscreen.md)
- [useHash](https://mantine.dev/llms/hooks-use-hash.md)
- [useHeadroom](https://mantine.dev/llms/hooks-use-headroom.md)
- [useHotkeys](https://mantine.dev/llms/hooks-use-hotkeys.md)
- [useHover](https://mantine.dev/llms/hooks-use-hover.md)
- [useId](https://mantine.dev/llms/hooks-use-id.md)
- [useIdle](https://mantine.dev/llms/hooks-use-idle.md)
- [useInputState](https://mantine.dev/llms/hooks-use-input-state.md)
- [useIntersection](https://mantine.dev/llms/hooks-use-intersection.md)
- [useInterval](https://mantine.dev/llms/hooks-use-interval.md)
- [useInViewport](https://mantine.dev/llms/hooks-use-in-viewport.md)
- [useIsFirstRender](https://mantine.dev/llms/hooks-use-is-first-render.md)
- [useIsomorphicEffect](https://mantine.dev/llms/hooks-use-isomorphic-effect.md)
- [useListState](https://mantine.dev/llms/hooks-use-list-state.md)
- [useLocalStorage](https://mantine.dev/llms/hooks-use-local-storage.md)
- [useLogger](https://mantine.dev/llms/hooks-use-logger.md)
- [useLongPress](https://mantine.dev/llms/hooks-use-long-press.md)
- [useMap](https://mantine.dev/llms/hooks-use-map.md)
- [useMask](https://mantine.dev/llms/hooks-use-mask.md)
- [useMediaQuery](https://mantine.dev/llms/hooks-use-media-query.md)
- [useMergedRef](https://mantine.dev/llms/hooks-use-merged-ref.md)
- [useMounted](https://mantine.dev/llms/hooks-use-mounted.md)
- [useMouse](https://mantine.dev/llms/hooks-use-mouse.md)
- [useMove](https://mantine.dev/llms/hooks-use-move.md)
- [useMutationObserver](https://mantine.dev/llms/hooks-use-mutation-observer.md)
- [useNetwork](https://mantine.dev/llms/hooks-use-network.md)
- [useOrientation](https://mantine.dev/llms/hooks-use-orientation.md)
- [useOs](https://mantine.dev/llms/hooks-use-os.md)
- [usePageLeave](https://mantine.dev/llms/hooks-use-page-leave.md)
- [usePagination](https://mantine.dev/llms/hooks-use-pagination.md)
- [usePrevious](https://mantine.dev/llms/hooks-use-previous.md)
- [useQueue](https://mantine.dev/llms/hooks-use-queue.md)
- [useRadialMove](https://mantine.dev/llms/hooks-use-radial-move.md)
- [useReducedMotion](https://mantine.dev/llms/hooks-use-reduced-motion.md)
- [useResizeObserver](https://mantine.dev/llms/hooks-use-resize-observer.md)
- [useRovingIndex](https://mantine.dev/llms/hooks-use-roving-index.md)
- [useScrollDirection](https://mantine.dev/llms/hooks-use-scroll-direction.md)
- [useScroller](https://mantine.dev/llms/hooks-use-scroller.md)
- [useScrollIntoView](https://mantine.dev/llms/hooks-use-scroll-into-view.md)
- [useScrollSpy](https://mantine.dev/llms/hooks-use-scroll-spy.md)
- [useSelection](https://mantine.dev/llms/hooks-use-selection.md)
- [useSet](https://mantine.dev/llms/hooks-use-set.md)
- [useSetState](https://mantine.dev/llms/hooks-use-set-state.md)
- [useShallowEffect](https://mantine.dev/llms/hooks-use-shallow-effect.md)
- [useSplitter](https://mantine.dev/llms/hooks-use-splitter.md)
- [useStateHistory](https://mantine.dev/llms/hooks-use-state-history.md)
- [useTextSelection](https://mantine.dev/llms/hooks-use-text-selection.md)
- [useThrottledCallback](https://mantine.dev/llms/hooks-use-throttled-callback.md)
- [useThrottledState](https://mantine.dev/llms/hooks-use-throttled-state.md)
- [useThrottledValue](https://mantine.dev/llms/hooks-use-throttled-value.md)
- [useTimeout](https://mantine.dev/llms/hooks-use-timeout.md)
- [useToggle](https://mantine.dev/llms/hooks-use-toggle.md)
- [useUncontrolled](https://mantine.dev/llms/hooks-use-uncontrolled.md)
- [useValidatedState](https://mantine.dev/llms/hooks-use-validated-state.md)
- [useViewportSize](https://mantine.dev/llms/hooks-use-viewport-size.md)
- [useWindowEvent](https://mantine.dev/llms/hooks-use-window-event.md)
- [useWindowScroll](https://mantine.dev/llms/hooks-use-window-scroll.md)
## Form
- [Form context](https://mantine.dev/llms/form-create-form-context.md): Add context support to use-form with createFormContext
- [formActions](https://mantine.dev/llms/form-actions.md)
- [formAllInputs](https://mantine.dev/llms/form-all-inputs.md)
- [formErrors](https://mantine.dev/llms/form-errors.md)
- [formGetInputProps](https://mantine.dev/llms/form-get-input-props.md)
- [formNested](https://mantine.dev/llms/form-nested.md)
- [FormPackage](https://mantine.dev/llms/form-package.md)
- [formRecipes](https://mantine.dev/llms/form-recipes.md)
- [formSchemaValidation](https://mantine.dev/llms/form-schema-validation.md)
- [formStatus](https://mantine.dev/llms/form-status.md)
- [formUncontrolled](https://mantine.dev/llms/form-uncontrolled.md)
- [formValidation](https://mantine.dev/llms/form-validation.md)
- [formValidators](https://mantine.dev/llms/form-validators.md)
- [formValues](https://mantine.dev/llms/form-values.md)
- [use-field](https://mantine.dev/llms/form-use-field.md): use-field hook – manage single field state
- [use-form](https://mantine.dev/llms/form-use-form.md): Manage form state
## Dates
- [Calendar](https://mantine.dev/llms/dates-calendar.md): Base component for custom date pickers
- [DateInput](https://mantine.dev/llms/dates-date-input.md): Free form date input
- [DatePicker](https://mantine.dev/llms/dates-date-picker.md): Inline date, multiple dates and dates range picker
- [DatePickerInput](https://mantine.dev/llms/dates-date-picker-input.md): Date, multiple dates and dates range picker input
- [DateTimePicker](https://mantine.dev/llms/dates-date-time-picker.md): Capture datetime from the user
- [GettingStartedDates](https://mantine.dev/llms/dates-getting-started.md)
- [InlineDateTimePicker](https://mantine.dev/llms/dates-inline-date-time-picker.md): Inline date and time picker with range support
- [MiniCalendar](https://mantine.dev/llms/dates-mini-calendar.md): Compact calendar to display a small number of days in a row
- [MonthPicker](https://mantine.dev/llms/dates-month-picker.md): Inline month, multiple months and months range picker
- [MonthPickerInput](https://mantine.dev/llms/dates-month-picker-input.md): Month, multiple months and months range picker input
- [TimeGrid](https://mantine.dev/llms/dates-time-grid.md): Captures time value from the user with a predefined set of options
- [TimeInput](https://mantine.dev/llms/dates-time-input.md): Capture time from the user
- [TimePicker](https://mantine.dev/llms/dates-time-picker.md): Captures time value from the user
- [TimeValue](https://mantine.dev/llms/dates-time-value.md): Display a formatted time value
- [YearPicker](https://mantine.dev/llms/dates-year-picker.md): Inline year, multiple years and years range picker
- [YearPickerInput](https://mantine.dev/llms/dates-year-picker-input.md): Inline year, multiple years and years range picker
## Charts
- [AreaChart](https://mantine.dev/llms/charts-area-chart.md): Area chart component with stacked, percent and split variants
- [BarChart](https://mantine.dev/llms/charts-bar-chart.md): Bar chart component with stacked and percent variants
- [BarsList](https://mantine.dev/llms/charts-bars-list.md): Display a list of bars with names and values
- [BubbleChart](https://mantine.dev/llms/charts-bubble-chart.md): Bubble chart component
- [BulletChart](https://mantine.dev/llms/charts-bullet-chart.md): Compact KPI chart with value, target, and qualitative ranges
- [CompositeChart](https://mantine.dev/llms/charts-composite-chart.md): Composed chart with support for Area, Bar and Line charts
- [DonutChart](https://mantine.dev/llms/charts-donut-chart.md): Donut chart component
- [FunnelChart](https://mantine.dev/llms/charts-funnel-chart.md): Funnel chart component
- [GettingStartedCharts](https://mantine.dev/llms/charts-getting-started.md)
- [Heatmap](https://mantine.dev/llms/charts-heatmap.md): Heatmap chart component
- [LineChart](https://mantine.dev/llms/charts-line-chart.md): Line chart component
- [PieChart](https://mantine.dev/llms/charts-pie-chart.md): Pie chart component
- [RadarChart](https://mantine.dev/llms/charts-radar-chart.md): Radar chart component
- [RadialBarChart](https://mantine.dev/llms/charts-radial-bar-chart.md): Radial bar chart component
- [SankeyChart](https://mantine.dev/llms/charts-sankey-chart.md): Sankey diagram component
- [ScatterChart](https://mantine.dev/llms/charts-scatter-chart.md): Scatter chart component
- [Sparkline](https://mantine.dev/llms/charts-sparkline.md): Simplified area chart to show trends
- [SunburstChart](https://mantine.dev/llms/charts-sunburst-chart.md): Sunburst chart component for hierarchical data
- [Treemap](https://mantine.dev/llms/charts-treemap.md): Treemap chart component
## Schedule
- [AgendaView](https://mantine.dev/llms/schedule-agenda-view.md): Vertical list view of events grouped by date
- [DayView](https://mantine.dev/llms/schedule-day-view.md): Standalone schedule day view component
- [GettingStartedSchedule](https://mantine.dev/llms/schedule-getting-started.md)
- [MobileMonthView](https://mantine.dev/llms/schedule-mobile-month-view.md): Mobile-optimized schedule month view component
- [MonthView](https://mantine.dev/llms/schedule-month-view.md): Standalone schedule month view component
- [RecurringEventsSchedule](https://mantine.dev/llms/schedule-recurring-events.md)
- [ResourcesDayView](https://mantine.dev/llms/schedule-resources-day-view.md): Day view with resource columns for scheduling across resources
- [ResourcesMonthView](https://mantine.dev/llms/schedule-resources-month-view.md): Month view with resource lanes for scheduling across resources
- [ResourcesSchedule](https://mantine.dev/llms/schedule-resources-schedule.md): Schedule wrapper component that combines resource day, week and month views
- [ResourcesWeekView](https://mantine.dev/llms/schedule-resources-week-view.md): Week view with resource rows for scheduling across resources
- [Schedule](https://mantine.dev/llms/schedule-schedule.md): Schedule component with day, week, month and year views
- [ScheduleEventsData](https://mantine.dev/llms/schedule-events-data.md)
- [WeekView](https://mantine.dev/llms/schedule-week-view.md): Standalone schedule week view component
- [YearView](https://mantine.dev/llms/schedule-year-view.md): Standalone schedule year view component
## Guides
- [ControlledVsUncontrolled](https://mantine.dev/llms/guides-controlled-vs-uncontrolled.md)
- [CustomComponents](https://mantine.dev/llms/guides-custom-components.md)
- [EightToNine](https://mantine.dev/llms/guides-8x-to-9x.md)
- [FunctionsReference](https://mantine.dev/llms/guides-functions-reference.md)
- [Gatsby](https://mantine.dev/llms/guides-gatsby.md)
- [Icons](https://mantine.dev/llms/guides-icons.md)
- [JavaScript](https://mantine.dev/llms/guides-javascript.md)
- [Jest](https://mantine.dev/llms/guides-jest.md)
- [LLMDocumentation](https://mantine.dev/llms/guides-llms.md)
- [NextJs](https://mantine.dev/llms/guides-next.md)
- [Polymorphic](https://mantine.dev/llms/guides-polymorphic.md)
- [ReactRouter](https://mantine.dev/llms/guides-react-router.md)
- [Redwood](https://mantine.dev/llms/guides-redwood.md)
- [SevenToEight](https://mantine.dev/llms/guides-7x-to-8x.md)
- [SixToSeven](https://mantine.dev/llms/guides-6x-to-7x.md)
- [Storybook](https://mantine.dev/llms/guides-storybook.md)
- [Tiptap3Migration](https://mantine.dev/llms/guides-tiptap-3-migration.md)
- [TypeScript](https://mantine.dev/llms/guides-typescript.md)
- [Vite](https://mantine.dev/llms/guides-vite.md)
- [Vitest](https://mantine.dev/llms/guides-vitest.md)
## Theming
- [Colors](https://mantine.dev/llms/theming-colors.md)
- [ColorSchemes](https://mantine.dev/llms/theming-color-schemes.md)
- [DefaultProps](https://mantine.dev/llms/theming-default-props.md)
- [MantineProvider](https://mantine.dev/llms/theming-mantine-provider.md)
- [ThemeObject](https://mantine.dev/llms/theming-theme-object.md)
- [Typography](https://mantine.dev/llms/theming-typography.md): Styles provider for html content
## Styles
- [ColorFunctions](https://mantine.dev/llms/styles-color-functions.md)
- [CSSFilesList](https://mantine.dev/llms/styles-css-files-list.md)
- [CSSModules](https://mantine.dev/llms/styles-css-modules.md)
- [CssVariables](https://mantine.dev/llms/styles-css-variables.md)
- [CSSVariablesList](https://mantine.dev/llms/styles-css-variables-list.md)
- [DataAttributes](https://mantine.dev/llms/styles-data-attributes.md)
- [Emotion](https://mantine.dev/llms/styles-emotion.md)
- [GlobalStyles](https://mantine.dev/llms/styles-global-styles.md)
- [MantineStyles](https://mantine.dev/llms/styles-mantine-styles.md)
- [PostCSSPreset](https://mantine.dev/llms/styles-postcss-preset.md)
- [Rem](https://mantine.dev/llms/styles-rem.md)
- [ResponsiveStyles](https://mantine.dev/llms/styles-responsive.md)
- [Rtl](https://mantine.dev/llms/styles-rtl.md)
- [StyleProp](https://mantine.dev/llms/styles-style.md)
- [StyleProps](https://mantine.dev/llms/styles-style-props.md)
- [StylesApi](https://mantine.dev/llms/styles-styles-api.md)
- [StylesOverview](https://mantine.dev/llms/styles-styles-overview.md)
- [StylesPerformance](https://mantine.dev/llms/styles-styles-performance.md)
- [UnstyledComponents](https://mantine.dev/llms/styles-unstyled.md)
- [UsageWithSass](https://mantine.dev/llms/styles-sass.md)
- [VanillaExtract](https://mantine.dev/llms/styles-vanilla-extract.md)
- [VariantsAndSizes](https://mantine.dev/llms/styles-variants-sizes.md)
## X
- [Carousel](https://mantine.dev/llms/x-carousel.md): Embla based carousel component
- [CodeHighlight](https://mantine.dev/llms/x-code-highlight.md): Highlight code with shiki or highlight.js
- [Dropzone](https://mantine.dev/llms/x-dropzone.md): Capture files from user with drag and drop
- [ExtensionsPackage](https://mantine.dev/llms/x-extensions.md)
- [Modals manager](https://mantine.dev/llms/x-modals.md): Centralized modals manager with option to handle state of multi-step modals
- [NavigationProgress](https://mantine.dev/llms/x-nprogress.md): Navigation progress bar
- [Notifications system](https://mantine.dev/llms/x-notifications.md): Mantine notifications system
- [Rich text editor](https://mantine.dev/llms/x-tiptap.md): Tiptap based rich text editor
- [Spotlight](https://mantine.dev/llms/x-spotlight.md): Command center for your application
## Other
- [About](https://mantine.dev/llms/about.md)
- [AllReleases](https://mantine.dev/llms/changelog-all-releases.md)
- [BrowserSupport](https://mantine.dev/llms/browser-support.md)
- [Changelog700](https://mantine.dev/llms/changelog-7-0-0.md)
- [Changelog710](https://mantine.dev/llms/changelog-7-1-0.md)
- [Changelog7100](https://mantine.dev/llms/changelog-7-10-0.md)
- [Changelog7110](https://mantine.dev/llms/changelog-7-11-0.md)
- [Changelog7120](https://mantine.dev/llms/changelog-7-12-0.md)
- [Changelog7130](https://mantine.dev/llms/changelog-7-13-0.md)
- [Changelog7140](https://mantine.dev/llms/changelog-7-14-0.md)
- [Changelog7150](https://mantine.dev/llms/changelog-7-15-0.md)
- [Changelog7160](https://mantine.dev/llms/changelog-7-16-0.md)
- [Changelog7170](https://mantine.dev/llms/changelog-7-17-0.md)
- [Changelog720](https://mantine.dev/llms/changelog-7-2-0.md)
- [Changelog730](https://mantine.dev/llms/changelog-7-3-0.md)
- [Changelog740](https://mantine.dev/llms/changelog-7-4-0.md)
- [Changelog750](https://mantine.dev/llms/changelog-7-5-0.md)
- [Changelog760](https://mantine.dev/llms/changelog-7-6-0.md)
- [Changelog770](https://mantine.dev/llms/changelog-7-7-0.md)
- [Changelog780](https://mantine.dev/llms/changelog-7-8-0.md)
- [Changelog790](https://mantine.dev/llms/changelog-7-9-0.md)
- [Changelog800](https://mantine.dev/llms/changelog-8-0-0.md)
- [Changelog810](https://mantine.dev/llms/changelog-8-1-0.md)
- [Changelog820](https://mantine.dev/llms/changelog-8-2-0.md)
- [Changelog830](https://mantine.dev/llms/changelog-8-3-0.md)
- [Changelog900](https://mantine.dev/llms/changelog-9-0-0.md)
- [Changelog910](https://mantine.dev/llms/changelog-9-1-0.md)
- [Changelog920](https://mantine.dev/llms/changelog-9-2-0.md)
- [Changelog930](https://mantine.dev/llms/changelog-9-3-0.md)
- [Changelog940](https://mantine.dev/llms/changelog-9-4-0.md)
- [Changelog950](https://mantine.dev/llms/changelog-9-5-0.md)
- [Contribute](https://mantine.dev/llms/contribute.md)
- [GettingStarted](https://mantine.dev/llms/getting-started.md)
- [NotFound](https://mantine.dev/llms/404.md)
- [OxcConfig](https://mantine.dev/llms/oxc-config-mantine.md)
- [PreviousChangelogs](https://mantine.dev/llms/changelog-previous-versions.md)
- [Support](https://mantine.dev/llms/support.md)
+39
View File
@@ -0,0 +1,39 @@
# Product
## Register
product
## Users
Homarr is for self-hosters and administrators who assemble dashboards for services across home labs and private infrastructure. They need dense, reliable controls and status at a glance, while retaining enough guidance to configure advanced features without hiding how they work.
## Product Purpose
Homarr provides one place to organize, observe, and operate self-hosted services. Success means users can build useful dashboards quickly, understand their state, recover from configuration errors, act on services, and reshape their workspace without repeatedly leaving the board. Boards must load immediately, stay visually stable, and keep widgets legible across screen sizes.
## Brand Personality
Direct, capable, precise, and approachable. Homarr should feel calm under load, be truthful about system state, and reward users who refine details.
## Anti-references
- Decorative dashboards, oversized metric cards, and motion without state meaning.
- Wizard flows that fight ordinary scrolling or rigid layouts that force one workflow.
- Hidden configuration magic and implementation complexity exposed before it is needed.
- Clever custom controls where familiar platform or Mantine interactions already work.
## Design Principles
1. Keep configuration, consequences, and system state visible.
2. Make simple tasks short without limiting advanced workflows.
3. Preserve expert depth while prioritizing urgent information at small sizes.
4. Reveal dense workspaces progressively through a consistent advanced focus mode.
5. Isolate failures and show loading, stale, partial, empty, and failed states truthfully.
6. Use the existing Mantine design language consistently across management and board surfaces.
7. Keep spatial actions direct: moving, resizing, nesting, and using fixed rails happen on the canvas with immediate feedback.
8. Preserve predictable widget composition with fixed logical tile sizes and horizontal scrolling before controls become inaccessible.
## Accessibility & Inclusion
Target WCAG 2.2 AA. Every pointer interaction needs a keyboard and coarse-pointer path. Support screen readers, visible and restored focus, sufficient contrast, reduced motion, non-color status cues, responsive layouts, actionable error messages, and keyboard equivalents for drag and resize operations.
+263
View File
@@ -0,0 +1,263 @@
# PR #6502 widget-modernization ultra-review
## Executive result
**Merge recommendation: changes required.** No P0 was found, but the current branch still has five P1 issues: a pre-existing Custom API authorization/redirect weakness exposed by this subsystem review, an inaccessible manual advanced-focus interaction, a likely fixed-position/transform geometry defect, systemic false empty states for failed queries, and unvalidated integration-provided navigation URLs. Eight P2 issues cover raw error disclosure, unsafe runtime-ref interfaces, localization, responsive/theming drift, test coverage, and inaccurate PR copy.
Audit health (Impeccable rubric): accessibility 1/4, performance 2/4, responsive design 2/4, theming 2/4, anti-patterns 3/4; **10/20 (Acceptable, significant work required)**. The UI does not look generically AI-generated; its problem is inconsistent technical behavior beneath an otherwise coherent Mantine vocabulary.
## Reviewed revision and environment
| Item | Value |
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
| PR | #6502, `feat: modernize dashboard widgets` |
| Reviewed head | `96e92485a5fd45f3648cfac0453e614779c414ba` |
| Base branch / fetched base | `dev` / `d58a47b776a55d04dee2220f59d072b9af8bbed3` |
| Merge base | `d58a47b776a55d04dee2220f59d072b9af8bbed3` |
| Diff | 359 files, +15,721/-4,478 |
| PR state | open, non-draft, `BLOCKED`; no approval decision |
| Hosted checks at audit start | Fast gate, Container and E2E, CodeQL, JS/TS analysis, Actions analysis, title, auto-fix: green |
| Inline threads | 34 total, 7 unresolved; only 2 remain substantively valid |
| Mantine | repository lock: `@mantine/core`, hooks, charts and dates 9.4.1 |
| Local tools | Node 24.16.0 and pnpm 9.5.0; repository requires Node >=24.18.0 and pnpm >=11.15.1 |
The former red baseline `7b7045202d289b7d83946befcee5d2845ea4a706` had a green Fast gate and a failed Container/E2E run (`30748255760`, job `91498308523`). The latest head has a green Container/E2E job (`30756067198`, job `91519039504`). This proves the current suite passed once; it does not satisfy the requested three-run flake characterization.
## PR-description traceability
| Claim | Result | Evidence / correction |
| --------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| “all 54 widget renderers with compact and advanced modes” | **Inaccurate** | 54 `WidgetKind` values exist. `displayMode="advanced"` reaches 53; `app` is deliberately excluded in `item-content.tsx:51` and documented as compact-only. Change copy to “53 widget renderers; app tiles remain compact,” or implement app advanced mode. |
| advanced focus: hover, keyboard, touch, geometry | **Partial** | Entry paths, Escape ownership, reduced motion, close focus, touch button and zero-dimension guards exist. WID-002/003 remain. |
| richer widget context actions | **Partial** | `Menu.ContextMenu` is a good Mantine choice and query scope is item-aware. Dispatch still crosses `any`; Custom API action authorization is not represented. |
| standardized query identity/cache/status | **Partial** | Query keys, matchers, runtime capture, recovery and tests exist. Thirty-nine of 53 renderer query calls do not read errors; runtime ref registration is render-time mutable state. |
| safe polling | **Mostly confirmed** | defaults are limited to `widget` namespace; Custom API resume immediately refetches; per-widget intervals exist. State/ref typing still needs redesign. |
| partial-failure rendering | **Partial** | settlement helper and several routers are strong; many renderer-level total failures still appear empty/disconnected. |
| bounded integration responses | **Mostly confirmed** | common bounded reader, Custom API 2 MiB, timetable 512 KiB, Traefik bounds and Beszel queue bounds exist. Custom action execution remains outside the hardened fetch path. |
| timetable hardening | **Confirmed** | board/item permission checks, saved-value comparison, public-address resolution, private ranges, per-request DNS re-resolution, pinned dispatcher, redirect rejection, timeouts, response bounds and schemas are present. |
| precise placement | **Mostly confirmed** | mouse, touch tolerance, keyboard, occupied-cell validation, dynamic bounds, nearest fit and immediate persistence exist. Failure/race/live responsive coverage remains insufficient. |
| widget catalog documentation | **Partial** | all kinds have pages and shared interactions; wording overclaims focus behavior and validation counts are stale. |
| validation: 40 files/162 tests, 6/6 E2E | **Stale** | current diff contains 57 changed unit-spec files (including three E2E specs); repository has 42 widget spec files and six E2E specs. Update from the exact final run. |
| independent reviews “pass” | **Not supported** | unresolved valid findings, current P1s, and unavailable live QA contradict an unqualified pass. |
## Mantine suitability ledger
Legend: **U** already used appropriately; **C** consolidation/replacement candidate; **R** relevant but current implementation can stay; **N** not relevant to widget runtime; **D** dependency/version addition. Exact 9.4.1 package archives were inspected in `/tmp`; live docs/Context7 include 9.5.0.
### Core catalog
- **U:** ActionIcon, Alert, Anchor, Avatar, Badge, Box, Button, Card, Center, Code, Divider, Flex, Grid, Group, Image, Loader, Menu (`Menu.ContextMenu`), Overlay, Paper, Popover, Portal, Progress, RingProgress, ScrollArea, SimpleGrid, Skeleton, Stack, Table, Tabs, Text, ThemeIcon, Title, Tooltip, UnstyledButton.
- **C:** Accordion (dense integration sections), DataList (semantic label/value stats), EmptyState (replace bespoke empty state), FocusTrap and Modal compound API (WID-002), LoadingOverlay (background refetch without deleting content), NumberFormatter, OverflowList/Scroller (responsive action and chip rows), Table.ScrollContainer, Timeline (activity/feed histories), VisuallyHidden and Kbd, CloseButton, Dialog/HoverCard/Notification where persistent errors/actions need semantics.
- **R:** Affix, AppShell, AspectRatio, BackgroundImage, Breadcrumbs, Burger, Checkbox, Chip, Collapse, ColorInput/ColorPicker/ColorSwatch/AlphaSlider/AngleSlider/HueSlider, Combobox/ComboboxPopover, Container, CopyButton, Drawer, Fieldset, FileButton/FileInput, FloatingIndicator/FloatingWindow, Highlight, Indicator, Input/JsonInput, List, Mark, Marquee, MaskInput, Menubar, MultiSelect/NativeSelect/NumberInput/PasswordInput/PillsInput/PinInput/Radio/RangeSlider/Rating/SegmentedControl/Select/Slider/Switch/TagsInput/Textarea/TextInput, Pagination, Pill, SemiCircleProgress, Space, Splitter, Spoiler, Stepper, Transition, Tree/TreeSelect, Typography. Use only where an existing widget requirement matches; do not churn working layouts.
- **N:** Alpha/color/angle primitives outside configuration forms, AppShell/navigation primitives, TableOfContents, CorePackage documentation.
- **D (9.5, not 9.4.1):** Cascader. **RollingNumber and Splitter are present in 9.4.1.**
### Hooks catalog
- **C:** `useFocusReturn`, `useFocusTrap`, `useReducedMotion`, `useRovingIndex`, `useLongPress`, `useScroller`, `useElementSize`, `useViewportSize`, `useHotkeys`, `useMediaQuery`, `useResizeObserver`.
- **U/R:** focus-within, hover, disclosure, click-outside, merged-ref, interval/timeout, local-storage, event/window listeners, throttled/debounced values and list/set/map/state helpers are available in 9.4.1 and should replace local mechanics only when behavior and tests improve.
- **N:** document title/favicon/hash/headroom/page-leave/OS/eye-dropper/fullscreen/network/orientation and other page-level utilities have no direct widget-remediation need. Every hook listed under `## Hooks` in `MANTINE.md` is covered by this rule; no hook requires 9.5 for the recommendations above.
### Form, dates, charts, schedule, extensions, theme and styles
- **Form:** existing `@homarr/form` should remain the abstraction over all cataloged form APIs. No widget-runtime replacement; use current validation/status APIs for widget editors (**R**).
- **Dates:** all catalog entries are in installed 9.4.1 dates. `Calendar`, `MiniCalendar`, and `TimeValue` are candidates; other picker/input entries are editor-only (**R**). Importing dates directly into `@homarr/widgets` requires adding that workspace dependency (**D**).
- **Charts:** 9.4.1 exports Area, Bar, BarsList, Bubble, Composite, Donut, Funnel, Heatmap, Line, Pie, Radar, RadialBar, Sankey, Scatter, Sparkline and Treemap (**C/R**). Prefer Sparkline for tiny trends, BarsList for ranked values, Heatmap for calendar/activity density, Composite for mixed analytics. **BulletChart and SunburstChart are 9.5-only (D)**. Replacement requires accessibility and bundle comparison, not blanket Recharts removal.
- **Schedule:** all `AgendaView`, day/week/month/year/resource views and Schedule pages require new `@mantine/schedule` dependency (**D**). AgendaView is the only plausible calendar-widget candidate; its weight and semantics must beat the existing grouped list before adoption.
- **Guides/FAQ/Other:** server components, portal testing, scroll lock, accessibility, nested popovers, zoom, responsive styling and version changelogs are relevant references (**R**); framework/setup/contribution/old migration entries are **N**. The 9.5 changelog is informational only.
- **Theming/styles:** Colors, ColorSchemes, DefaultProps, MantineProvider, ThemeObject, CSS variables, data attributes, CSS Modules, responsive/RTL/style props/Styles API/performance are **U/R**. WID-009 requires shared layer/color tokens. Emotion, Sass, Vanilla Extract and unrelated global setup are **N**.
- **Extensions:** existing modals, notifications, spotlight and rich-text abstractions are **U**; Carousel could consolidate Immich only after behavior parity; Dropzone/CodeHighlight/NProgress are **N** for this audit.
## Complete 54-widget coverage matrix
“Test” means a focused widget-local spec exists, not that all required states are covered. Every row still requires the common contract suite in WID-013.
| WidgetKind | Family | Advanced | Focused test | Principal audit gap |
| --------------------------- | ----------------------- | ----------- | ------------ | ---------------------------------------------------------- |
| clock | basic | yes | no | query error, locale-format options |
| weather | basic/feed | yes | yes | query error; dayjs locale/time patterns |
| app | basic/link | **no** | no | PR wording; URL contract |
| iframe | custom content | yes | yes | manual focus/iframe trapping; URL privacy thread now fixed |
| video | custom content | yes | no | media/focus/live behavior |
| notebook | notes | yes (inner) | yes | editor focus inside manual surface |
| anchorNote | notes | yes | no | query error, save/error regression |
| dnsHoleSummary | network | yes | no | total query error, hard-coded chart colors |
| dnsHoleControls | network/security | yes | no | total error and permission matrix |
| smartHome-entityState | smart home | yes | no | raw state in aria; action permissions |
| smartHome-executeAutomation | smart home | yes | no | mutation/permission/edit-mode E2E |
| stockPrice | analytics | yes | yes | total error |
| mediaServer | media | yes | no | total error and table a11y |
| calendar | calendar | yes | yes | total error; fixed HH:mm; schedule tradeoff |
| downloads | media | yes | yes | four-state/component coverage, arbitrary z-index |
| mediaRequests-requestList | media | yes | no | total error; external URLs/actions |
| mediaRequests-requestStats | media | yes | no | total error; links/colors |
| mediaTranscoding | media | yes | yes | total error and responsive table |
| mediaMissing | media | yes | yes | total error and external URL |
| minecraftServerStatus | infrastructure | yes | no | total error/malformed payload |
| networkControllerSummary | networking | yes | no | total error; WAN/unknown localization |
| networkControllerStatus | networking | yes | no | responsive rule duplication |
| rssFeed | feed | yes | yes | strong stale/partial state; locale relative time |
| bookmarks | basic/link | yes | yes | recomputation; URL contract |
| indexerManager | media | yes | no | total error; runtime action ref |
| healthMonitoring | infrastructure | yes | yes | integration query error |
| releases | infrastructure/feed | yes | yes | secret/item authorization needs E2E |
| mediaReleases | media | yes | no | total error; external URLs |
| dockerContainers | infrastructure | yes | yes | total error; z-index/table semantics |
| firewall | networking/security | yes | yes | partial-query matrix; loose router generics |
| notifications | infrastructure | yes | no | total error; untrusted href |
| systemResources | infrastructure | yes | yes | total error; previous network thread fixed |
| coolify | infrastructure | yes | yes | total error; URL validation |
| systemDisks | infrastructure | yes | yes | total error |
| timetable | calendar | yes | no | backend strong; component locale/live tests absent |
| immich-serverStats | media | yes | yes | two ignored query errors |
| immich-albumCarousel | media | yes | yes (query) | query error and nested overlay/focus E2E |
| paperlessNgx | analytics | yes | no | former error thread fixed, no regression spec |
| patchmon | infrastructure | yes | yes | total error |
| bazarr | media | yes | yes | total error |
| tracearr | analytics | yes | yes | total error |
| speedtestTracker | analytics | yes | yes | total error |
| uptimeKuma | infrastructure | yes | yes | total error |
| audioStats | media | yes | yes | good dual-error handling; partial-state tests needed |
| umami | analytics | yes | no | runtime query registration; chart a11y |
| vpn | networking/security | yes | no | total error; status/permission matrix |
| archiveTeamWarrior | infrastructure | yes | yes | suspense path; docs present |
| ups | infrastructure | yes | no | total error |
| beszelSystemTable | monitoring | yes | yes | nested chart/focus and live subscription |
| beszelSystemGrid | monitoring | yes | no | live/error UI; no focused renderer test |
| beszelAlerts | monitoring | yes | no | partial error and status semantics |
| beszelSystemStats | monitoring | yes | yes | runtime refs, locale tooltip |
| traefik | networking | yes | no | total error; bounded backend positive |
| customApi | custom content/security | yes | yes | WID-001/007/008; polling resume fixed |
## Severity-ranked findings
### WID-001 — P1 confirmed, pre-existing: Custom API actions lack resource authorization and hardened fetch policy
- **Location/claim:** `packages/api/src/router/custom-widget/custom-widget-router.ts:57-102,392-451`; “harden Custom API” and action-permission semantics.
- **Evidence/reproduction:** any authenticated account can call `customWidget.all`, learn every definition ID and URL, then call `customWidget.execute({definitionId})`. The mutation verifies neither board nor item nor an action permission. It decrypts administrator-provided secrets, uses `new URL()` only, and `redirect: "follow"`. The PR hardened `widget.customApi.getData`, but not this sibling execution path.
- **Impact/rule:** unauthorized side effects against internal services and authenticated redirect chains; broken object-level authorization. A view-only account can invoke administrator-created controls outside any board context.
- **Correction:** require `{boardId,itemId,definitionId}`; load the item and verify it is `customApi`, belongs to the board, references the definition, and the caller has an explicit execute policy. Until that policy exists, restrict to admin. Share `validateCustomApiUrl`, timeout, redirect rejection, bounded disposal and redacted errors with getData. Compatibility: old direct callers must send item/board context.
- **Tests:** procedure tests for anonymous, unrelated authenticated, view-only, modifier, admin, mismatched item/definition, disabled definition, redirects, credential URL, timeout and secret non-disclosure; E2E action button across roles.
### WID-002 — P1 confirmed, PR-introduced: manual advanced focus is not an accessible modal
- **Location:** `advanced-focus/context.tsx:194-209`, `items/item-content.tsx:113-189`.
- **Evidence:** manual mode renders an Overlay plus a `role="region"` Card. There is no FocusTrap, `aria-modal`, background inertness, scroll lock or backdrop dismissal. Only the close button is initially focused; Tab can leave the surface and activate dimmed board controls.
- **Impact/rule:** keyboard/screen-reader users lose the interaction boundary; WCAG 2.1.2, 2.4.3 and ARIA dialog pattern. Iframes/editors make escape especially likely.
- **Correction/Mantine:** render manual mode through `Modal.Root` compound parts (or ModalBase), using its portal, FocusTrap, scroll lock, Escape/return-focus and semantic content. Preserve preview mode as non-modal. Configure nested overlays and `removeScrollProps`; keep a 44px `Modal.CloseButton` and explicit title via VisuallyHidden if needed.
- **Tests:** RTL portal/focus-cycle/return tests; Playwright Tab/Shift+Tab, nested Menu/Select/Popover/editor/iframe, backdrop and every close path; reduced motion and 200% zoom.
### WID-003 — P1 probable, PR-introduced: fixed advanced surface remains under transformed Gridstack ancestry
- **Location:** `advanced-focus.module.css:35-42`; Card remains inside the Gridstack item while only the backdrop is portaled.
- **Evidence:** CSS fixed descendants establish their containing block at a transformed ancestor. Gridstack uses transforms for positioned items, so viewport-derived `left/top` can be interpreted relative to the item on affected layouts. A Portal wrapper exists only for Overlay.
- **Impact:** off-screen, clipped or incorrectly scaled focus surface across breakpoints/remounts.
- **Correction:** portal the focused surface itself (naturally achieved by WID-002) and calculate geometry in the same viewport coordinate system. Do not compensate with transform-specific offsets.
- **Tests:** real Gridstack at every layout/viewport, scrolled page, header, dynamic section and resize/remount; assert bounding rect within viewport and finite scale.
### WID-004 — P1 confirmed, regression-exposed/systemic: query failures still become valid empty/disconnected UI
- **Location:** 39 of 53 renderer query calls do not read `error`, including bookmarks, weather, calendar, media server, system resources, Coolify, DNS Hole, Tracearr, notifications, Immich, VPN and others listed in the matrix. Query defaults do not set `throwOnError` (`trpc.tsx:69-93`).
- **Evidence:** TanStack Query returns `data=undefined` on total failure. Components default to `[]`, `null`, or empty branches, so the board ErrorBoundary is never reached. Network Controller and Paperless were fixed, proving the pattern; it was not completed.
- **Impact:** operators are told “empty” or “disconnected” when monitoring failed, violating the product’s truthful-state requirement.
- **Correction/Mantine:** introduce a shared `WidgetQueryState` contract distinguishing initial Skeleton, background LoadingOverlay, stale Alert, partial warning, true EmptyState and terminal error. Either explicitly throw only when no usable cached data or render a shared error Alert. Preserve stale data.
- **Tests:** parameterized contract tests for every queried widget: initial, success-empty, total failure, stale+failure, partial failure, refetch, remount/persisted cache and sibling isolation.
### WID-005 — P1 confirmed, PR-introduced/systemic: integration-provided links bypass a shared URL policy
- **Location:** examples include `media-missing/component.tsx:291`, `notifications/component.tsx:77`, media requests list/stats, Coolify resource/server links, releases, bookmarks and iframe. Only RSS and custom JSX visibly use explicit protocol sanitizers.
- **Evidence:** dynamic strings are assigned directly to `href`/new-tab controls. Integration APIs and remote feeds are trust boundaries; no renderer-wide `http/https` guard is enforced.
- **Impact:** unsafe schemes, credential-bearing URLs, internal detail disclosure and inconsistent opener behavior.
- **Correction:** add one typed application URL helper returning a sanitized URL/undefined, with allowed schemes per use case, credential stripping/rejection, consistent `noopener noreferrer`, and disabled non-link presentation. Validate again server-side for action endpoints.
- **Tests:** `javascript:`, `data:`, malformed, credential, relative, http/https and missing URLs for each shared link primitive.
### WID-006 — P2 confirmed, PR-introduced: runtime query/action state mutates untyped refs during render
- **Location:** `definition.ts:121-162`; runtime registration calls in media missing/transcoding, audio, calendar, Umami, Beszel and Immich; `widget-context-menu.tsx:157-174`.
- **Evidence:** `setWidgetRuntimeQueries` mutates a shared object during render; concurrent/aborted renders can publish uncommitted query identity. Action dispatch casts the definition to `Record<string,unknown>` and `any`, then casts its result back.
- **Correction:** make definition generics preserve kind/options/action types end-to-end; replace stringly opaque ref slots with a typed per-item registration API updated in a committed effect (or use `useSyncExternalStore`). Separate query registration from imperative actions.
- **Tests:** StrictMode, interrupted/remount, options change and cleanup; compile-time `tsd`/type assertions rejecting wrong action props.
### WID-007 — P2 confirmed, regression-exposed: raw backend errors are exposed to users and assistive text
- **Location:** Custom API UI `component.tsx:561-576`; API `custom-api.ts:79-85`; several partial fallbacks use `error.message`.
- **Impact:** internal hostnames, request paths and implementation details can appear in tooltips/aria labels and logs.
- **Correction/Mantine:** return stable error codes plus redacted public messages; keep detailed causes server-side with correlation IDs. Render localized Alert/Notification details, never raw throwable text.
- **Tests:** errors containing credentials, query strings, private hosts and multiline backend bodies must not reach tRPC payload, DOM, accessibility tree or notifications.
### WID-008 — P2 confirmed, PR-introduced/regression-exposed: locale handling remains incomplete
- **Locations:** Beszel chart/tooltip dayjs formats, calendar event `HH:mm`, downloads `YYYY-MM-DD HH:mm`, timetable `HH:mm:ss`, weather day/month/time formats; `_inputs/widget-location-input.tsx:199` hardcodes `Intl.NumberFormat("en")`.
- **Also:** `beszel/_shared/disk-usage.tsx:41`, stats loading aria label, Network Controller `WAN`/`unknown` are English/raw accessible strings.
- **Correction:** centralize app-locale date/time/number/duration/status helpers; load canonical dayjs locale only at the boundary; use localized translation keys for aria/status. NumberFormatter is suitable for simple display but application helpers should own locale.
- **Tests:** at least `en-US`, `fr-FR`, `de-DE`, RTL locale, 12/24-hour preference and long translations, including accessibility names.
### WID-009 — P2 confirmed: layer and color tokens are inconsistent
- **Locations:** downloads z-index 1000 and rgba row colors; item title z-index 4; item menu/dynamic menu 10; media server white rgba; Indexer Manager hex colors; DNS Hole rgba palette; Umami hex tick colors.
- **Correction:** define shared semantic layer tokens relative to Mantine CSS z-index variables and semantic status/chart tokens respecting light/dark/forced colors. Do not replace intentional service branding.
- **Tests:** light/dark/forced-colors with nested overlays and tooltip ordering.
### WID-010 — P2 confirmed: responsive decisions are duplicated and under-tested
- **Location:** Network Controller Status `component.tsx:44,59-78` (open valid thread); Bookmark `component.tsx:94` recomputation (valid but low-impact thread); similar width/height literals across renderers.
- **Correction:** small typed breakpoint lookup helpers per family, memoized only for genuinely expensive layout. Prefer container-query Grid/SimpleGrid where structural. Do not cargo-cult `useMemo` for a cheap pure calculation; benchmark Bookmark before accepting the review suggestion.
- **Tests:** boundary tables for minimum/typical/wide/short/advanced sizes and 200% zoom.
### WID-011 — P2 confirmed: PR/docs overstate behavior and validation
- **Location:** PR body, board/getting-started copy “advanced preview in place,” validation numbers, and unqualified review pass.
- **Correction:** state 53 advanced renderers plus compact-only app; call manual focus an overlay only after WID-002/003; publish exact final-SHA check/test counts and disclose unsupported/live-untested states.
### WID-012 — P2 confirmed: focused automated coverage is far below the 54-widget/data-state claim
- **Evidence:** 42 widget-local spec files are helper-heavy; 20+ renderers have no focused spec. Six E2E files exist, but only board interactions and Custom JSX directly exercise this modernization. The earlier Custom JSX login wait failed once and current exact-route wait remains.
- **Correction:** add shared renderer contract harness plus family E2Es. Replace Custom JSX’s exact post-submit URL race with a shared login helper that waits for authenticated session/stable destination, then run it three times in CI.
### WID-013 — P2 confirmed: open review-thread state no longer matches code
| Thread | Classification |
| ------------------------------------------- | ------------------------------------------------------------------- |
| iframe complete URL | already fixed/false positive at current line: only host is rendered |
| system resources network gate | already fixed, outdated |
| RSS total error | already fixed: no-data error throws; stale data retained |
| bookmarks memoization | valid P3/P2-low; benchmark first |
| network-controller total error | already fixed, outdated |
| network-controller breakpoint consolidation | valid P2 maintainability |
| Paperless total error | already fixed, outdated |
Resolve the five stale threads with evidence; address or explicitly disposition the two live suggestions. Re-run review because CodeRabbit was paused during active development.
## Systemic remediation roadmap
1. **Security/authorization:** WID-001 and WID-005; add negative-role/protocol tests before merge.
2. **Interaction accessibility:** replace manual focus with portaled Mantine modal semantics (WID-002/003), then test nested widget content.
3. **Truthful data state:** build the shared query-state contract and migrate all 39 ambiguous sites; keep valid stale data and partial results.
4. **Typed interfaces:** replace `widgetStateRef` slot mutation and `any` context dispatch with committed typed registrations.
5. **Widget families:** media/downloads, infrastructure, networking/security, smart-home, analytics/calendar/feed, then Beszel; apply URL, state, locale, responsive and a11y contracts in that order.
6. **Design-system cleanup:** semantic layers/colors and restrained Mantine consolidation. Do not add Schedule or upgrade to 9.5 without a separate dependency/bundle decision.
7. **Docs/PR:** correct renderer count, behavior and exact validation evidence.
8. **Final proof:** frozen install on supported Node/pnpm; format, lint, full typecheck, affected and full unit tests, production and docs builds; Custom JSX three times; complete production-container E2E; desktop/tablet/mobile keyboard/touch/light/dark/reduced-motion/zoom/a11y-tree QA. Push nothing until all refer to one unchanged SHA; close every review thread and obtain required approval.
## Positive findings
- Timetable is the strongest security work in the PR: saved resource authorization, private-range rejection, DNS rebinding resistance through pinned lookup, redirect rejection, bounds and schemas are coherently tested.
- Bounded response helpers, Beszel bounded queue/cancellation, and partial-integration settlement materially reduce failure blast radius.
- RSS preserves stale data and distinguishes partial feed failure; Network Controller and Paperless now correctly escalate total failures.
- Item-scoped query matchers and tests address sibling-widget refresh isolation; global polling defaults are restricted to the widget namespace.
- Touch long-press has movement tolerance; focus scale guards prevent Infinity/NaN; reduced-motion and remount cleanup are present.
- Existing Homarr theme, Mantine components and shared UI vocabulary are generally preserved. Most proposed Mantine work is consolidation, not redesign.
## Validation evidence and uncertainty
- `MANTINE.md` was fetched from `https://mantine.dev/llms.txt` and passed byte-for-byte `cmp`; `llms-full.txt` stayed in `/tmp`.
- Context7 current Mantine docs were queried for overlay/focus, responsive/data-state/accessibility, charts and Schedule. Static 9.4.1 archives verified exact exports; Cascader, BulletChart and SunburstChart are 9.5-only.
- Local frozen install could not start because host Node/pnpm are below repository engine requirements. No dependencies, source files or machine toolchain were changed.
- Docker API access is denied, so production-container E2E could not be rerun locally. Hosted exact-head Fast gate and Container/E2E are green.
- `agent-browser` is not installed. Temporary `npx` execution was rejected by the environment’s security reviewer, so live exploratory QA/a11y-tree inspection was not performed.
- External integrations were not available; malformed/partial/permission combinations are static-analysis conclusions unless a focused test already exists.
- This review intentionally changes no production code and makes no commit, push, thread resolution or PR edit.
Finalization must re-query the PR head/checks/threads. If the head differs from the reviewed SHA, this report is a snapshot and the delta requires review before merge.
**Final refresh (2026-08-03):** head remained `96e92485a5fd45f3648cfac0453e614779c414ba`; all reported hosted checks remained successful; merge state remained `BLOCKED` with no approval decision; unresolved threads remained 7 of 34. The branch did not change during the audit.
+10 -4
View File
@@ -21,8 +21,14 @@ pnpm dev:docs
# Or from within this directory
pnpm dev
# Point the docs at a local PocketBase instance
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
```
The standalone docs use `WORKSHOP_API_URL` for PocketBase and `HOMARR_WEBSITE_URL` for the public site origin. The old
`WORKSHOP_URL` name is a deprecated one-release alias.
## Build
```bash
@@ -40,7 +46,7 @@ Uses oxlint and oxfmt (same as the rest of the monorepo):
```bash
pnpm lint # oxlint
pnpm format # oxfmt (check)
pnpm format -- --write # oxfmt (fix)
pnpm format:fix # oxfmt (fix)
```
## Typecheck
@@ -55,8 +61,8 @@ pnpm typecheck
docs/
├── getting-started/ # Installation guides, glossary, prerequisites
├── management/ # Boards, apps, users, integrations, settings, tasks, API
├── integrations/ # 55 integration guides (one folder each)
├── widgets/ # 42 widget guides (one folder each)
├── integrations/ # 56 integration guides (one folder each)
├── widgets/ # 43 widget guides (one folder each)
├── advanced/ # SSO, env vars, proxy, styling, CLI, development
└── community/ # FAQ, donate, license, translations, get-in-touch
```
@@ -93,7 +99,7 @@ pnpm verify:search
| Layer | Technology |
| -------------- | ------------------------------------------- |
| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) |
| Styling | Tailwind CSS 3 + Docusaurus Infima |
| Styling | Tailwind CSS 4 + Docusaurus Infima |
| Icons | `@tabler/icons-react` |
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
| Search | Algolia DocSearch |
+24
View File
@@ -0,0 +1,24 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/css/custom.css",
"baseColor": "mist",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "tabler",
"rtl": false,
"menuColor": "default",
"menuAccent": "subtle"
}
@@ -0,0 +1,41 @@
# Developer Docker CLI
The host-side developer CLI builds and runs local Homarr images and pull-request previews. Run it directly from a checkout with `pnpm dev:cli -- ...`; no shell-specific setup script is required. It is separate from the recovery CLI included in production images.
To install an optional `homarr` binary, run `pnpm dev:cli:install` and make your Go bin directory available on `PATH`. GitHub CLI authentication is only needed for pull-request features.
## Browse development images
```bash
pnpm dev:cli --
```
The main screen combines local `homarr:*` images with open pull requests and their GHCR preview status. Select an image to start or stop it, use `R` to build a remote pull request locally in the background or rebuild a local image, or use `p` to force a remote preview pull. Press `d` to switch to the instance dashboard and `d` again to return without losing the browser state. The explicit `dev` and `dash` subcommands start on either screen.
A locally built PR image is folded into its remote PR row only when its recorded revision exactly matches the current GitHub head SHA. The row is marked `◆ local`, and starting it prefers that local image without a registry pull. Outdated local builds remain separate rows. Navigation and dashboard switching remain available during background builds.
## Build images
```bash
pnpm dev:cli -- build <name>
pnpm dev:cli -- build --pr <number> [name]
```
The first command builds the current checkout as `homarr:<name>`. The pull-request form uses a depth-one clone in a temporary directory and defaults to `homarr:pr-<number>` when `name` is omitted.
## Rebuild images
```bash
pnpm dev:cli -- rebuild <name>
```
Images built by this CLI record their source checkout, revision, and optional pull-request number. Rebuild uses that provenance to select the original checkout or create a fresh temporary pull-request checkout.
## Run images
```bash
homarr run <name>
homarr run --pr <number>
```
Add `--detach` to run in the background, `--demo` to enable demo data, or repeat `--env KEY=VALUE` to provide additional environment variables.
@@ -8,62 +8,121 @@ tags:
sidebar_position: 1
---
## Setting up your development environment
This guide will help you set up your development environment to start contributing to homarr.
## Set up your development environment
### Prerequisites
- **Node.js (lts)**: You can download it from the [official website](https://nodejs.org/).
- **pnpm (latest)**: You can install it by running `npm install -g pnpm`.
- **Node.js 24.18 or newer**: You can download it from the [official website](https://nodejs.org/).
- **Corepack**: Run `corepack enable` so the repository selects its pinned pnpm 11 version.
- **Git**: You can download it from the [official website](https://git-scm.com/).
- **Docker or Docker Desktop**: You can download it from the [official website](https://www.docker.com/).
- **A GitHub account**: You can create one on the [official website](https://github.com/).
- **Docker or Docker Desktop**: Required for the Redis service and the developer Docker CLI.
- **Go 1.25+**: Required only for the developer Docker CLI. You can download it from the [official website](https://go.dev/).
- **GitHub CLI**: Required only for pull-request image features. Install it from the [official website](https://cli.github.com/), then authenticate with `gh auth login`.
### Setting up the project
The versions in the root `package.json` are the supported baseline. Let Corepack select the repository version instead
of installing an unrelated global pnpm:
1. Fork the repository by clicking on the "Fork" button on the top right corner of the repository page.
```sh
corepack enable
corepack install
pnpm install --frozen-lockfile
```
![Fork button](./img/fork.png)
### Run the Homarr application
2. Clone the repository to your local machine with `git clone`. You can find the url to use by clicking on the "Code" button on the top right corner of your forks repository page.
1. Copy `.env.example` to `.env`.
2. Set `DB_URL` to an absolute writable SQLite file path.
3. Keep Redis and the optional local databases running in one terminal.
4. Apply migrations and start Next.js in another terminal.
![Clone button](./img/clone.png)
```sh
cp .env.example .env
```
3. Change directory to the project folder with `cd homarr`.
Terminal 1:
4. Install the dependencies with `pnpm install`.
```sh
pnpm docker:dev
```
5. Start redis by running `pnpm run docker:dev`. This will run a docker-compose file with a redis service.
Terminal 2:
6. Copy the `.env.example` file to `.env` and replace `DB_URL` with the full path you want to use for the sqlite database file. For example `DB_URL='C:\\Dev\\homarr\\packages\\db\\db.sqlite'`
```sh
pnpm db:migration:sqlite:run
pnpm dev
```
7. Run the migrations to create the sqlite db file with `pnpm run db:migration:sqlite:run`.
The infrastructure command remains in the foreground. Open `http://127.0.0.1:3000`; the first user created through
Homarr becomes the instance administrator.
Use `pnpm docker:dev:up` instead when you only need Redis in the background. Running the migration also seeds the
default data; use `pnpm db:seed` to seed it explicitly later.
8. Start the development server with `pnpm dev` and wait for a message that says `✓ Ready in 10.7s`
### Run PocketBase and documentation
9. Open your browser and navigate to [http://localhost:3000](http://localhost:3000). (Loading the page might take up to 2 minutes the first time)
Start PocketBase in another terminal:
### Useful npm scripts
```sh
cp apps/workshop/.env.example apps/workshop/.env
docker compose --env-file apps/workshop/.env -f apps/workshop/docker-compose.yml up --build workshop
```
#### During development
Then start Docusaurus in a third terminal:
- `pnpm dev`: Start the development server.
- `pnpm cli`: Run cli commands.
- `pnpm db:migration:sqlite:run`: Run the migrations to create or update the sqlite db file.
- `pnpm db:migration:sqlite:generate`: Generate a new migration file for the sqlite db. Also works with `mysql`.
- `pnpm db:studio`: Open a visual studio for the database.
- `pnpm package:new`: Create a new package in the monorepo.
```sh
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
```
#### Checking code quality
PocketBase runs on port 8090 and Docusaurus on port 3003. Stopping the services preserves the PocketBase volume.
- `pnpm lint`: Run the linter to check for code quality issues.
- `pnpm format:fix`: Run the formatter to fix code style issues.
- `pnpm test`: Run the tests. You can also use `pnpm test:ui` to get a UI for the tests.
- `pnpm typecheck`: Run the typescript type checker.
Open `http://127.0.0.1:8090/_/` and create the first PocketBase superuser. The Workshop UI is available at
`http://127.0.0.1:3003/workshop`.
#### Building the project
Point Homarr at the local services with:
- `pnpm build`: Build the project for production.
- `docker build -t homarr .`: Build a docker image for the project.
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:latest`: Run the created docker image.
```dotenv
HOMARR_WEBSITE_URL=http://127.0.0.1:3003
WORKSHOP_API_URL=http://127.0.0.1:8090
WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop
```
Restart `pnpm dev` after changing `.env`. GitHub OAuth is optional locally. See
[`apps/workshop/README.md`](https://github.com/homarr-labs/homarr/blob/HEAD/apps/workshop/README.md) for OAuth,
Workshop moderator promotion, logs, tests, backup, restore, stopping, and intentional volume reset.
### URL testing
Docusaurus reads `HOMARR_WEBSITE_URL` and `WORKSHOP_API_URL` when it starts or builds. Homarr also reads
`WORKSHOP_WEB_URL` when its server starts, so these values do not rely on `NEXT_PUBLIC_*` build variables. Test a
separate API origin before release:
```sh
HOMARR_WEBSITE_URL=http://127.0.0.1:3003 \
WORKSHOP_API_URL=http://127.0.0.1:8090 \
WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop \
pnpm dev:docs
```
### Useful commands
- `pnpm dev` — Next.js on port 3000.
- `pnpm dev:benchmark` — authenticated-board benchmark; readiness-only `--smoke` runs are not performance claims.
- `pnpm dev:cli -- dev` — browse and run local images and remote pull-request images.
- `pnpm dev:cli -- build <name>` — build the current checkout as `homarr:<name>`.
- `pnpm dev:cli -- build --pr <number>` — build a pull request from a temporary checkout.
- `pnpm dev:cli -- rebuild <name>` — rebuild an image from its recorded checkout or pull request.
- `pnpm dev:cli:install` — optionally install the developer CLI as a `homarr` binary.
- `pnpm dev:docs` — Docusaurus only on port 3003.
- `pnpm docker:workshop` — local PocketBase through Docker Compose.
- `pnpm docker:dev:up` — Redis development service in the background.
- `pnpm cli` — Homarr operations CLI.
- `pnpm db:migration:sqlite:run` — create or update a SQLite database.
- `pnpm db:seed` — seed the default database data explicitly.
- `pnpm db:studio` — inspect the database.
- `pnpm format` / `pnpm format:fix` — check or apply oxfmt.
- `pnpm lint` / `pnpm lint:fix` — check or apply oxlint.
- `pnpm typecheck` — typecheck all workspaces.
- `pnpm test` — unit and contract suites.
- `pnpm test:workshop` — disposable Workshop integration suite.
- `pnpm build` — production monorepo build.
- `docker build -t homarr .` — build a production Docker image.
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:<name>` — run it.
@@ -25,13 +25,17 @@ This is especially useful for [docker secrets](https://docs.docker.com/engine/sw
Using the `PUID` and `PGID` will require you to set the correct permissions on the mounted volumes and if used the docker socket. See more in the [Running as a different user](/docs/advanced/running-as-different-user) documentation.
:::
| Environment Variable | Description | Possible values | Default |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------- |
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
| `NO_EXTERNAL_CONNECTION` | Disables some requests that need internet connection | `true` or `false` | `false` |
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
| Environment Variable | Description | Possible values | Default |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------- |
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, and RSS feeds | `true` or `false` | `false` |
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
| `HOMARR_DISABLE_IPV6` | Disables the IPv6 listener of the internal nginx proxy, even when the host supports IPv6. IPv6 is normally disabled automatically when the host has no IPv6 configured. See [#4596](https://github.com/homarr-labs/homarr/issues/4596) | `true` or `false` | `false` |
| `HOMARR_WEBSITE_URL` | Main Homarr documentation/site base used by runtime links. Must be an absolute HTTP(S) URL. | HTTP(S) URL | `https://homarr.dev` |
| `WORKSHOP_API_URL` | PocketBase API used by Homarr and standalone documentation. | HTTP(S) URL | `HOMARR_WEBSITE_URL` |
| `WORKSHOP_WEB_URL` | Public Workshop page opened from Homarr. | HTTP(S) URL | `HOMARR_WEBSITE_URL/workshop` |
## Authentication
@@ -51,11 +55,11 @@ A random secret can be generated by using the following command: `openssl rand -
## Docker
| Environment Variable | Description | Possible values | Default |
| --------------------- | ----------------------------------------------- | -------------------------------------------------------------------- | ------- |
| `DOCKER_HOSTNAMES` | Comma seperated list of hostnames to connect to | For example `localhost,docker.example.com` | - |
| `DOCKER_PORTS` | Comma seperated list of ports to connect to | For example `2375,2376` | - |
| `DOCKER_SOCKET_PATHS` | Comma seperated list of paths to sockets | For example `/var/run/docker.sock,/run/user/1000/podman/podman.sock` | - |
| Environment Variable | Description | Possible values | Default |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- |
| `DOCKER_HOSTNAMES` | Comma-separated list of hostnames to connect to | For example `localhost,docker.example.com` | - |
| `DOCKER_PORTS` | Comma-separated list of ports to connect to | For example `2375,2376` | - |
| `DOCKER_SOCKET_PATHS` | Comma-separated list of mounted Docker-compatible socket paths. See the [Docker and Podman integration guide](/docs/integrations/docker) | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
## Database
@@ -105,6 +109,12 @@ External redis is currently not supported with Proxmox Community Scripts
The advanced deployments environment variables should only be used if you know what you are doing.
:::
| Environment Variable | Description |
| ------------------------ | -------------------------------------------------- |
| `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts |
| Environment Variable | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DB_MIGRATIONS_DISABLED` | Disable db migrations. For example for helm charts. |
| `WORKSHOP_PUBLIC_ORIGIN` | Origin used in PocketBase-generated links and email. It must be set explicitly in production and must match the externally reachable Workshop origin. |
| `PB_ALLOWED_ORIGINS` | Browser origins accepted by PocketBase. Use `*` for the central public community so arbitrary self-hosted Homarr instances can use the API and OAuth; private staging may use a comma-separated origin allowlist. |
All public URL variables reject non-HTTP protocols, embedded credentials, query strings, and fragments, and normalize
trailing slashes when Homarr or Docusaurus starts. The Docusaurus-only `WORKSHOP_URL` name is accepted as a deprecated alias for
`WORKSHOP_API_URL` for one release. `WORKSHOP_API_URL` takes precedence.
+9 -3
View File
@@ -12,7 +12,7 @@ tags:
Icons in Homarr are automatically requested from multiple sources:
- [Dashboard Icons](https://dashboardicons.com/) - Our recommended source with over 1888+ curated, high-quality icons
- [Dashboard Icons](https://dashboardicons.com/) - Our recommended source with more than 1,888 curated, high-quality icons
- [selfh.st icons](https://github.com/selfhst/icons)
- [Simple icons](https://github.com/simple-icons/simple-icons)
- [Tabler icons](https://tabler-icons.io/)
@@ -21,7 +21,7 @@ Icons in Homarr are automatically requested from multiple sources:
- **Local icons**: automatically fetched from the [medias feature](/docs/management/media/)
Using these icon sources, Homarr provides a total collection of over 11,000 icons at your fingertips.
The icon picker is a fast searcher that helps you to find your desired icon simply by entering a search term.
The image picker searches this collection after a short typing delay. When a name is available, such as an app or search engine name, the picker uses it as the initial search. Results show your locally uploaded images first, followed by SVG icons and then other image formats.
### Finding Missing Icons
@@ -31,7 +31,13 @@ If you can't find the icon you're looking for in Homarr's built-in picker, we st
Icons that are high quality and printable ([SVG](https://wikipedia.org/wiki/Scalable_Vector_Graphics)) are marked with a red badge "SVG" at the top right.
You can clear the selected image with the clear button or the usual keyboard shortcut for deleting a line (Command+Backspace on macOS or Control+Backspace on other platforms). Clearing the field does not restore the previous image.
### Using external icons
If you don't find the icon you're looking for on Dashboard Icons or wish to use a different source, you can simply enter a URL that points to an icon.
It is important that the URL directly points to the file without any advertisements or elements around it.
Direct HTTP(S) image URLs are used as entered instead of being searched in the built-in repositories. The picker previews the image and shows whether it loaded successfully. It is important that the URL directly points to the file without any advertisements or elements around it.
### Uploading a local image
Users with media upload permission can select **Upload image** next to the field. After the upload finishes, the new local image is selected immediately and is available at the top of future searches.
@@ -6,16 +6,40 @@ tags:
Homarr has the following keyboard shortcuts:
| Shortcut | Description |
| ----------- | -------------------- |
| `MOD` + `K` | Opens the search bar |
| `MOD` + `J` | Toggles Dark Mode |
| `MOD` + `E` | Toggles Edit Mode |
| Shortcut | Description |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `MOD` + `K` | Opens the search bar |
| `MOD` + `J` | Toggles Dark Mode |
| `MOD` + `E` | Toggles Edit Mode |
| Hold `Shift` for 500 ms over a supported widget | Opens its temporary advanced view |
| `Shift` + `Enter` on a supported widget | Opens its advanced view until it is closed |
| `Esc` | Stops keyboard tile editing, cancels an active pointer or touch move/resize, or closes an open advanced view or board menu |
## Board editing
`MOD` + `E` enters edit mode. Homarr keeps the current board visible while the editor prepares; movement and resizing become available when it is ready.
When a tile is focused in edit mode:
| Shortcut | Description |
| -------------------- | --------------------------------------------------------------------------- |
| `Enter` or `Space` | Starts or stops keyboard editing |
| Arrow keys | Moves the focused tile within its current canvas, rail, or Container |
| `Shift` + Arrow keys | Resizes from the tile's lower-right corner: Left/Up shrink; Right/Down grow |
| `Esc` | Stops keyboard editing without undoing its changes |
Keyboard moves and resizes apply immediately. To move an item between a canvas, rail, or Container, drag it there or use **Move / resize item** for precise numeric placement.
Commands at a grid boundary or at the tile's minimum size are ignored and announced instead of moving or shrinking the tile.
On touch screens, press and hold a tile before moving it; a quick tap does not start a drag. Resize handles accept direct touch input. During a pointer or touch move or resize, `Esc` cancels the interaction and restores the layout from before it started.
:::tip
Here `MOD` refers to the modifier key, which is `Ctrl` on Windows and `Cmd` on macOS.
Advanced view is available outside edit mode. Widgets without a distinct advanced experience adapt to their normal
dimensions instead. A progress ring follows the pointer while the temporary advanced view is preparing to open.
:::
:::note
-1
View File
@@ -49,7 +49,6 @@ services:
traefik.http.routers.homarr.rule: Host(`your.internal.dns.address.here.com`)
traefik.http.routers.homarr.entrypoints: websecure
traefik.http.routers.homarr-secure.app: homarr
traefik.http.apps.homarr.loadbalancer.server.port: 7575
networks:
proxy:
@@ -128,6 +128,8 @@ Homarr supports multiple authentication options, from internal userbase (credent
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
To let an existing credentials user also sign in with OIDC, set `AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING=true`. On the first OIDC sign-in, Homarr links a credentials user with the same email address instead of creating a second user. The user keeps their password login, local username and profile, groups, permissions, boards, settings, and API keys. OIDC profile and group claims do not overwrite the credentials-owned data. Homarr requires the OIDC profile to contain `email_verified: true` while this option is enabled. Only enable this when your OIDC provider verifies email addresses, because a matching email could otherwise be used to take over an existing account.
<details>
<summary>
Example Setup
@@ -205,6 +207,7 @@ Homarr supports multiple authentication options, from internal userbase (credent
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
| ``AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING`` | Link an OIDC identity to an existing credentials user with the same email. The credentials login and existing user data are preserved. Requires the OIDC profile to contain `email_verified: true`. | false |
| ``AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD`` | Override method for the token endpoint authentication. Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt` and `none`. | client_secret_basic |
### Permission System
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 38 KiB

+2 -2
View File
@@ -85,8 +85,8 @@ To do this, enter the board settings:
![](./img/enter-board-settings.png)
Scroll down a bit and click on custom CSS.
Here you can enter your CSS code, which will be applied on the respective board page.
Scroll to the **Custom CSS** section on the board settings page. It is part of the unified settings form; enter your CSS
there and save the board settings to apply it to that board.
![](./img/custom-css-setting.png)
+13 -3
View File
@@ -27,11 +27,21 @@ So make sure to manually whitelist this address.
### Can I add my own widgets? Do I need programming skills?
Yes. Administrators can create [Custom Widgets](/docs/management/custom-widgets) that read an HTTP API and render the response with a built-in display type. These display types require no programming.
Yes. A [Custom Widget](/docs/management/custom-widgets) can read a public or private HTTP API.
Custom JSX is available when you need a more specific layout or an interactive control. It uses a restricted declarative JSX interpreter with a classified Mantine component set. Network operations are declared as named, same-origin requests and are checked against board permissions on the server. It does not execute arbitrary JavaScript or expose browser `fetch`.
It can show responsive Mantine JSX. It can also use widget options and actions with board permission checks.
You should be comfortable reading JSON and JSX to author complex templates. The management workbench includes examples, diagnostics, a component reference, API response inspection, and a prompt you can copy into an AI assistant. Always review imported templates and request capabilities before saving them.
Custom JSX v2 is the only Custom Widget format. Write the JSON and JSX in the workbench.
You can also use the AI copy prompt. An MCP agent can validate and preview a definition before it saves the definition.
The workbench reads a pasted AI response. It shows request capabilities and redacted diagnostics.
A template cannot import packages or run arbitrary JavaScript. It cannot call browser `fetch`.
Named requests use the Homarr server. The server applies board permissions and the network policy.
Before installation, examine the origins, methods, permissions, and actions. Use the same review before publication.
### Is it secure to expose Homarr to the web?
@@ -10,10 +10,16 @@ import imageStartFromScratchStepServerSettings from "./img/onboarding-from-scrat
import imageStartFromScratchStepAdminUser from "./img/onboarding-from-scratch-step-admin-user.png";
import imageStartFromScratchStepExternalAdminGroup from "./img/onboarding-from-scratch-step-external-admin-group.png";
import imageStartFromScratchStepFinish from "./img/onboarding-from-scratch-step-finish.png";
import manageBoardHeaderEditMode from "./img/manage-board-header-edit-mode.png";
import manageBoardHeaderAddItem from "./img/manage-board-header-add-item-menu.png";
import manageBoardHeaderChooseItem from "./img/manage-board-header-choose-item.png";
import manageBoardNoApp from "./img/manage-board-no-app.png";
import currentKeyboardTileEditing from "../management/boards/img/keyboard-tile-editing.png";
import boardDesktopCurrent from "./img/onboarding-board-desktop-current.png";
import boardEditModeCurrent from "./img/onboarding-board-edit-mode-current.png";
import addContentMenuCurrent from "./img/onboarding-add-content-menu-current.png";
import chooseItemCurrent from "./img/onboarding-choose-item-current.png";
import editItemCurrent from "./img/onboarding-edit-item-current.png";
import boardsHomeMobileMenu from "./img/onboarding-boards-home-mobile-menu.png";
import moveResizeMenuCurrent from "./img/onboarding-move-resize-menu-current.png";
import currentBoardMobileView from "./img/onboarding-board-mobile-current.png";
import manageExportOldHomarr from "./img/old-homarr-export-data.png";
import manageImportOldHomarrDropzone from "./img/old-homarr-import-dropzone.png";
import manageImportOldHomarrChooseData from "./img/old-homarr-import-choose-imported-data.png";
@@ -29,12 +35,12 @@ Congratulations on installing Homarr. Now it's time to get yourself familiar wit
## Onboarding
The onboard will show the first time you start Homarr.
The onboarding will show the first time you start Homarr.
This process will help you with your first steps and adding some initial data, such as the administrator account.
:::note
If you see this screen repeatitly after you restarted the Homarr container, your Docker mounting paths may be missing,
If you see this screen repeatedly after you restart the Homarr container, your Docker mounting paths may be missing,
incorrect or read-only. Read [our installation instructions](/docs/getting-started/installation/docker) again to fix this issue.
:::
@@ -68,7 +74,7 @@ The name of the group must match exactly the name of the group in the provider.
/>
Next, you can configure some basic settings for Homarr. We recommend to leave all settings at default for most users.
Here you can also disable analytics. Please note that our analytics are completly anonymous and is fully compliant with GDPR and PECR.
Here you can also disable analytics. Please note that our analytics are completely anonymous and fully compliant with GDPR and PECR.
<img
src={imageStartFromScratchStepServerSettings}
@@ -126,18 +132,20 @@ It is important that you first migrate correctly using [the list of breaking cha
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open `Migrate to 1.0`.
2. Check the contents that you want to export. We recommend you to export all data.
3. Click on the export data button
4. Save the ZIP file, that is being downloaded, on your device. If you selected "integrations" or "users", you also get an encryption / decryption key.
Copy it and do not loose it!
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose `Import from Homarr before 1.0`
4. Save the downloaded ZIP file on your device. If you selected "integrations" or "users", you also get an encryption / decryption key.
Copy it and keep it safe; you need it later in the import flow.
5. Go to your new Homarr 1.0 or later instance and start the onboarding. Choose `Import from Homarr before 1.0`.
6. Drag and drop your previously downloaded ZIP archive into the file upload: <br/><img src={manageImportOldHomarrDropzone} style={{ maxWidth: 500 }} />
7. Next, choose the data that you want to import and how to import it. Since screen sizes were removed in 1.0, we create a new board for every screen size by default. We recommend you to only keep the best layout and adjust it later in 1.0.0 - but you can also import all of them and pick later.<br/><img src={manageImportOldHomarrChooseData} style={{ maxWidth: 500 }} />
7. Next, choose the data that you want to import and how to import it. Old Homarr screen sizes are imported as responsive layouts on the same board: the narrowest becomes **Mobile**, the widest becomes **Base**, and layouts between them become custom layouts. Homarr normalizes duplicate breakpoints automatically, so you can review the imported board in Board Settings and adjust the layouts there.<br/><img src={manageImportOldHomarrChooseData} style={{ maxWidth: 500 }} />
For example, legacy `sm`, `md`, and `lg` layouts at `0px`, `800px`, and `1400px` become Mobile, a custom layout, and Base. The imported board keeps each layout's positions, names, and column counts where possible so you can review the result before saving. If a board has one saved layout, Homarr keeps it as Base and generates Mobile from it; a board without saved layouts receives Mobile at `0px` with 3 columns and Base at `768px` with 10 columns.
8. If you previously exported integrations or users, you will be asked to decrypt the data safely using your obtained encryption token. Paste into the modal and confirm your action.<br/><img src={manageImportDecryptionToken} style={{ maxWidth: 500 }} />
9. Next, you can [follow the same setup as when starting from scratch](#starting-from-scratch). After you completed the onboarding, all of your data will be migrated and available.
## Creating your first board
In Homarr, you organize your apps and widgets on boards.
Boards can be organized using the interactive drag and drop.
Boards use fixed-size tiles so the same widget keeps the same proportions on every screen.
Drag, resize, and section controls are available in edit mode.
Let's create your first board by navigating here: `Manage` > `Boards` > `New board`:
![](./img/manage-create-board-button.png)
@@ -146,9 +154,25 @@ Public boards are also accessible for users, that are not logged in ([see access
![](./img/manage-create-board-modal.png)
Congratulations - you've just created your first board. You can see boards, that you have access to, on this page:
Congratulations - you've just created your first board. You can see the boards you have access to on this page:
![](./img/manage-view-board.png)
<figure>
<img
src={boardsHomeMobileMenu}
alt="Boards management page showing a Home badge and separate desktop and mobile home-board menu entries"
style={{ maxWidth: "100%" }}
/>
<figcaption>The Boards page selects the board opened from Home separately for desktop and mobile.</figcaption>
</figure>
The board marked **Home** is the board opened from the desktop Home route. **Set as your mobile board** selects which board
opens on phones. This is separate from the **Mobile** layout: the home-board setting chooses a board, while **Mobile** and
**Base** are responsive layouts inside that board. A phone can therefore open a different home board and still use that
board's Mobile layout.
New boards start with **Mobile** and **Base** responsive layouts automatically, so you can edit the board for phones and
desktops without creating extra layouts first. Mobile starts at `0px` with 3 columns and Base at `768px` with 10 columns.
Each layout keeps its own tile positions and sizes.
## Add your first app to Homarr
@@ -172,32 +196,63 @@ After you created the app, you'll see in on the apps page:
Let's add your freshly created app, from the previous step, to the board.
To do this, edit your previously created board by clicking on the big `Open board` button:
![](./img/manage-view-board.png)
<figure>
<img src={boardDesktopCurrent} alt="Current Homarr board opened from the Boards page" style={{ maxWidth: "100%" }} />
<figcaption>Open a board to view its dashboard, then enter edit mode to arrange its content.</figcaption>
</figure>
Here you have the following options in the header:
![](./img/manage-board-header.png)
- **Search**: Here you can search for data. You can search for integrations, boards, actions, sites and much more. Some actions may be context dependent (eg. will only show when certain widgets are on your page)
- **Edit mode**: Clicking this button will toggle the edit mode. In this mode you can edit the contents of your board.
- **Edit board**: Clicking this button will toggle edit mode. In this mode you can edit the contents of your board.
- **Settings**: Clicking this button will redirect you to a settings page where you can adjust the board settings (page title, colors, ...).
- **Board switcher**: Use this button to open another board that you can access.
- **Profile**: This avatar will be visible on every page. Clicking it will show a menu with actions.
When entering the edit mode, you'll get some additional buttons to work with:
<img src={manageBoardHeaderEditMode} alt={"Screenshot of buttons in the edit mode"} width={200} />
<figure>
<img
src={boardEditModeCurrent}
alt="Current Homarr board editor with tile controls visible"
style={{ maxWidth: "100%" }}
/>
<figcaption>The current editor keeps the board visible while its editing controls are available.</figcaption>
</figure>
- **Add item**: Click this button to add new elements to your page.
- **Exit edit mode**: Click this btuton to save your changes and exit edit mode.
- **Add board content**: Click this button to create a new item, app, integration, or Container.
- **Save board**: Click this button to save your changes and leave edit mode. If you leave with unsaved changes, Homarr asks whether to discard them.
To continue, click the option "New Item":
Outside edit mode, right-click a widget for its live status, refresh, settings, quick options, and supported actions.
Hold `Shift` while hovering a widget that supports advanced view to expand it in place while the rest of the board
stays visible but dimmed. Release `Shift` to close it. You can also focus that widget and press `Shift` + `Enter` to
keep its advanced view open until you close it. Widgets without a distinct advanced experience adapt in place.
<img src={manageBoardHeaderAddItem} alt={"Screenshot of the add item menu"} width={200} />
To continue, click **New item** in the add-content menu. New content is placed at the first available position on the main
canvas; it is not inserted at a clicked empty cell or directly into a rail or Container:
<figure>
<img
src={addContentMenuCurrent}
alt="Current add board content menu with item, app, integration, and Container actions"
style={{ maxWidth: "100%" }}
/>
<figcaption>The add-content menu provides the four supported ways to place new content on the board.</figcaption>
</figure>
Next, you can choose what item you'd like to add.
We will discuss the other options in the following sections - for now choose "App" to continue.
<img src={manageBoardHeaderChooseItem} alt={"Screenshot of choosing the app item in the modal"} width={300} />
<figure>
<img
src={chooseItemCurrent}
alt="Current item picker showing the App card and other item types"
style={{ maxWidth: "100%" }}
/>
<figcaption>Choose the App item from the current picker to add an app tile.</figcaption>
</figure>
As soon as you chose the app, the modal will close and you'll get the following item on your board:
@@ -206,28 +261,57 @@ As soon as you chose the app, the modal will close and you'll get the following
This is expected, because you haven't told Homarr what app it should show in this item.
To configure this, click on the small 3 dots at the top of the item in the edit mode and choose "Edit item" in the menu.
![](./img/manage-board-edit-app.gif)
<figure>
<img
src={moveResizeMenuCurrent}
alt="Current board tile menu showing Edit item and Move or resize item"
style={{ maxWidth: "450px" }}
/>
<figcaption>The tile menu contains the item editor and the separate move-and-resize tools.</figcaption>
</figure>
In the modal you can edit the app that you want to display:
![](./img/manage-board-choose-app.png)
<figure>
<img src={editItemCurrent} alt="Current Edit item modal for an App tile" style={{ maxWidth: "620px" }} />
<figcaption>Use the Edit item modal to choose the app and configure how its tile is displayed.</figcaption>
</figure>
As soon as you save your changes, the tile on the board will refresh and display your app.
Using the small bidirectional arrow at the bottom right you can resize the element.
Start pressing with your mouse button down, move your mouse to resize and release your finger as soon your you're happy with the size.
Resize from any visible edge or corner handle. For a Container, activate its top-left grip before using its resize handles.
Press and hold a handle, move the pointer, and release it when the tile has the size you want.
![](./img/resize-item.gif)
To move a tile in edit mode, press and hold its inert content area, move it to the desired position, and release it.
Widget content is disabled while editing so its controls do not intercept the drag; use the tile's settings button for edit actions.
The tile snaps to the grid. A single compatible collision may swap the two items when both resulting placements fit;
otherwise the collision chain reflows neighboring tiles automatically.
To move your item to a different position on the board, simply hold the mouse button anywhere on the item and move with the mouse to the desired position.
As soon as you release your finger, the item will snap in place in the grid.
Every `1 × 1` tile has a logical content size of `200 × 200` pixels.
Larger tiles use multiples of that fixed unit, with a consistent gap between cells.
Homarr scales the complete board uniformly, like zooming a page.
The complete canvas always fits the available width without horizontal board scrolling. Below 100%, Homarr preserves the
widget's readable typography and control density instead of shrinking each widget independently.
It does not squeeze or reflow the content of individual widgets, so a widget keeps the same design and proportions across screen sizes.
![](./img/move-item.gif)
On a narrow screen, Homarr uses the **Mobile layout** for the selected board and stacks the board content vertically while
preserving each widget's fixed logical proportions:
<figure>
<img
src={currentBoardMobileView}
alt="Homarr's Mobile layout in a 390-pixel-wide viewport, with tiles scaled to fit and stacked vertically"
style={{ maxWidth: "390px" }}
/>
<figcaption>
At phone width, the selected board uses its Mobile layout and fits the complete canvas to the viewport.
</figcaption>
</figure>
:::note
You cannot move items freely on the vertical axis.
We always align all items to the top of the page to simply organization.
To move items to the bottom, you must add more items or resize items.
Viewing a board initially uses the lightweight, non-interactive layout.
For users with edit access, Homarr prepares drag-and-drop and resize controls in the background after the board becomes interactive.
If you enter edit mode before that finishes, the existing board stays visible until the editor is ready.
:::
@@ -238,14 +322,17 @@ This helps you to explore the capabilities of Homarr before you build your board
:::
Users on touch or mobile devices may not be able to drag and drop or move items.
They can use the "Move / resize item" option in the edit menu of items instead:
Keyboard users can focus a tile, press Enter or Space, then use arrow keys to move and Shift plus arrow keys to resize
within its current canvas, rail, or Container. Press Escape to stop keyboard editing. Touch dragging starts after a short
hold of about `200 ms`; resize handles accept direct touch input. During a pointer or touch move or resize, press Escape
to cancel and restore the previous layout. For cross-grid moves or precise numeric controls, choose **Move / resize item**
from the tile's edit menu.
Keyboard commands at a grid boundary or minimum tile size are ignored and announced; they do not move or shrink the tile.
![](./img/manage-board-move-resize-item-menu.png)
<img src={currentKeyboardTileEditing} alt="A board tile focused for keyboard editing" style={{ maxWidth: "100%" }} />
Be sure to save your changes by exiting edit mode!
![](./img/exit-edit-mode.png)
Open **Move / resize item** from the tile menu to move the tile to another section or rail and enter precise numeric
coordinates and dimensions. After making changes, click **Save board** to persist them and leave edit mode.
## Adding your first integration
@@ -280,7 +367,14 @@ Let's add your integration, that you have configured in the section above, as a
First, navigate back to your board that you want to add it to.
Next, enter edit mode again and add another item to your board:
![](./img/manage-board-header-add-item-menu.png)
<figure>
<img
src={addContentMenuCurrent}
alt="Current add board content menu with item, app, integration, and Container actions"
style={{ maxWidth: "100%" }}
/>
<figcaption>Use the same add-content menu to place an integration widget on the board.</figcaption>
</figure>
Instead of choosing an app, let's choose `System Health Monitoring` this time.
Click on "Add to board":
@@ -307,47 +401,38 @@ Editing a widget will trigger a data update and data will soon appear:
![](./img/widget-system-health.png)
As with every change, don't forget to save your changes using the exit edit mode button:
As with every change, click **Save board** to persist your board changes and leave edit mode.
![](./img/exit-edit-mode.png)
## Organizing items with Containers
## Grouping your items
Containers are resizable areas for apps, widgets, and nested Containers.
The same Container works on the main canvas, inside a fixed rail, or inside another Container.
Each Container can configure:
As the last step, let's organize your freshly created items using two different options.
You can choose either or both of them on your boards.
- Its label and whether that label is visible.
- Allow or disable collapsing.
- Show or hide **Open all in new tabs**.
- Change its border color and custom CSS classes.
### Using dynamic sections
Open the add-content menu and choose **New container**.
In edit mode, move and resize it like any other item, then drag items into or out of it.
Nested content stays within the Container's bounds.
While resizing, the solid outline follows the pointer continuously and the dashed grid preview shows the size that will be saved on release.
Dynamic sections are a simple, resizable visual group to group items.
### Collapsing and opening apps
![](./img/create-dynamic-section.png)
Users can collapse an eligible Container like an accordion in view mode without changing its saved expanded size.
The full-width header becomes the compact expansion bar, so the half-row state has no empty pill-shaped spacer.
Expanding it restores its bounds and makes room for its contents.
If **Open all in new tabs** is enabled, the Container menu can open every app link it contains.
After adding a dynamic section, you can simply drag and drop items into the section:
### Fixed rails
![](./img/dynamic-section.gif)
### Using categories
Categories are more advanced and complicated than dynamic sections.
To create a category, open the add item modal and choose "new category":
![](./img/add-category.png)
Every category must have a name. After you click "Add category", you will get a new item on your board:
![](./img/category.gif)
You can drag and drop items in to this category in the edit mode.
Categories will have a big space before and after them in the edit mode to
enable you to drag and drop items outside the category.
You can also collapse or open categories in edit mode to facilitate your editing process.
:::note
A current limitation of categories is, that they will always have a width of 100%.
You cannot reduce their horizontal width.
:::
Open the board's **Layout** settings to enable the left or right rail for each responsive layout.
Choose one, two, or three columns with the slider and use the preview to check the remaining canvas width.
The rail reserves those columns from the dashboard, so Homarr moves canvas items into the next available positions.
Rail content stays visible while the page scrolls and remains editable.
Each responsive layout keeps its own rail widths, item positions, and item sizes.
## Guided tours
+10 -10
View File
@@ -1,13 +1,13 @@
# Glossary
In the glossary, you'll learn the different components and their names of Homarr.
This glossary explains Homarr's main components and the terms used for them.
| Term | Definition |
| --------------- | -------------------------------------------------------------------------------------------------------------- |
| Apps | An piece of software that you or a third party hosts (eg. Google) |
| Integration | A connection to a service outside of Homarr |
| Dynamic section | Resizeable area where items on your board can be placed and grouped |
| Widget | A tile that uses your integrations to display data or enables you to interact with with them |
| Edit mode | Temporary state that you can enter where you can drag and drop, edit or customize your board |
| Board | Customizable page which can be edited using the edit mode. This is where apps, sections and widgets are placed |
| Tasks | Workloads that are performed in the background - even if there is no user present |
| Term | Definition |
| ----------- | --------------------------------------------------------------------------------------- |
| Apps | A piece of software that you or a third party hosts (for example, Google) |
| Integration | A connection to a service outside of Homarr |
| Container | A resizable area that can hold apps, widgets, and nested Containers |
| Widget | A tile that uses your integrations to display data or enables you to interact with them |
| Edit mode | A temporary state where you can drag, resize, edit, or customize your board |
| Board | A customizable page where apps, widgets, and Containers are arranged |
| Tasks | Workloads that are performed in the background, even if no user is present |
Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@@ -2,9 +2,9 @@
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
![Version: 8.22.0](https://img.shields.io/badge/Version-8.22.0-informational?style=flat)
![Version: 8.26.0](https://img.shields.io/badge/Version-8.26.0-informational?style=flat)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat)
![AppVersion: v1.70.0](https://img.shields.io/badge/AppVersion-v1.70.0-informational?style=flat)
![AppVersion: v1.74.0](https://img.shields.io/badge/AppVersion-v1.74.0-informational?style=flat)
A Helm chart to deploy homarr for Kubernetes
@@ -263,7 +263,7 @@ httproute:
value: /
backendRefs:
- name: homarr
port: 8080
port: 7575
```
</details>
@@ -427,17 +427,17 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
| envSecrets.dbEncryption.key | string | `"db-encryption-key"` | Secret key for SECRET_ENCRYPTION_KEY can be generated with `openssl rand -hex 32` |
| fullnameOverride | string | `""` | Overrides chart's fullname |
| hostAliases | list | `[]` | Add static entries to /etc/hosts in the Pod. This is useful in the following cases: - You are running in a dual-stack cluster (IPv4 + IPv6) and want to force usage of IPv4 for specific hostnames - Your application is having DNS resolution issues or IPv6 preference issues - You need to override or simulate DNS entries without changing global DNS - You are running in an air-gapped or isolated environment without external DNS Example: hostAliases: - ip: "192.168.1.10" hostnames: - "example.com" - "example.internal" |
| httproute | object | `{"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"my-gateway","namespace":"default"}],"rules":[{"backendRefs":[{"name":"homarr","port":8080}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | Gateway API HTTPRoute configuration |
| httproute | object | `{"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"my-gateway","namespace":"default"}],"rules":[{"backendRefs":[{"name":"homarr","port":7575}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | Gateway API HTTPRoute configuration |
| httproute.enabled | bool | `false` | Enable HTTPRoute |
| httproute.hostnames | list | `["chart-example.local"]` | Hostnames this route matches (similar to ingress.hosts.host) |
| httproute.parentRefs | list | `[{"name":"my-gateway","namespace":"default"}]` | References to the parent Gateway(s) this route attaches to. Each item must include at least a `name`, and optionally a `namespace`. |
| httproute.rules | list | `[{"backendRefs":[{"name":"homarr","port":8080}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | List of routing rules. Each rule can include: - matches: path/header/query matching - filters: optional transformations (redirects, header modifications, etc.) - backendRefs: one or more Kubernetes Services to forward traffic to |
| httproute.rules | list | `[{"backendRefs":[{"name":"homarr","port":7575}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | List of routing rules. Each rule can include: - matches: path/header/query matching - filters: optional transformations (redirects, header modifications, etc.) - backendRefs: one or more Kubernetes Services to forward traffic to |
| httproute.rules[0].filters | list | `[]` | Optional filters for this rule (default: empty) |
| httproute.rules[0].matches[0].path.type | string | `"PathPrefix"` | Path match type. One of: Exact, PathPrefix, RegularExpression |
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
| image.tag | string | `"v1.70.0"` | Overrides the image tag whose default is the chart appVersion |
| image.tag | string | `"v1.74.0"` | Overrides the image tag whose default is the chart appVersion |
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
| ingress.annotations | object | `{}` | Ingress annotations |
| ingress.enabled | bool | `false` | Enable ingress |
@@ -54,6 +54,74 @@ services:
With docker run, add this parameter: `-v /var/run/docker.sock:/var/run/docker.sock`.
In Windows, you'll need to adjust the slashes: `-v //var/run/docker.sock:/var/run/docker.sock`
### Using Podman
On Linux, Homarr can connect to Podman's [Docker-compatible API](https://docs.podman.io/en/latest/markdown/podman-system-service.1.html).
The Podman API socket must be running and mounted into the Homarr container.
The socket grants full access to Podman, including arbitrary code execution as the user running the API, so only mount it into trusted containers.
For rootless Podman, enable the socket for the user that runs Homarr:
```bash
systemctl --user enable --now podman.socket
```
The rootless socket is located at `$XDG_RUNTIME_DIR/podman/podman.sock` (usually `/run/user/<uid>/podman/podman.sock`).
You can confirm the path with:
```bash
podman info --format '{{.Host.RemoteSocket.Path}}'
```
Mount that socket into the Homarr container and point `DOCKER_SOCKET_PATHS` to its path **inside** the container:
```yaml
services:
homarr:
container_name: homarr
image: ghcr.io/homarr-labs/homarr:latest
restart: unless-stopped
volumes:
- ${XDG_RUNTIME_DIR}/podman/podman.sock:/var/run/podman.sock
- ./homarr/appdata:/appdata
environment:
SECRET_ENCRYPTION_KEY: your_64_character_hex_string
DOCKER_SOCKET_PATHS: /var/run/podman.sock
ports:
- "7575:7575"
```
Start Homarr with `podman compose up -d`.
Run the compose project as the same user that owns the rootless Podman socket so `$XDG_RUNTIME_DIR` resolves to the correct path.
To keep the user socket available after logout and across reboots, enable lingering with `loginctl enable-linger <user>`.
For rootful Podman, enable the system socket with `sudo systemctl enable --now podman.socket`.
Its default path is `/run/podman/podman.sock`; mount that path instead and start Homarr with `sudo podman compose up -d`.
:::note SELinux
On systems with SELinux enabled, Podman's documentation recommends disabling label separation for a container that accesses the API socket.
Add the following to the Homarr service:
```yaml
security_opt:
- label=disable
```
:::
You can connect Homarr to multiple Docker-compatible sockets by mounting each socket and listing their in-container paths, separated by commas:
```yaml
services:
homarr:
image: ghcr.io/homarr-labs/homarr:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${XDG_RUNTIME_DIR}/podman/podman.sock:/var/run/podman.sock
environment:
DOCKER_SOCKET_PATHS: /var/run/docker.sock,/var/run/podman.sock
```
### Managing your Docker containers within Homarr
Homarr allows you to interact with Docker containers running on your system.
+1 -2
View File
@@ -43,12 +43,11 @@ import { immichServerStatsWidget } from "../../widgets/immich-server-stats";
<code>user.read</code>,{" "}
<code>user.update</code>,{" "}
<code>asset.read</code>,{" "}
<code>asset.view</code>,{" "}
<code>asset.download</code>,{" "}
<code>asset.statistics</code>,{" "}
<code>server.statistics</code>,{" "}
<code>server.about</code>.
<br />
Additional permissions may be required depending on your Immich version. If you encounter authorization errors, grant any missing permissions requested by the server.
</span>
@@ -10,14 +10,15 @@ import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { navidromeIntegration } from ".";
import { audioStatsWidget } from "@site/docs/widgets/audio-stats";
import { mediaServerWidget } from "@site/docs/widgets/media-server";
<IntegrationHeader integration={navidromeIntegration} categories={["Media"]} />
<IntegrationHeader integration={navidromeIntegration} categories={["Media", "Media server"]} />
Navidrome provides music library statistics including artist, album, and song counts, as well as now playing information via the Subsonic API.
Navidrome provides music library statistics including artist, album, and song counts, as well as now playing audio streams via the Subsonic API.
### Widgets & Capabilities
<IntegrationCapabilites items={[{ widget: audioStatsWidget }]} />
<IntegrationCapabilites items={[{ widget: audioStatsWidget }, { widget: mediaServerWidget }]} />
### Adding the integration
@@ -0,0 +1,48 @@
---
title: "What's Up Docker"
description: "What's Up Docker (WUD) watches your Docker containers and reports which ones have newer image versions available."
hide_title: true
---
import { IntegrationHeader } from "@site/src/components/integrations/header";
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
import { AddingIntegration } from "@site/src/components/integrations/adding";
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
import { wudIntegration } from ".";
import { wudWidget } from "@site/docs/widgets/whats-up-docker";
<IntegrationHeader integration={wudIntegration} categories={["Health Monitoring"]} />
The What's Up Docker integration lets Homarr connect to your [WUD](https://github.com/getwud/wud) instance so you can see how many of your watched containers have image updates available directly from your dashboard.
### Widgets & Capabilities
<IntegrationCapabilites items={[{ widget: wudWidget }]} />
### Adding the integration
<AddingIntegration />
:::info URL configuration
Use the root URL of your WUD instance, for example `https://wud.example.com`. Homarr calls the `/api/app` and `/api/containers` endpoints under that URL automatically.
:::
### Secrets
WUD does not require authentication by default. If your instance is protected with [Basic Authentication](https://github.com/getwud/wud/blob/main/docs/configuration/authentications/basic/README.md), you can provide a username and password.
<IntegrationSecrets
secrets={[
{
credentials: [],
steps: [],
},
{
credentials: ["username", "password"],
steps: [
"Open your WUD instance's configuration and configure a WUD_AUTH_BASIC_* user as described in the WUD documentation",
"Enter that username and password in Homarr — Basic authentication is handled automatically",
],
},
]}
/>
@@ -0,0 +1,9 @@
import { IntegrationDefinition } from "@site/src/types";
export const wudIntegration: IntegrationDefinition = {
name: "What's Up Docker",
description:
"What's Up Docker (WUD) watches your Docker containers and reports which ones have newer image versions available.",
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/whats-up-docker.svg",
path: "../../integrations/whats-up-docker",
};
+33
View File
@@ -82,4 +82,37 @@ 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
Multi-integration widget queries keep data from integrations that remain available. Calendar and media-server results include
an optional `error` on a failed integration; media-request responses expose a `failedIntegrations` array, and the latest
requests endpoint returns its items in `requests`. When every selected integration fails, the query returns an error instead
of a partial response.
Other widgets use the same settlement pattern with endpoint-specific shapes: a failed integration may appear as a named
fallback row, in a failure array, or be omitted while healthy results remain. Widget pages should identify the shape they
render rather than assuming every query exposes an `error` field.
### Saving dashboard layouts
The dashboard editor saves item positions and sections as one board update.
Canvas height follows its content automatically and does not require a separate API setting.
Responsive layouts store optional left and right gutter widths from zero to three columns.
Gutter columns are reserved from the layout's total column count, and changing them reflows affected items.
## Get Custom Widget authoring resources
Custom JSX v2 provides four HTTP discovery endpoints:
- `GET /api/custom-widgets/authoring-prompt` returns the current prompt plus MCP and HTTP resource identifiers.
- `GET /api/custom-widgets/schema` returns the canonical widget JSON Schema.
- `GET /api/custom-widgets/components` returns the release-matched static component catalog.
- `GET /api/custom-widgets/skill` returns the concise live `SKILL.md` plus skills.sh, source, and install metadata. Retrieve the schema, component catalog, and examples through their dedicated resources only when needed.
Call `customWidget_configurationRequestUser` to create a temporary API source setup URL where the user can confirm their server URL and enter credentials. Do not construct or save this URL.
Authenticated tRPC and MCP procedures support definition management, validation, previews, template patches, and credential writes. An exported definition does not contain credentials.
+4 -1
View File
@@ -28,7 +28,7 @@ The name must be at least one character long and can be chosen freely.
### Icon URL
This is the URL to the icon that will be displayed on the app widget.
It can be a url to an image in the internet, one from the included icon repositories or a local file uploaded through the upload button.
You can paste a direct HTTP(S) URL to an image, search the included icon repositories by name, or upload a local file through the upload button. The app name is pre-filled as the initial library search when no image is selected. Local images appear first, followed by SVG icons and other formats. Direct image URLs are previewed, used as entered, and are not searched in the icon repositories.
![Icon URL](./img/app-icon-url.png)
@@ -40,11 +40,14 @@ An optional description that can be shown as tooltip on the app widget.
The URL to the website you want to link to. Clicking on the app widget or bookmark will open this URL.
You can also use custom URI schemes such as `vscode://file/path`, `microsoft-edge:https://example.com`, or `myapp://open` to link to local applications or third-party programs. We trust that you know what you are doing — only `javascript:` URIs are blocked for security reasons.
### Ping URL
When you want to use another url for the simple status check you can set it here.
This will allow you to set a different URL for the ping than the URL that is opened when clicking on the app widget.
It's useful when you want to ping the service internally for example with a custom ip but open the website externally.
Note that the ping URL only accepts `http://` and `https://` URLs — custom URI schemes are not supported for pings.
## List of apps
+362
View File
@@ -0,0 +1,362 @@
---
tags:
- Assistant
- AI
- Administration
- Security
---
import assistantConfigImage from "./img/assistant-config.png";
import assistantWidgetImage from "./img/assistant-widget.png";
# Assistant
Administrators can configure the instance-wide AI assistant from **Management → Assistant**.
It is available to signed-in users from the user menu, with `Shift + A`, and from the
`Cmd/Ctrl + K` command menu. **Ask Homarr Assistant** is always available as a local result in the default
search. Selecting it with an existing query sends that query in the background; selecting it without a
query turns the command-menu search field into a prompt composer. Press
`Enter` to send the prompt in the background without opening the conversation. The search field receives
focus whenever the command menu opens, including when it is opened by an assistant tool. Sending from the
command menu preserves any draft in the expanded conversation. The local result is temporarily disabled while
the assistant is responding or waiting for an approval.
Availability follows the setup state. Signed-out users do not get an assistant action in the user menu or from
`Shift + A`; the always-visible command-menu result is disabled and says **Sign in**. If the provider connection or
model is configured but **Enable Assistant** is off, the user-menu and `Shift + A` entry points do not open the
assistant, while `Cmd/Ctrl + K` keeps its result visible but disabled with an explanation that an administrator must
configure and enable it. Saving a working connection, model, and enabled state makes all three entry points
available again.
While a response is running, a compact activity bar appears at the bottom of the page and the user-menu
avatar shows a thinking indicator. The activity bar can be opened to follow the response. Completed
responses add an unread count to the user menu until they are opened or dismissed. The expanded
conversation has a minimize action that returns it to the activity bar without interrupting a response.
Its adjacent close action hides the panel without leaving an activity bar behind. Both window actions use
subtle buttons with an icon, tooltip, and accessible label.
The transcript follows streamed output while it is already at the bottom, including fast responses. Scrolling
up releases that follow state until **Scroll to latest** is selected or the user returns to the bottom.
`Shift + A` opens the compact conversation panel directly. The shortcut is ignored while typing in text
fields, selectors, and editable content.
The **Assistant** board widget embeds the same persisted conversation and full interaction surface directly
on a dashboard. By default it follows the current conversation. Widget settings can instead pin a specific
conversation; the conversation picker stays hidden until **Pinned conversation** is selected.
If the pinned conversation was deleted or cannot be opened, the widget shows an **Open conversation** recovery action
and retry state instead of silently losing the widget content or falling back to another thread. **Open conversation**
retries the same pinned thread after a failed switch.
<img
src={assistantWidgetImage}
alt="Assistant widget embedded on a dashboard with its conversation header, automatic approvals control, model selector, and composer"
style={{ maxWidth: "100%" }}
/>
The current preview capture shows the board-embedded surface, including its conversation header, automatic-approval
control, model and thinking controls, and composer. These controls operate on the same persisted thread as the
floating assistant while keeping this surface's draft and focus independent.
The Shift+A panel is non-modal: opening it focuses its composer, but it does not capture focus from the board.
Users can continue typing in an Assistant widget or interacting with other dashboard controls while the panel
is open. Every visible assistant surface keeps its own draft, attachment list, selection, and focus while sharing
the same persisted conversation. Switching conversations restores the draft that belongs to that surface and
conversation instead of copying text into another widget or the floating panel.
When the assistant is unavailable, its command-menu result remains visible but disabled and explains that
an administrator must configure and enable it.
The assistant uses the assistant-ui runtime for **AI SDK v7**. Homarr tracks the newest mutually compatible
package set permitted by its supply-chain release-age policy. Persisted message rows can still report the upstream
codec name `ai-sdk/v6`; assistant-ui's current v7 adapter deliberately retains that identifier for its compatible
UI-message wire format. It does not mean the application is running AI SDK v6.
## Configure a model provider
Homarr includes presets for OpenRouter, OpenAI, Anthropic Claude, Google Gemini, xAI, Groq, Mistral AI,
DeepSeek, Together AI, Ollama, and LM Studio. Provider icons make the presets easy to identify. A custom
preset connects any other service that implements the OpenAI Chat Completions API.
The provider list also shows **Homarr — Coming soon** as a disabled option.
The management page presents setup as three compact sections: provider and endpoint, credentials, then
model and availability. A status summary at the top shows whether the connection is ready, whether a key
is protected, and which model is selected. Redundant active-state badges are omitted when the setup is ready.
<img
src={assistantConfigImage}
alt="Assistant configuration page showing provider, credentials, model, and availability sections"
style={{ maxWidth: "100%" }}
/>
The screenshot shows the configuration surface with no provider key entered; the assistant remains disabled until a
working provider, model, and enabled state are saved.
1. Choose a provider preset or **Custom endpoint**, then confirm the API base URL. It usually ends in `/v1`.
2. Add an API key if the service requires bearer authentication. Additional authentication or routing
headers are available in the collapsed advanced section.
3. Save the provider connection.
4. Refresh the model list and select a discovered model. Homarr displays the friendly model name but
stores and sends the provider's exact model ID. Context-window sizes use compact labels such as `1M`.
5. Enable the assistant and save the configuration.
Changing the provider or base URL immediately resets the unsaved credential/header draft, selected model, and
enabled state, and puts the model section into a **save connection first** state. Saving that destination change also
clears stored credentials for the previous destination. Changing the discovery path resets the selected model and
enabled state; save the new path before relying on its discovery results. For an unsaved provider or base-URL change,
the model controls, **Refresh models**, web-search switch, enable switch, and **Save assistant** remain unavailable.
After the connection is saved, refresh the model list and select a model; if discovery is blank or returns no models,
the manual model-ID field is the supported fallback. Only then enable the assistant and save the configuration.
The model discovery path defaults to `/models` and is appended to the API base URL. Leave the custom path blank when
the service does not expose a usable discovery endpoint. A manual model ID can still be used when discovery is
unavailable or returns no models.
API keys and additional header values are encrypted in the Homarr database. They are decrypted only on
the server for discovery and model requests, and they are never returned to the browser. Changing the
provider or base URL, then saving the connection, clears existing credentials. This prevents credentials for one
service from being sent to another endpoint. Additional headers are write-only; saving them replaces the complete
stored header set. Use the
explicit clear/remove flow when you want to remove previously saved headers.
When a key is already stored, the page shows its protected state instead of an ambiguous empty password
field. Removing the API key requires explicit confirmation. Save that removal before entering and saving a
different key.
:::info Container networking
For a containerized Homarr installation, `localhost` refers to the Homarr container. Use a resolvable
container name, internal DNS name, or host gateway address to reach Ollama, LM Studio, or another local
model server.
:::
The configured endpoint must support streaming Chat Completions and structured tool calls for the full
assistant experience. Model discovery alone does not guarantee tool support, except when OpenRouter
explicitly advertises the model's `tools` capability.
OpenRouter web search is an opt-in option in the model section. It registers OpenRouter's model-callable
[`openrouter:web_search` server tool](https://openrouter.ai/docs/guides/features/server-tools/web-search)
alongside Homarr's tools, so OpenRouter performs each search without
exposing the stored credential to the browser. The model decides when live research is needed and can use the
grounded results and source links in responses or dashboard notes. Homarr limits each assistant request to
three searches with five results per search. OpenRouter charges web-search usage separately from model tokens.
Completed responses show a compact web-search activity card when OpenRouter reports search usage. OpenRouter
does not expose these server-side searches as ordinary client-executed tool calls, so Homarr shows the reported
search count and the safe title and URL from each returned `url_citation` annotation instead. Citation excerpts
stay in the provider response and are not persisted in the activity card.
The option is also available for custom endpoints that forward OpenRouter's server-tools request format. This
supports OpenRouter proxies, including the future Homarr Router provider. Leave it disabled for ordinary
OpenAI-compatible endpoints because the `openrouter:*` tool type is not part of the standard OpenAI API.
The Claude preset uses Anthropic's OpenAI SDK compatibility layer. Anthropic describes that layer as
suited to testing and comparison rather than as its full native API; unsupported OpenAI fields may be
ignored. Streaming responses and tool calls are supported, which are the capabilities Homarr Assistant
uses. Choose OpenRouter or another provider when you prefer a provider-neutral production endpoint.
## Conversations and tools
Conversations and messages are stored per user in the Homarr database. The assistant stays out of the
page layout until it is opened. Users can start, revisit, and delete conversations from the
conversation menu in the floating panel.
Messages support copy, edit-and-resend, response regeneration and branch navigation, Markdown export,
and helpful/not-helpful feedback. The conversation menu also provides a compact export action beside delete for
each conversation. Its debugging-oriented Markdown includes every stored user, assistant, reasoning, tool, error,
branch-parent, and server-metadata record while redacting secret-looking fields and embedded attachment data.
The per-response export uses the same tool-aware format instead of assistant-ui's text-only copy representation.
Selecting text in a message exposes a quote action that carries the selected passage into the next prompt.
Files returned by a compatible model can be downloaded directly from the message.
Assistant responses render GitHub-flavored Markdown, including headings, links, lists, task lists, tables,
quotes, inline code, and fenced code blocks. Line breaks are preserved, and Homarr repairs common streamed
responses where a provider returns escaped line breaks or places a bold-label list on one line.
Completed fenced code blocks use language-aware syntax highlighting; a streaming block stays plain until its
content is stable. Safe same-origin links are prefetched and use client-side navigation, so opening a returned
preview, editor, board, or other Homarr item does not unload the assistant. External links keep their normal
external-browser behavior.
Fenced `mermaid` blocks render as diagrams in the current color scheme using Mermaid's strict security mode;
invalid diagrams fall back to their source instead of breaking the response.
When Homarr's icon search returns an image URL, visual result tables render that actual icon in a compact
image column instead of substituting an emoji. Assistant-provided Markdown images accept safe relative,
HTTP, and HTTPS sources and load without forwarding the Homarr page as the referrer. Image message parts
render through assistant-ui's image primitive in a bounded responsive preview with an accessible loading state,
a filename caption when available, and an in-place retry when the source fails.
Images up to 1 MB and text, Markdown, CSV, HTML, XML, or JSON documents up to 350 KB can be attached by
using the paperclip, dropping files onto the composer, or pasting an image with `Cmd/Ctrl + V`. Attachment
previews stay local to the composer where they were added and show upload state, file type, and a removable image
thumbnail or document tile. A request can contain up to five attachments.
Image understanding depends on the selected model; document text is extracted locally by Homarr and sent
as part of the prompt.
Type `@` in the composer to add permission-checked context. The nested menu contains apps, integrations,
boards, widgets, and tools available to the current user. Homarr validates every referenced ID again on
the server before adding it to the model context. Selected references appear as compact, typed pills in the
composer and sent message, while preserving assistant-ui's underlying directive syntax for the model. When the
assistant cites one of those references in its own response, Homarr renders the same typed pill and uses the
stored app or integration icon when one is available. Type `/`
for common prompt shortcuts. Both menus support keyboard navigation and keep the active result visible while
moving through long lists. The selector below the composer combines searchable, provider-grouped models with
their description, context size and image-input capability, plus the thinking-level control. Model selection is
saved to the current conversation when selected. For a persisted thread, changing the model updates that thread's
preference without changing the administrator's configured default; switching threads restores each thread's saved
model, while a new thread starts from the configured default until the user changes it. Thinking mode can be left on
**Auto**, disabled, or set
from minimal through high; explicit levels are sent using the AI SDK's provider-neutral reasoning setting and
still depend on support from the chosen model and endpoint. Reasoning text is shown live when the provider streams
it through assistant-ui's grouped-parts and chain-of-thought primitives. The compact **Thinking** trace groups
adjacent reasoning with ordinary tool calls, shows each tool's status, duration and safe human-readable target,
and expands raw input, output and result previews only on request. Approval and question tools remain full-width
outside the trace. The disclosure renders Markdown, can be opened or closed without interrupting the response,
and keeps the user's collapsed state when another message starts. A compact dot matrix distinguishes an initial
thinking pass from response streaming and settles into waiting, success, or error states in the minimized activity
bar. The state always keeps its text label, and the animation is disabled when the operating system requests
reduced motion.
A circular context ring beside the model selector gives a quick glance at cumulative conversation usage without
expanding the composer toolbar. Its accessible label includes cumulative tokens and cost when available, while
its popover keeps the latest complete context reading as a new response starts and lists every request
turn with its model, provider, input, output, cached-input and reasoning tokens, context use, duration, speed,
agent steps, routing details, and cost when reported. Before usage is available, the control remains a compact
ring instead of showing a redundant missing-data label. Closing and reopening the popover reads persisted
conversation metadata instead of resetting the display to zero. A larger ring inside the popover breaks the latest
reported context into used, remaining and total capacity plus input, cached-input, output and reasoning tokens.
Each assistant response places a small provider logo in the same stable action toolbar as copy, export,
feedback, and regenerate. Hovering the logo shows the provider, exact model, timing, throughput, and cost without
adding another metadata row to the transcript. Branch navigation occupies its own adjacent group, so switching
between response versions never pushes the response actions off-screen.
OpenRouter responses request inline usage and routing metadata. Opening detailed turn data also loads the
server-side generation record for its model steps and briefly polls while OpenRouter finishes processing it.
The details separate Homarr's end-to-end timing from OpenRouter's provider latency, generation time, and provider
throughput, and can include normalized and native token counts, moderation latency, cache discount, fallback count,
service tier, data region, BYOK status, routed provider, generation identifier, exact cost, and upstream inference
cost. Provider credentials remain server-side; the browser can request only generation identifiers already stored
on a conversation owned by the signed-in user and signed by the Homarr server for that user and conversation.
Successful generation lookups are cached briefly so polling and multi-step requests do not repeatedly query
OpenRouter for the same record.
When a provider omits cost but model discovery includes prompt and completion pricing, Homarr labels the
calculated value as estimated. Missing provider data is shown as not reported rather than inferred.
The assistant can use the same internal tool definitions as the [Homarr MCP server](./mcp).
Each tool call runs as the signed-in user, so normal Homarr permission checks still apply. Read-only
procedures can run during the agent loop. Mutating procedures require native approval unless automatic approvals are
enabled for the conversation. Neutral choices and questions still wait for the user. The approval appears as a large
**Approve and run** or **Deny** action;
the assistant does not require a second typed confirmation first. Tool cards expose their status, timing,
validated input, result, and error state without flooding the conversation with raw JSON by default.
Common collection and widget results are summarized inline as compact responsive cards, while the full
structured result remains available in the tool card details. Icon searches show the returned image variants,
file formats, and repositories instead of the size of the complete icon catalog. Their trace uses a readable
activity such as **Looking for “homarr” icons** instead of exposing the internal MCP procedure name.
Large tool-driven requests can use up to 32 agent steps and a larger response budget within one request, so
multi-item dashboard changes normally finish without requiring a follow-up “Continue” message. OpenRouter calls
Homarr tools sequentially to prevent routed models from interleaving or truncating parallel JSON arguments, while
every mutation still follows its native approval policy, including conversation-scoped automatic approvals. A safely
recoverable truncated icon-search object is repaired
before validation; incomplete search strings and all mutation inputs are never guessed.
Homarr also normalizes internal tool results to the model's JSON-compatible message format before continuing an
agent loop. Rich server values such as dates cannot invalidate the next model step, and an interrupted tool call
without a result is omitted when the conversation resumes instead of making the entire prompt fail.
The assistant can create a formatted dashboard note by adding a **Notebook** widget. Ask it for a note with
headings, lists, tasks, links, emphasis, tables, or images; it generates compatible rich-text content, shows
the native widget configuration form and standard mutation approval, then places the widget in the next
available dashboard position. The generated content is retained even though the Notebook content field is
normally hidden from widget settings.
Automatic placement checks every responsive layout and treats both widgets and complete container footprints
as occupied. New assistant-created items use the first free position in the main canvas instead of overlapping
existing content or being placed in a gutter.
Every assistant-created dashboard widget now passes through Homarr's native widget editor before it is added.
The form fills widget defaults, validates widget-specific options, and only offers compatible integrations that
the current user can use. Required integrations cannot be left empty, single-integration widgets cannot receive
multiple integrations, and the reviewed values are passed unchanged into the final approval-gated mutation.
Custom-widget tools let an administrator list, inspect, create, and update definitions. The custom-widget
form's **Design with AI** action sends a focused request into the current conversation, where the assistant
can propose the matching mutation and use the same approval controls. Existing stored secret values are
reported only as configured/not configured and are preserved on update unless replacements are explicitly
provided. For Custom Widget requests, Homarr lazily adds the complete installed skill, all bundled schema,
runtime and security references, and the current JSON Schema to the trusted authoring context. This avoids an
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. 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.
The authoring phase limits broad component-document retrieval and then moves into validation with the smallest useful
reference set. Supplied samples and successful preview responses are treated as binding contracts: the assistant is
instructed to render the requested fields, guard optional nested data, preserve every returned item, expose freshness
when timestamps are available, and provide a refresh path for recoverable empty or error states.
The shield control in the conversation header can enable automatic approvals for the current conversation; they are
off by default.
While enabled, Homarr accepts every affirmative approval step as it arrives: protected mutations are approved,
presented app details and board changes are used, and structured questions with an explicitly categorized
**Confirm** answer choose that answer. Neutral choices such as selecting a board still wait for input because
they do not have a safe affirmative default. Automatic approvals turn off when you switch conversations, and
the shared approval tracker prevents the same call from being handled twice when both a board widget and the
floating assistant are visible.
When the assistant genuinely needs missing information or a choice, it can pause with a structured
question. Each proposed answer is visibly categorized as **Confirm**, **Decline**, or **Option**, so the effect
of automatic approval is unambiguous. **Other** is shown only when the tool allows it (`allowOther` is not `false`);
selecting it opens a freeform response and is always a manual alternative, not an automatic approval choice. While a
question is unanswered, its controls stay in a dedicated area immediately
above the composer instead of requiring the user to find the tool call in the transcript. The question temporarily
uses the available conversation body, with its own scrolling when necessary, rather than squeezing a long option
list into a short dock beside part of the transcript. The answered choice then remains in the conversation history.
Submitting the answer resumes the same agent run automatically. The minimized activity bar shows the
question or approval that needs attention and opens directly back to the request. While an answer, app
form, or mutation approval is pending, the composer preserves its draft but does not replace the pending
tool call with a new message. If the provider stops before an interactive tool supplies complete arguments,
Homarr replaces the loading state with an explicit error and confirms that no change was made.
App creation uses Homarr's native app form inside the conversation. The form reuses the local icon picker,
including automatic matching and searches through the `icon.findIcons` procedure, before the final
`app.create` approval is requested. App fields are filled only after the complete tool input has arrived, so
streaming partial arguments cannot replace the proposed values with empty fields. The model is instructed not
to invent external icon CDN URLs.
Board appearance changes use a similar review flow. The assistant first reads the board's current settings,
including its complete custom stylesheet, and then opens an editable form with Custom CSS, Appearance,
Background, and General tabs. The form highlights how many fields were proposed, preserves settings the
assistant did not change, and returns only values that differ from the current board. If nothing differs, the review
cancels without creating a mutation or approval. Custom CSS can be
copied or restored before continuing, and Homarr warns when it contains an imported stylesheet or remote
resource URL. Continuing from the form opens the normal mutation approval before anything is saved.
The board-settings tool covers General, Background, Appearance, Custom CSS, and app-status settings. It does not edit
responsive layouts, board access control, or Danger zone actions; those remain outside this review scope.
Ask for changes such as “make the cards denser on my Home board,” “add a mobile CSS rule without removing
my existing styles,” or “change the board colors and disable app status checks.” The current user must have
modify permission on the selected board. The empty conversation includes an **Improve a dashboard** starter,
and `/style` prepares the same guided workflow from the composer.
Homarr also exposes a small set of browser-only actions to the assistant. These actions can navigate to
safe internal routes, refresh the current view, and open Homarr interfaces such as the command menu and media request search.
They never receive provider credentials and cannot navigate to arbitrary external URLs.
After an approved mutation completes, Homarr automatically refetches active page and dashboard data so
board items, settings, custom CSS, apps, and other visible results update without a full browser reload.
The refresh action in the conversation header performs the same safe refetch manually. It keeps the
conversation open while refreshing both client queries and server-rendered route data.
:::warning
Assistant prompts and selected tool results are sent to the configured model endpoint. Review that
service's privacy and retention terms before enabling this feature for an instance.
:::
+1 -1
View File
@@ -29,7 +29,7 @@ Click the **Export** button in the export card. You'll be prompted to type `I un
| `db.sqlite` | Full SQLite database with all entities |
| `metadata.json` | Backup metadata: Homarr version, creation timestamp, encryption key |
The backup includes everything: apps, integrations, secrets, boards, users, groups, permissions, search engines, custom widgets, and server settings. (It just backs-up the whole database)
The backup contains the complete database. It includes Custom Widgets and all other Homarr configuration.
## Restoring a Backup
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 256 KiB

+194 -25
View File
@@ -11,15 +11,18 @@ tags:
import iconColorImage from "./img/icon-color.png";
import itemRadiusImage from "./img/item-radius.png";
import layoutLargeImage from "./img/layout-large.png";
import layoutMediumImage from "./img/layout-medium.png";
import layoutSmallImage from "./img/layout-small.png";
import layoutSettingsImage from "./img/layout-settings.png";
import layoutSettingsCustomRailsImage from "./img/layout-settings-custom-rails.png";
import boardViewModeImage from "./img/board-view-mode.png";
import boardEditModeImage from "./img/board-edit-mode.png";
import addBoardContentMenuImage from "./img/add-board-content-menu.png";
import keyboardTileEditingImage from "./img/keyboard-tile-editing.png";
Each dashboard is called a "board".
You can create as many boards as you want.
Boards can be restricted to user groups.
A user may also have a "home board" which they will see at the root path (e.g. my-homarr.com or my-homarr.com/board).
Other boards can be viewed by direct links (e.g. my-homarr.com/board/smart-home) or via the management pages.
A user may also have a "home board" which they will see at the root path (e.g. my-homarr.com or my-homarr.com/boards).
Other boards can be viewed by direct links (e.g. my-homarr.com/boards/smart-home) or via the management pages.
Names of boards must be unique because this will be used in the URL to load the desired board.
@@ -36,13 +39,15 @@ Underscores and hyphen-minus are allowed.
### Column count
Homarr has a complex drag and drop system that can be used from the browser.
The column count defines how many horizontal columns are available to drag and drop elements to.
The column count defines how many fixed-width cells are available in each row.
One cell contains a `200 × 200` logical-pixel tile.
Homarr scales the whole board uniformly instead of changing widget proportions, and always fits it to the available width without horizontal scrolling.
The main canvas and enabled rails stay at least one viewport tall, and the canvas grows automatically while you drag or resize items near its lower edge.
### Public
This will enable public access for everyone - regardless of whether they are logged in or not.
This can be really useful if you want to have public boards in your company or for family and fiends.
This can be really useful if you want to have public boards in your company or for family and friends.
Modifications for public boards must be allowed explicitly using user groups and their permissions.
## Board settings
@@ -55,6 +60,8 @@ Alternatively, you can also navigate from the board page itself using the cog ic
![](./img/navigate-to-settings-2.png)
All board settings are edited on a single page. Click the **Save changes** button in the save bar at the bottom to apply them, or **Discard** to restore the last saved values. The save bar only appears when you have unsaved changes. Access control and the Danger zone are independent sections and are applied immediately.
### General
#### Page title
@@ -86,32 +93,166 @@ Homarr will fall back to the Homarr logo if nothing is set.
### Layout
In this section you can configure the different responsive layouts. Depending on your screens width the layout will change.
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.
![](./img/layout-settings.png)
<img
src={layoutSettingsImage}
alt="Board settings showing protected Mobile and Base responsive layouts with rail controls"
style={{ maxWidth: "100%" }}
/>
On each element the position and size of items can be changed seperately.
When adding a new layout the position and sizes will automatically be generated depending on the column count.
When adding a new item / dynamic section that will always be 1x1 on each layout and placed on the first available position.
The layout cards show the representative geometry used for planning. This current example also shows a custom layout
with both fixed rails enabled; the rails reserve columns and the remaining canvas reflows automatically.
<img
src={layoutSettingsCustomRailsImage}
alt="Board layout settings with a custom layout and both fixed rails enabled"
style={{ maxWidth: "100%" }}
/>
Every board has two protected layouts. Creating a board adds **Mobile** at `0px` with three columns and **Base** at
`768px` with the column count you choose. Both layouts can be edited, but neither can be removed.
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.
Widgets see their logical size, while the board's visual scale makes the complete layout larger or smaller.
When fitting below 100%, Homarr keeps widget typography and control density at their normal readable size.
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.
- **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. 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 start from Base's current column count and rail widths; Homarr chooses a
free breakpoint between Mobile and Base, and you can then change the breakpoint, columns, and rails.
Older or incomplete boards are repaired automatically. A board with no layouts receives Mobile at `0px` with three
columns and Base at `768px` with ten columns. A board with one layout keeps it as Base and receives a generated Mobile
layout. With multiple layouts, the narrowest becomes Mobile, the widest becomes Base, and intermediate layouts remain
custom. Duplicate or out-of-order breakpoints are normalized without discarding saved positions.
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 choosen freely. It is only used for the 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
Amount of columns for this layout. Defines how many items can be placed in a row. When changed it will automatically adjust the position and size of the items.
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
Each responsive layout can enable a fixed left rail, a fixed right rail, or both.
Set a rail width to zero to disable it for that layout.
Choose a width from one to three columns with the three-step slider.
The preview shows how much space remains for the main canvas.
Rail columns are reserved from the layout's total column count.
For example, a 12-column layout with a two-column left rail leaves ten columns for the canvas.
The combined rail widths cannot consume the final main-canvas column.
Enabling or widening a rail reflows canvas items into the remaining columns.
Disabling a rail moves its contents back into the next available canvas positions.
Rails fill the available window height and remain fixed while the page scrolls. They do not grow or scroll independently.
Apps, widgets, and Containers inside them still use the same fixed `200 × 200` cells and can be rearranged in edit mode, where each rail shows its name and boundary.
#### Breakpoint
Minimum width of the screen where the layout will be used.
Once the minmum of the next layout is reached the layout will change.
The minimum screen width where the layout is used; a layout is eligible at its breakpoint and above. Mobile is fixed at
`0px`. Custom breakpoints must be unique and sit between Mobile and Base. Base must remain the highest breakpoint.
When the initial viewport is not available, Homarr uses its remembered viewport information, client hints, or User-Agent
fallback to select a layout.
#### View and edit modes
View mode renders a static board without executing the drag-and-drop editor.
After the board is interactive, Homarr prepares the editor in the background for users who can modify it.
Entering edit mode keeps the static board visible until that preparation finishes, then enables collision-aware movement and resizing.
Collapsed Containers are expanded while editing so their contents and controls are reachable.
<img
src={boardViewModeImage}
alt="A Homarr board in view mode with the dashboard canvas and static widgets visible"
style={{ maxWidth: "100%" }}
/>
Widget content is inert in edit mode, so dragging from the inert widget area moves the tile instead of activating the widget;
settings and other controls remain usable in the compact header above the tile.
A single compatible collision can swap two items when both resulting placements fit, including different-sized or lateral moves.
For other collisions, or when a tile grows into occupied space, nearby tiles are compacted out of the way and collision chains reflow.
The visible corner controls provide a 44-pixel resize target, and edge controls appear on hover for horizontal or vertical resizing.
Resize handles stop at the tile's minimum allowed size while the pointer is still moving.
Use the pencil button in the board header to enter edit mode; click it again to save. If you leave the page with unsaved
changes, Homarr asks whether to discard them. If the editor cannot load, the board remains in view mode.
<img
src={boardEditModeImage}
alt="Board in edit mode with the board header controls and editable dashboard tiles visible"
style={{ maxWidth: "100%" }}
/>
While edit mode is active, **Add board content** creates a new item, app, integration, or Container on the main canvas.
New content uses the first available position; clicking an empty cell does not place it there, and items cannot be added
directly into a rail or Container from this menu.
<img
src={addBoardContentMenuImage}
alt="Board edit mode add-content menu showing New item, Add an app, New integration, and New container"
style={{ maxWidth: "100%" }}
/>
Pointer users can drag from the inert widget area; interactive controls are excluded. Containers use the grab button at their top-left corner.
Widgets can be resized from any edge or corner handle. Activate a Container's chrome with its top-left grip before using
its resize handles.
On a touch screen, press and hold a tile for about `200 ms` before moving it; a quick tap does not start a drag. Resize
handles support direct touch input.
Keyboard users can focus a tile, press Enter or Space to start editing, use the arrow keys to move it, and use Shift plus the arrow keys to resize it.
Press Enter, Space, or Escape to stop keyboard editing.
Keyboard moves and resizes apply immediately within the current canvas, rail, or Container; they cannot move an item
between grids. Use pointer drag or **Move / resize item** for that.
Escape stops keyboard editing without undoing those changes.
During a pointer or touch interaction, Escape cancels the move or resize and restores the layout from before that interaction.
Invalid, outside, or otherwise forbidden drops are cancelled in the same way.
The **Move / resize item** action in the edit menu remains available for precise numeric input.
<img src={keyboardTileEditingImage} alt="A focused board tile in keyboard editing mode" style={{ maxWidth: "100%" }} />
#### Preview
<img src={layoutSmallImage} height={256} />
<img src={layoutMediumImage} height={256} />
<img src={layoutLargeImage} height={256} />
Layout Settings shows a lightweight structural preview for every layout, including saved spans, Containers, widget labels, and app icons.
It uses a representative viewport width to project the saved geometry; it does not execute widget queries or represent every
pixel of the screen range where the 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.
### Containers and rails
Containers can hold apps, widgets, and nested Containers.
The same Container model works on the main canvas, inside a fixed rail, or inside another Container.
Move and resize Containers directly in edit mode; nested content stays within the Container's local bounds.
Resize outlines follow the pointer continuously while a dashed preview marks the grid size that will be applied on release.
Each responsive layout stores its own Container position and size.
Each Container can configure:
- Its label and whether that label is visible.
- Whether users can collapse and expand it like an accordion.
- Whether its menu shows **Open all in new tabs** for the app links it contains.
- Its border color and custom CSS classes.
Collapsing a Container reduces its full-width header to a half-row expansion bar and compacts the surrounding layout without losing its expanded size.
Expanding it smoothly restores those bounds and moves following items out of the way without reloading the widgets inside it.
### Background
@@ -157,7 +298,7 @@ Change the color of your bookmark / app icons.
#### Item radius
Change the radius of your items / categories.
Change the radius of your items and Containers.
<img src={itemRadiusImage} height={100} />
@@ -166,6 +307,11 @@ Change the radius of your items / categories.
This is the field where you can enter custom CSS.
The styling will only be applied on the respective board page.
When Homarr Assistant is enabled, users with modify permission can also ask it to edit the board's General, Background,
Appearance, Custom CSS, and app-status settings. The assistant reads the current stylesheet first and presents an
editable review form before requesting approval to save the change. It does not edit responsive layouts, access control,
or Danger zone actions.
[See this guide on how to work with custom CSS](/docs/advanced/styling/).
### Behaviour
@@ -174,6 +320,20 @@ The styling will only be applied on the respective board page.
This will disable the status check for apps.
#### Work directly from the board
Widgets expose their routine controls without requiring edit mode:
- Hold `Shift` while hovering a widget that supports advanced view to expand it in place while the other widgets
remain visible but dimmed. Release `Shift` to close it. Focus that widget and press `Shift` + `Enter` to keep the view
open until you close it. On touch devices, open it from the widget menu. Other widgets adapt in place instead.
- When signed in, outside edit mode, and with **Enable right click on widgets** enabled, right-click a widget to inspect
its live query status, refresh its data, change quick options, open settings, or run supported actions. App tiles do
not use this widget menu, and available controls follow your board and integration permissions.
The widget context menu follows the **Enable right click on widgets** preference in your user settings. On touch devices,
use the widget menu where your browser exposes it.
### Access control
With the access control, you can permit a single user or a group to your board.
@@ -219,7 +379,7 @@ This action is irreversible and cannot be undone.
The deletion of boards is permanent and cannot be undone.
There are two ways to delete a board:
- Navigate to the settings of your board, open the "Danger zone" dropdown and click on the "delete this board" button.
- Navigate to the settings of your board, open the "Danger zone" section and click on the "delete this board" button.
- Navigate to the list of boards in the management pages, click the three dots and click on "delete permanently".
Both actions will execute the same deletion process.
@@ -231,7 +391,8 @@ Duplicating a board will not copy the users and groups that have access to the b
nor the integrations the user does not have access to.
:::
Duplicating a board will create a new board with the same settings and layout as the original board.
Duplicating a board will create a new board with the same settings, all responsive layouts, and independent positions as the original board.
Duplicating a board that contains Custom Widgets also requires an administrator.
You can duplicate a board by clicking on the three dots in the list of boards and selecting "Duplicate board".
![](./img/duplicate-board-modal.png)
@@ -289,5 +450,13 @@ For the global home board you can only select boards that are public.
### Mobile home board
Same as the home board, you can also set a mobile home board.
It will be used for devices that are detected as mobile devices. (Using User-Agent header)
The mobile home board controls which board opens for devices that Homarr detects as mobile from the User-Agent header.
This is separate from a board's **Mobile layout**, which controls the layout selected inside that board.
You can set a mobile home board for yourself in your user preferences, for a user or group through management, or for the
whole server with **Global mobile board** under `Management` > `Settings` > `Boards`.
For signed-in users, Homarr checks the user's mobile home board, then the user's desktop home board, then the mobile and
desktop defaults for the lowest-position matching group, followed by the mobile and desktop defaults for the `Everyone`
group, and finally the server-wide mobile and desktop defaults. Anonymous visitors use the server-wide mobile default and
then the server-wide desktop default. Server-wide fallback boards must be public. See the
[global board defaults](/docs/management/settings#boards) for the complete precedence and the required public-board rule.
@@ -0,0 +1,9 @@
{
"label": "Custom Widgets",
"collapsed": true,
"position": 8,
"link": {
"type": "doc",
"id": "management/custom-widgets/index"
}
}
@@ -1,26 +1,156 @@
---
sidebar_label: AI and MCP authoring
---
# Use agents and skills.sh
# AI and MCP authoring
An external agent creates one widget at a time with the Homarr MCP server. The agent harness manages the list for multiple widgets.
Homarr exposes an iterative, validation-first workflow to authenticated administrators. Agents should continue until validation, preview, and diagnostics are clean; they must not invent inline network access.
Homarr does not create widget packs or drafts for multiple widgets.
## Recommended loop
The MCP API key must belong to a Homarr administrator. Non-administrator keys do not list Custom Widget tools, prompts,
or resources. The same administrator requirement applies to the matching HTTP authoring endpoints.
1. Read `customWidget_schema` and the generated component/capability reference.
2. Read an existing definition with `customWidget_byId`, or create a v3 draft.
3. Call `customWidget_validate`; fix every issue using its exact path.
4. Call `customWidget_preview` for the base GET and retain the preview session ID.
5. Test each named query with `customWidget_previewQuery`.
6. Dry-run every action with `customWidget_simulatePreviewAction`.
7. Inspect `customWidget_previewJournal` for status, duration, and method coverage.
8. Revalidate, then call import/create or a focused update/patch operation.
<a className="button button--primary" href="https://www.skills.sh/homarr-labs/homarr/homarr-custom-widget">
View the Custom Widget Skill on skills.sh
</a>
Stored secret values are never readable. Omitting secrets during an update preserves them. MCP actions are simulation-only; enabling live preview actions remains a browser-only administrator decision.
Install the Agent Skill from the verified Homarr repository:
## Template format
```sh
npx skills add https://github.com/homarr-labs/homarr --skill homarr-custom-widget
```
Use multiline JSX when reading or patching templates. Prefer focused template read/write/patch tools over rewriting an escaped one-line JSON string. When a complete portable artifact is needed, use the authoring bundle placeholder plus a separate fenced JSX block.
The portable skill gives concise authoring guidance. For connected agents, Homarr returns the complete `SKILL.md` and
all bundled skill references once in one lazy tool response. The Homarr MCP prompt then gives the agent a
retrieval workflow: read the live schema and fetch only the component documentation needed for the widget.
The AI prompt is generated from the same registry, examples, request rules, and limits as the validator. Agents should still trust validation output over prompt memory because the installed Homarr version is authoritative.
The workbench's **Copy AI prompt** action is separate. It creates a self-contained prompt below 12,000 characters for an ordinary ChatGPT or Claude conversation. It includes the lean contract, bindings, recommended components, and two polished examples. It never tells an offline model to call MCP tools.
The source is in [`.agents/skills/homarr-custom-widget`](https://github.com/homarr-labs/homarr/tree/HEAD/.agents/skills/homarr-custom-widget). `HEAD` follows the repository default branch, so the link is not tied to an unreleased feature branch. Homarr does not provide a ZIP archive.
After publication, run the installation command once. This action registers installation activity.
Then, verify the skills.sh page. Request official maker attribution for `homarr-labs` from skills.sh.
Repository code cannot give this attribution.
## Get MCP resources
- Prompt: `homarr-custom-widget-author`
- Resources: `homarr://custom-widgets/schema`, `components`, `components/{name}`, `examples/{name}`, and `skill`
- HTTP: `GET /api/custom-widgets/authoring-prompt`, `schema`, `components`, and `skill`
The authoring-prompt tool description contains one sentence. The prompt contains the workflow instructions and resource links.
`GET /api/custom-widgets/skill` and `customWidget_getSkill` return a self-contained structured skill payload:
```ts
{
name: "homarr-custom-widget";
version: string;
skillMd: string; // Complete SKILL.md
references: Record<string, string>;
skillsShUrl: string;
sourceUrl: string;
installCommand: string;
}
```
The `homarr://custom-widgets/skill` MCP resource returns the same `SKILL.md` and references once as bundled Markdown.
The component catalog is a compact index of names, categories, safety levels, shared prop names, policies, and example
IDs. Per-component results contain only component-specific props and a reference to shared prop documentation. Use
`customWidget_getSharedProps` once with only the shared prop names needed across the selected components. This avoids
repeating hundreds of global props in every tool result. The catalog is deliberately not embedded in the skill or
clipboard prompt; the skill response still contains the complete authoring, schema, runtime, and security references.
These interfaces do not return an archive URL or archive hash.
## Create a Custom Widget with MCP
1. Load `customWidget_getSkill`, then get the prompt and live schema.
2. Call `customWidget_getComponentCatalog`, followed by `customWidget_getComponent` for only the relevant installed
components and `customWidget_getExample` when one catalog example matches the interaction pattern. Fetch at most
eight component documents before the first validation (or four after a complete example already matches). If shared
props are needed, collect their catalog names and call `customWidget_getSharedProps` once for that set.
3. Read the external API documentation.
4. Build one definition, using `templateLines` for multiline JSX tool input.
5. Validate the definition without saving it and repair every reported issue.
6. Create a preview session.
7. Run `customWidget_previewQuery` for every query returned by the preview and inspect its actual status and data shape. Use
`{}` for load queries; manual queries require exactly the returned primitive `parameterNames`.
8. Test relevant actions in simulation mode.
9. Open the returned preview path with browser tools.
10. Inspect the redacted journal and revise the local candidate until validation, data, and rendering all work. Keep the
exact tested preview candidate locally. Any definition, template, or source change requires a new validation and preview;
earlier query evidence belongs to the previous preview revision.
11. Create the widget from that final tested preview with `customWidget_createFromPreview`. The preview session already carries
the source URLs and encrypted credentials supplied during preview; the create call accepts only its session ID plus an
optional target board and persists that tested preview.
12. Open its returned management path, and use a structured question when the user must choose whether to place it on a board.
13. Otherwise, ask the user to configure the saved definition with the ten-minute Homarr source-setup URL.
14. Create a final preview with the saved definition ID so Homarr loads its stored credentials, then test it again.
15. Update the saved definition if the final verification finds a problem. `customWidget_update` also accepts
`templateLines` for multiline JSX without resetting omitted options.
16. Repeat the procedure for the next widget.
Successful validation returns a compact summary instead of echoing the full widget and template. Preview creation
returns its session, links, and query checklist without repeating the submitted definition. Keep the candidate locally
and reuse it for the next lifecycle step.
Each source-configuration URL applies to one definition or preview and one source. It is short-lived, single-use, and
can only be opened and completed by an authenticated Homarr administrator. The administrator confirms the server URL,
network scope, and required credential fields directly in Homarr. The agent receives only request status and never
receives plaintext credentials. Preview-scoped values expire with that preview; definition-scoped values persist.
Agents can install existing widgets without regenerating them:
1. Search with `customWidget_workshopSearch`.
2. Inspect and validate a result with `customWidget_workshopGet`.
3. Install it with `customWidget_workshopInstall`.
4. Configure deployment-specific URLs and supplied credentials with `customWidget_sourceConfigure`.
5. If the agent does not have those values, call `customWidget_configurationRequestUser` and wait for completion.
6. Test the installed definition, then use `board_addItem` with kind `customApi` and the returned definition ID.
Homarr encrypts the plaintext immediately. Homarr does not return the plaintext.
## Evaluate the copied prompt
Homarr maintainers can run the same self-contained prompt that the workbench copies through the live generation-quality suite:
```sh
OPENROUTER_API_KEY=... pnpm --filter @homarr/custom-widgets test:ai
```
The manifest suite covers nine complex and public-API prompts. Its deterministic fixture-backed cases include a fake
homelab health API, CoinMarketCap's keyless public API, Bored, Agify, and a multi-request Pokédex. Before the configured
model judges a result, the candidate must pass Homarr's real clipboard parser, canonical schema, and deterministic
checks for documented endpoints, authentication, request triggers, query bindings, response fields, and essential
runtime components.
To exercise the real Homarr Assistant lifecycle instead of accepting a returned JSON block, run:
```sh
OPENROUTER_API_KEY=... pnpm --filter @homarr/custom-widgets test:assistant-ai
```
The assistant test exposes the same lazy skill, live schema, component catalog, targeted component references,
validation, preview-query, and create-from-preview tools as the product. It refuses to save a definition that was not
validated exactly or whose preview queries were not all tested. Multi-request fixtures return a different payload for
each documented path and require manual-query parameters. Use `--case=pokedex`, `--case=coinmarketcap-keyless`,
`--case=bored-activity`, or `--case=agify-name` for a focused run.
Both suites default generation and harsh review to OpenRouter's `~deepseek/deepseek-v4-flash-latest` route. They save
tool traces, validated manifests, raw reviews, and detailed category scores under
`packages/custom-widgets/.ai-evaluations/`. These live tests are maintainer-invoked rather than CI-required because
they consume a configured provider account and exercise a nondeterministic model. The repository's ordinary tests
cover the lifecycle enforcement, fixture routing, score calculation, and failure behavior without network access.
Each scenario stops as soon as it passes and has a hard limit of ten generation attempts. Override the models or
per-scenario limit with `OPENROUTER_GENERATOR_MODEL`, `OPENROUTER_JUDGE_MODEL`, and
`CUSTOM_WIDGET_AI_MAX_LOOPS`. Generated artifacts are local and ignored by Git; promote a reviewed, validated result
into the bundled-widget source deliberately.
The reference API check uses a real PokeAPI list/detail request and a read-only Portainer container query when `PORTAINER_BASE_URL` and `PORTAINER_API_KEY` are set. Use `pnpm --filter @homarr/custom-widgets test:reference-apis -- --pokeapi-only` for the public PokeAPI check alone. To exercise start, restart, and stop against a disposable container, also set `PORTAINER_TEST_CONTAINER_ID` and the explicit safeguard `PORTAINER_ALLOW_MUTATIONS=1`; the script restores the container's original running state.
Use `pnpm --filter @homarr/custom-widgets test:reference-apis -- --public-ai-apis-only` to verify the live Bored and
CoinMarketCap response shapes used by the assistant fixtures. The same command also verifies Agify when
`AGIFY_API_KEY` is configured; otherwise it reports the authenticated check as skipped without weakening the
deterministic Agify fixture tests.
@@ -1,29 +0,0 @@
---
sidebar_label: Maintainer architecture
---
# Maintainer architecture
Reusable Custom Widget behavior belongs to `@homarr/custom-widgets`:
- `core` owns schemas, descriptors, migrations, import/export, examples, diagnostics metadata, and prompt generation.
- `jsx` owns AST parsing, shared policy, interpreter, bindings, registry, wrappers, and analysis.
- `runtime` owns renderers and named-request controls through injected ports.
- `server` owns manifests, network execution, caching, limits, preview sessions, and typed domain errors.
- `workbench` owns form contracts, editor analysis, preview UI, response tree, and import review through injected ports.
The API package retains persistence and board authorization adapters. Widgets retain board registration and runtime transport wiring. Next.js routes retain navigation and shells. The domain package must never import API, widgets, or route modules.
## Add a display type
Extend the typed descriptor and discriminated schema, defaults/hydration/serialization, renderer, workbench section, preview, import round-trip fixtures, docs, and translations. Create, edit, import, export, MCP, and preview must all use the same transform.
## Add a JSX component
Classify the upstream export, document safety mode and supported props, add a wrapper if behavior needs constraining, and update registry tests. CI fails when a Mantine upgrade introduces an unclassified export.
## Change networking
Implement policy below the framework-neutral server boundary and expose infrastructure through a typed port. Add tests for permissions, typed substitution, DNS classes, redirects, headers, timeouts, limits, redaction, cache behavior, and action non-retry semantics before wiring the tRPC adapter.
Production files target 300 lines. Algorithmic AST/network modules may use a documented 400-line ceiling. Dependency-boundary, cycle, file-size, translation, generated-artifact, component-classification, focused unit, integration, E2E, accessibility, and docs-build gates protect the architecture. After a Next.js production build, `pnpm check:custom-widget-bundle` also enforces a 768 KiB gzip ceiling across the client chunks referenced by Custom JSX.
@@ -1,36 +1,70 @@
---
sidebar_label: Authoring workbench
---
# Create a Custom Widget
# Authoring workbench
import { CanonicalCustomWidgetExample } from "@site/src/components/custom-widget-code";
import workbenchImage from "./img/workbench.png";
import workbenchMobileImage from "./img/workbench-mobile.png";
import workbenchValidationImage from "./img/workbench-validation.png";
The Custom Widget form is a continuous workflow: choose a display, configure the HTTP connection, configure the display, then test it in the adjacent preview. Each section stays visible so you can revisit earlier choices without losing context.
The workbench exposes **General**, **API sources**, **Requests**, **Options**, **JSX**, and **Preview** sections. Advanced JSON is
available in the manifest and request/option editors. Code and preview sit side by side on desktop; on smaller screens the
**Configure**/**Preview** switch shows one pane at a time and the section navigation scrolls horizontally. Preview sizes display
as **S/M/L** (Compact/Standard/Wide), and fixtures are **Live**, **Loading**, **Empty**, or **Error**.
For multiline JSX, prefer `templateLines` in agent or MCP input and keep the final tested preview as the source of truth before saving.
## Choose a display
<img
src={workbenchImage}
alt="Custom Widget workbench with configuration sections and a live preview"
style={{ maxWidth: "100%" }}
/>
The display grid is the first product decision. Built-in displays are best for common metrics, tables, statuses, and actions. Choose Custom JSX when the layout needs multiple Mantine components, conditional presentation, charts, dates, or named requests.
<img
src={workbenchMobileImage}
alt="Mobile Custom Widget workbench with Configure and Preview switch and horizontally scrollable section navigation"
style={{ maxWidth: "100%" }}
/>
Changing display type preserves shared connection fields and initializes safe display defaults. A Custom JSX template is never blank; new and migrated forms receive the tested starter fixture.
The workbench keeps the configuration and preview together so you can inspect validation diagnostics, the redacted request
journal, fixture data, and preview size before saving. Invalid fields stay marked in the form while the preview explains why it
needs attention.
## Test and preview
<img
src={workbenchValidationImage}
alt="Custom Widget workbench showing a Base URL validation error with Test, preview, and Create actions disabled"
style={{ maxWidth: "100%" }}
/>
The preview has four tabs:
## Manual workflow
- **Widget** renders at compact, standard, or wide board-like sizes.
- **Response** uses a tree to inspect data, copy paths, and insert paths into JSX. The external-response action opens a temporary local blob and never publishes the response.
- **Request** shows the redacted base request, named capabilities, status, duration, and preview journal.
- **Diagnostics** shows template size, named requests, network scope, and blocking or advisory issues.
1. Configure the default API source, network scope, authentication, and credential.
2. Add query/action cards. Values can be literals, widget options, or invocation parameters.
3. Add widget options with their control and default value.
4. Write responsive JSX using `data`, `status`, `options`, and temporary `inputs`.
5. Test and preview, inspect Data and the redacted request journal, then save.
Only GET base requests run automatically. Preview actions are simulated until an administrator explicitly enables live actions for the five-minute preview session. DELETE still requires confirmation.
<CanonicalCustomWidgetExample
id="named-widget-requests"
label="Named queries and actions"
example="requests"
height="420px"
/>
## Keyboard and clipboard
<CanonicalCustomWidgetExample id="widget-options" label="Widget options" example="options" height="360px" />
The JSX and JSON editors support native Ctrl/Cmd+Z and redo, plus visible toolbar controls. Cursor position, character count, and the 50,000-character limit remain visible.
Every option has a default. Supported controls include text, textarea, number, switch, select, multi-select, slider, date, time, color, icon, URL, duration, time zone, and JSON. Dynamic choices use `choicesFrom: { request, itemsPath?, valuePath, labelPath }`; the request must be a load query without invocation parameters, and you cannot provide both static `choices` and `choicesFrom`.
On the list page, paste a complete v3 export with Ctrl+V or Command+V to import it as new. On an edit page, pasting opens a replacement review. Nothing is applied before the origin, methods, credential inheritance, and permissions are shown.
Preview actions are simulated by default and recorded in the journal without contacting the service. Enable **Run actions against the configured API** only when you intend a real mutation; live actions still require configured confirmation.
For AI output, prefer the authoring bundle format: a JSON block containing `"template": "__HOMARR_TEMPLATE__"`, followed by a JSX block. This keeps multiline JSX readable and avoids JSON escape errors.
## ChatGPT or Claude
## Save behavior
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.
Errors block save; warnings do not. Server validation runs again on every create or update. Leaving with dirty changes triggers protection. Secrets omitted during edit remain unchanged, and secrets are never exported.
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**. 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 copied prompt does not include the
latest preview response; use **Preview** > **Data** to inspect it and mention important response fields in your request. The prompt deliberately
recommends a focused Mantine component set, visual hierarchy, responsive layout, theme compatibility, and loading,
empty, failure, and success states.
Connected agents should use the skills.sh skill and retrieve the live schema and individual component metadata only as needed.
@@ -0,0 +1,13 @@
---
title: Component reference
sidebar_position: 3
tags: [Custom Widgets, JSX, Mantine, Components]
---
import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-component-reference";
# Custom JSX component reference
Search the components and properties exposed by Homarr's safe Custom JSX runtime. The live schema and diagnostics remain authoritative: properties marked as blocked cannot be passed from widget code, and denied components are shown only for explanation.
<CustomJsxComponentReference />
@@ -1,49 +1,68 @@
---
sidebar_label: Custom JSX language
---
# Use the safe JSX runtime
# Custom JSX language
Templates are JSX expressions interpreted inside a widget-local safety and render budget. They can read:
Custom JSX is a declarative expression language, not browser JavaScript. Homarr parses an explicit AST subset and emits React elements from a classified component registry. Templates cannot access browser globals, native prototypes, constructors, refs, portals, raw event handlers, or direct `fetch`.
- `data.requestId` — the latest response
- `status.requestId` — `{ loading, ok, status, statusText, error }`
- `options.name` — saved widget configuration
- `inputs.name` — temporary values from declarative `bind`
## Data and rendering
The v2 manifest requires a `default` source and supports up to eight sources, 64 requests, and 64 options. Source IDs are
case-insensitively unique. Each request allows up to 32 static headers and 32 invalidation targets; a widget can contain
up to 24 secrets, each up to 8,192 characters, and templates are limited to 50,000 characters. Validation reports the
exact field when a limit is exceeded.
The base GET response is available as `data`. Safe member access, conditionals, arrays, object literals, and interpreter-created collection callbacks can compose displays:
Authoring input accepts either `template` or `templateLines`, not both. `templateLines` accepts 1–2,000 lines, each up to
10,000 characters; after joining lines with newlines, the complete template must still fit the 50,000-character limit.
The safe interpreter also bounds JSX evaluation: AST depth 64, 25,000 operations, 4,000 collection items, 10,000 rendered
nodes, and 200,000 characters in one string or rendered value. API responses are bounded separately before they reach the
template.
Use normal Mantine layouts, typography, feedback, dates, and charts. Official compound names such as `Card.Section`, `Tabs.List`, and `Radio.Group` are supported. Ordinary safe props pass through; a dangerous capability produces an explicit error.
Unsafe URLs, callback props, and unapproved style properties are stripped. Response data is deep-sanitized and budgeted
before it reaches `data`, so treat it as untrusted input even when the source is private.
## Temporary inputs
```jsx
<Stack gap="xs">
{data.services
.filter((service) => service.visible)
.map((service) => (
<Group key={service.id} justify="space-between">
<Text>{service.name}</Text>
<Badge color={service.online ? "green" : "red"}>{service.status}</Badge>
</Group>
))}
<Stack>
<TextInput bind="search" defaultValue="" label="Search" />
<SubFetch requestId="search" params={{ query: inputs.search }}>
{(results, meta) => (
<Stack>
{(results ?? []).map((item) => (
<Text key={item.id}>{item.name}</Text>
))}
</Stack>
)}
</SubFetch>
</Stack>
```
Properties are resolved own-only. Reflective names such as `constructor`, `prototype`, `__proto__`, `caller`, `callee`, `arguments`, `bind`, `call`, and `apply` are rejected even when computed or Unicode escaped.
Bound values live only in React memory while the widget is mounted. They are not saved to the database, board options, exports, browser storage, or `localStorage`. Remounting resets them. Components can share a binding when their adapter types agree.
## Components and props
## Expression language
Every Mantine export is classified as allowed, wrapped, or denied. The [generated component reference](./#component-reference) is authoritative. Props are allowlisted per component; unsupported props produce warnings and are ignored. Polymorphic roots, unsafe URLs, portals, positioning escapes, refs, and function props are stripped.
Use expressions, conditionals, optional chaining, arrays, objects, template strings, safe standard helpers, and expression callbacks for `map`, `filter`, `sort`, and trusted runtime slots. Callback blocks, authored `const`, IIFEs, authored recursion, arbitrary functions, imports, hooks, refs, raw events, browser requests, `eval`, and bigint are blocked.
`SubData` is a convenience for simple response paths. Prefer the render-child pattern for rich nested results:
Bounded regex literals are supported for string matching, splitting, search, and replacement. Regex literals are limited to
128 characters and flags `gimsu`; lookarounds, backreferences, repeated alternatives/groups, unsupported flags, and more
than one variable quantifier are rejected.
```jsx
<SubFetch requestId="detail" params={{ id: data.selectedId }}>
{(detail, meta) => (
<Stack>
<Title>{detail.name}</Title>
<Text>HTTP {meta.status}</Text>
</Stack>
)}
</SubFetch>
```
## Runtime components
## Safe helpers and budgets
- `SubFetch` runs a named query. Automatic queries run when the widget loads; set `trigger="manual"` to defer one and pass
primitive `params` for its invocation references. With `triggerContent`, any content node (commonly a card or image) becomes
the accessible click and keyboard trigger instead of showing the default load button.
- `SubFetch` with `trigger="manual"` keeps its result local to that instance; it does not populate the shared
`data.requestId` or `status.requestId` roots. The child callback receives `(result, meta)`, while loading, errors, and
retry are handled by `SubFetch`. Provide both `triggerContent` and `triggerAriaLabel` when custom content launches it.
- `ActionButton` and `ToggleSwitch` run actions only when the widget is mounted outside edit mode. A toggle needs both
enabled and disabled request parameters and rolls back its visual state if the action fails.
- `RefreshButton` renders a compact, accessible icon control that invalidates the client&apos;s load-query cache so active load
queries can refetch; it does not execute a request itself. It is disabled when the widget has no active item or preview
session, or while the board is in edit mode.
- `Icon`/`TablerIcon` resolves installed safe icons.
Bindings include restricted conversion, string, array, object, JSON, math, URI, and pure date helpers. Native `Date` objects and native prototypes are not exposed.
Templates are limited to 50,000 characters, 10,000 rendered nodes, and explicit AST-depth, operation, collection, and string budgets. Budget errors stop only the Custom JSX renderer and are contained by its error boundary.
Always include responsive loading, empty, error, and success states. Runtime failures remain inside the widget tile.
@@ -1,66 +0,0 @@
---
sidebar_label: Extension recipes
---
# Custom Widget extension recipes
These recipes keep every authoring and runtime path on the same typed source of truth. Run `pnpm check:custom-widgets` after each structural change and add focused package tests before changing an adapter.
## Add a built-in display type
1. Add the discriminated configuration schema in `@homarr/custom-widgets/core`.
2. Add one descriptor with its icon, category, localized metadata keys, defaults, hydration, serialization, and validation.
3. Add the renderer to the runtime display map. It must consume normalized display data and injected actions only.
4. Add a bounded workbench section that edits the descriptor-owned form fields.
5. Add one fixture and assert create, edit, v2/v3 import, export, MCP, and preview round trips against it.
6. Add translation keys and update the display table in the user guide.
Do not add route-specific conversion code. `buildDisplayFormValues` and `buildDisplayConfigFromFormValues` are the only form conversion boundary.
## Add or update a JSX component
1. Classify the Mantine export as `allowed`, `wrapped`, or `denied` in the component registry. Denied entries require a reason.
2. Declare supported props and subcomponents. Function props, refs, portals, polymorphic roots, and unsafe positioning stay denied unless a wrapper removes the unsafe behavior.
3. Implement wrappers in the JSX package and register them through the package runtime-component factory.
4. Add analyzer and renderer tests for accepted props, stripped props, reflective-property attacks, and the rendered-node budget.
5. Regenerate the docs reference and verify the editor completion and AI prompt include the same entry.
The component-classification test must pass against the locked Mantine version. Never expose an upstream namespace or native prototype as a shortcut.
## Add a named-request capability
1. Extend the request schema and manifest renderer in `core` and `server` without accepting a raw runtime URL, method, header, or body.
2. Enforce permission, network scope, redirect, DNS, header, body, response, concurrency, and rate policies in the framework-neutral server service.
3. Expose infrastructure through a typed server port. Translate domain errors only in the API adapter.
4. Add the control to runtime through the injected request port. Queries use query semantics; actions require a gesture and use mutation semantics.
5. Add preview simulation and redacted-journal coverage. Live actions remain an explicit administrator-only browser choice.
6. Update MCP validation and simulation tools without exposing stored secrets or unrestricted action execution.
Tests must prove actions are never mounted automatically, polled, retried, cached, or redirected. DELETE must retain full permission and confirmation.
## Add a workbench feature
1. Put reusable state, analysis, or presentation in `@homarr/custom-widgets/workbench`.
2. Receive localized messages and typed callbacks as props. The package cannot import the tRPC client, Next.js navigation, notifications, or Homarr widget adapters.
3. Keep the Next.js component as a transport and page-shell adapter.
4. Cover keyboard operation, focus order, reduced motion, 320/768/desktop layouts, dirty-state behavior, and error-boundary recovery.
5. Keep each production module below 300 lines and preserve the continuous two-pane form behavior.
## Change examples or AI authoring
Examples live as typed shared fixtures. Tests, snippets, documentation data, and the AI prompt consume those fixtures. AI output uses an authoring bundle: a JSON block with `__HOMARR_TEMPLATE__`, followed by a readable multiline JSX block. Do not require agents to escape an entire template into one JSON string.
After changing a fixture, validate every example, render it, regenerate the component reference, and run the prompt snapshot tests. MCP agents must be able to read a widget, patch template lines with a revision, validate, preview named queries, simulate actions, inspect the journal, and update the definition without receiving secret values.
## Required checks
```bash
pnpm check:custom-widgets
pnpm test
pnpm test:e2e
pnpm turbo typecheck --affected
pnpm turbo lint --affected
pnpm turbo build --filter=@homarr/docs
```
Also run the component-classification, generated-reference drift, bundle-budget, accessibility, and focused package/API suites configured by the repository. A change is incomplete if any create, edit, import, export, MCP, preview, or board-runtime path uses a separate transform.
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

+100 -164
View File
@@ -1,204 +1,140 @@
---
tags:
- Custom Widgets
- API
- Widgets
- JSX
title: Custom Widgets
sidebar_label: Overview
tags: [Custom Widgets, API, JSX, MCP]
---
import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-component-reference";
import { BundledCustomWidgetGallery, CustomWidgetCodeExample } from "@site/src/components/custom-widget-code";
export const completeManifest = `{
"$schema": "homarr-custom-widget-v2",
"name": "Service status",
"sources": {
"default": { "name": "Service API", "baseUrl": "https://status.example.com", "networkScope": "public", "auth": "none" }
},
"requests": { "status": { "path": "/api/status" } },
"options": {},
"template": "<Badge color={data.status?.ok ? 'green' : 'red'}>{data.status?.label ?? 'Unknown'}</Badge>"
}`;
# Custom Widgets
Custom widgets connect a dashboard item to an HTTP API without compiling a new Homarr widget. Administrators choose the endpoint and authentication, then use a built-in display type or a Custom JSX template to render the response.
:::warning Experimental feature
## Guides
- [Authoring workbench](./authoring): create, edit, preview, paste, undo, import, and export.
- [Custom JSX language](./custom-jsx): supported syntax, components, bindings, diagnostics, and limits.
- [Named requests and security](./requests-and-security): permissions, network scopes, request manifests, and server protections.
- [AI and MCP authoring](./agent-authoring): an iterative agent workflow that validates and previews before saving.
- [Compatibility and migration](./migration): v1/v2 JSX and v2/v3 import behavior.
- [Troubleshooting](./troubleshooting): common editor, preview, permission, and networking failures.
- [Maintainer architecture](./architecture): package boundaries, extension points, and required tests.
- [Extension recipes](./extension-recipes): step-by-step changes for displays, JSX components, requests, and workbench features.
:::caution
A custom widget can expose data or perform actions with the credentials configured for it. Import definitions only from sources you trust and review every network capability before using the widget on a shared board.
This feature is experimental and will probably change. If you encounter bugs, need more capabilities, or find something confusing, tell us in the [official discussion thread](https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets). Your feedback directly shapes what comes next. Happy hacking!
:::
## Create a widget
Custom JSX v2 is Homarr's only Custom Widget format. A definition contains keyed API sources, keyed requests, optional widget options, and one safe JSX template. Credentials are encrypted separately and never exported.
Open **Management → Custom Widgets**, select **Add**, and configure:
- **Name, description, and icon**: identify the widget in management and the widget picker.
- **URL**: the full HTTP or HTTPS base endpoint.
- **Authentication**: none, bearer token, basic authentication, API-key header, or API-key query parameter.
- **Display type**: a built-in renderer or Custom JSX.
Use **Test** to run the current GET request. The preview workbench separates the rendered widget, sanitized response tree, redacted request journal, and diagnostics. It marks sample data as stale whenever the connection or display configuration changes after a test.
Testing creates a five-minute, administrator-bound preview session for unsaved named queries. Action controls are simulated by default. An administrator can explicitly enable live actions for that session; DELETE still requires confirmation.
## Display types
| Display type | Use it for |
| ---------------- | --------------------------------------------------------- |
| Single Value | One metric with a label and unit |
| Key Value | Several labeled values |
| Table | Arrays of records |
| Stat Grid | Compact colored metrics |
| Progress Bars | Capacity, quota, and completion |
| Status Indicator | Health and availability |
| Count Grid | Compact counters |
| Raw | Sanitized JSON inspection |
| Action Button | One explicit API action |
| Custom JSX | A declarative Mantine layout with optional named requests |
## Custom JSX workbench
The JSX editor provides syntax highlighting, formatting, undo/redo buttons and Ctrl/Cmd+Z shortcuts, a searchable component reference, character and cursor status, and diagnostics. Errors block saving; warnings identify behavior that still receives server-side validation. Templates can contain up to 50,000 characters and render up to 10,000 nodes.
The template reads the base GET response from `data`:
```jsx
<Stack gap="xs" p="sm">
<Text fw={600}>{data.name}</Text>
<Badge color={data.online ? "green" : "red"}>{data.status}</Badge>
</Stack>
```
The **Copy AI prompt** action is available on desktop and mobile. Its prompt, component list, and examples are generated from the same metadata used by the renderer, so the reference stays aligned with the installed Homarr version.
### JSX API versions
- **Version 2** is the default. Network access is declared in a named-request manifest and enforced by the server.
- **Version 1** remains available for display-only templates. Inline network components are disabled until the definition is reviewed and migrated.
Definitions without `jsxApiVersion` are treated as version 1. Display-only templates can be migrated by selecting version 2 and saving an empty request list.
## Named requests
Version 2 templates never choose an arbitrary URL, method, header, or body at runtime. An administrator declares each allowed operation once, and the template references its `requestId` with typed parameters.
```json
{
"type": "customJsx",
"jsxApiVersion": 2,
"networkScope": "public",
"template": "<SubFetch requestId=\"service-detail\" params={{ id: data.id }}><SubData path=\"status\" as=\"Badge\" /></SubFetch>",
"requests": [
{
"id": "service-detail",
"kind": "query",
"method": "GET",
"pathTemplate": "/api/services/{id}",
"parameters": { "id": "string" },
"auth": "inherit",
"minimumBoardPermission": "view",
"cacheTtlSeconds": 30
}
]
```ts
interface HomarrCustomWidgetV2 {
$schema: "homarr-custom-widget-v2";
name: string;
description?: string;
iconUrl?: string;
sources: Record<string, CustomWidgetSource>;
requests: Record<string, CustomWidgetRequest>;
options?: Record<string, CustomWidgetOption>;
template: string;
}
```
Path placeholders use `{parameter}` and are URL-encoded by the server. JSON body placeholders use `{ "$param": "parameter" }` and preserve the declared string, number, or boolean type.
### Design with AI
`SubData` remains available for simple paths. For richer nested output, pass an interpreter-created render child; it receives the response data and safe status metadata:
The **Design with AI** action is available while creating or editing a custom widget. Describe what the widget should show and,
when [Homarr Assistant](../assistant) is enabled, send the request directly from the form. The generated prompt includes the
widget schema and current non-secret configuration, but not the latest preview response. It proposes the matching create or
update tool for your approval.
```jsx
<SubFetch requestId="service-detail" params={{ id: data.id }}>
{(detail, meta) => (
<Stack gap="xs">
<Badge color={detail.online ? "green" : "red"}>{detail.status}</Badge>
<Text size="xs">HTTP {meta.status}</Text>
</Stack>
)}
</SubFetch>
```
Use the preview's **Data** tab to inspect the endpoint's real response shape and mention important fields in your request; the
copied prompt does not automatically include the latest preview response. Saved secrets are never added to the prompt. **Copy to
clipboard** remains available when the Assistant is disabled or when you prefer another AI service.
| Component | Request kind | Behavior |
| --------------- | ------------ | ------------------------------------------------------------- |
| `SubFetch` | `query` | Loads a named GET request automatically or manually |
| `ActionButton` | `action` | Runs a named POST, PUT, PATCH, or DELETE after a user gesture |
| `ToggleSwitch` | `action` | Passes declared on/off parameters and rolls back on failure |
| `RefreshButton` | None | Refreshes the parent GET response |
<CustomWidgetCodeExample
id="complete-widget-manifest"
label="Complete manifest"
code={completeManifest}
height="360px"
/>
Queries require board **view** permission. POST, PUT, and PATCH actions require **modify** by default. DELETE requires **full** permission and confirmation. Allowing viewers to execute a non-DELETE action requires explicit administrator approval. Anonymous viewers cannot execute actions.
The browser renders JSX. Homarr's server executes named requests, applies board permissions and network protections, and injects credentials. The browser never receives source credentials.
### Network scopes
## Permissions
| Scope | Allowed destinations |
| ---------- | ------------------------------------------------------------ |
| `public` | Public IP addresses only |
| `private` | Public addresses plus RFC1918 and IPv6 ULA private addresses |
| `loopback` | Public, private, and loopback addresses |
Custom Widget management is an administrator-only surface. Only a Homarr administrator can create, import, migrate,
preview, publish, install, configure, or write secrets, including through HTTP authoring resources or MCP. An API key
only lists Custom Widget MCP tools, prompts, and resources when its owner is an administrator.
Link-local, cloud-metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped bypass addresses remain blocked in every scope.
Adding or configuring a Custom Widget placement requires both administrator access and permission to modify that
board. Duplicating a board that contains Custom Widgets also requires an administrator. A non-administrator with
board-modify access can move, resize, change presentation settings, or remove an existing placement, but cannot add one,
change its definition, refresh interval, or Custom Widget options.
### Example with a named query
Once an administrator has placed a widget, ordinary board permissions apply to rendering and requests. Authorized
non-administrators can use it on private boards, and queries can render for anonymous viewers on public boards.
Mutations remain authenticated and enforce each request's view, modify, or full permission.
The editor and AI prompt include a shared Pokédex fixture that loads details only for the visible page. Its named request is equivalent to:
## Authoring paths
```json
{
"id": "pokemon-detail",
"kind": "query",
"method": "GET",
"pathTemplate": "/api/v2/pokemon/{name}",
"parameters": { "name": "string" },
"auth": "none",
"minimumBoardPermission": "view",
"cacheTtlSeconds": 300
}
```
- [Create a Custom Widget](./authoring) visually or with one copied AI prompt.
- [Use the JSX runtime](./custom-jsx) for data, Mantine components, temporary inputs, and diagnostics.
- [Browse the supported component reference](./component-reference).
- [Configure requests and security](./requests-and-security).
- [Use MCP and the skills.sh skill](./agent-authoring).
- [Correct problems](./troubleshooting).
## Agent authoring through MCP
The workbench keeps one continuous layout with visual source, request, and option editors. Advanced raw JSON remains available for experts and AI paste. Invalid values stay inline and disable both preview and Save/Create until they are corrected.
Administrators can use Homarr's MCP server to iterate on custom widgets without bypassing validation or network controls. A typical agent workflow is:
Exports, imports, the official Workshop client, and MCP validate the same schema. PocketBase stores community
submissions without interpreting their content, and Homarr validates them before installation. Widgets saved in the v1
format remain visible to administrators with a **Migration required** badge after upgrading. Use **Copy migration
prompt**, give the redacted prompt to an LLM, copy its single complete JSON block, then select **Paste migrated widget**
from that widget's action menu. Homarr reads the `template` value into the JSX editor automatically. The prompt keeps safe
request metadata but deliberately omits the v1 display configuration because legacy free-text and JSX can contain
hard-coded credentials. Add a credential-free description of the widget's visible layout and behavior when sending the
prompt.
1. Call `customWidget_schema` to inspect the current import format.
2. Generate a complete `homarr-custom-widget-v3` draft.
3. Call `customWidget_validate` and correct every returned issue.
4. Call `customWidget_preview` to test the base GET request through Homarr's hardened executor.
5. Use `customWidget_previewQuery` for named queries and `customWidget_simulatePreviewAction` to verify action parameters without sending mutations.
6. Inspect `customWidget_previewJournal`, then validate again.
7. Call `customWidget_import`, or read an existing widget with `customWidget_byId` and apply a focused `customWidget_update`.
Homarr keeps the original v1 definition and encrypted secret as a compatibility backup even after the validated v2
replacement is created. The v2 record uses the same stable ID and takes precedence. The archived v1 copy stays hidden
while the replacement exists; deleting the v2 record surfaces the v1 entry as migration-required again. Automatic
legacy deletion is intentionally unavailable during v2 stabilization. Credentials are never included in prompts,
logs, or exports.
MCP preview actions are simulation-only. Enabling live preview actions remains an explicit administrator choice in the browser workbench. Stored secret values are never returned by MCP; omit `secrets` when updating an existing widget to preserve them.
For administrators adding widgets while editing a board, the Custom Widgets section includes **Import from
Workshop**. Non-administrators do not see the Custom Widget picker or Workshop actions. Selecting **Install widget**
opens a two-step modal flow: first review the Workshop entry and its complete source in the read-only editor, then
confirm API sources and credentials. After import, the new definition appears in the picker. Its tile waits for the board
to be saved before server requests run.
## Component reference
## Import and Workshop
Custom JSX exposes a classified subset of Mantine Core, Charts, Dates, and Homarr components. Allowed components render directly. Wrapped components receive safe props or read-only behavior. Denied components remain in the registry so dependency upgrades cannot silently expand the template surface.
The install flow always links to the corresponding public Workshop entry using `WORKSHOP_WEB_URL`. Import confirmation shows origins, authentication modes, network scopes, methods, actions, and permissions. Every source has a suggested URL, but private and loopback sources must be confirmed or replaced for the current Homarr installation. The same screen accepts API keys, tokens, usernames, and passwords; credentials remain optional at install time and missing values stay visible on the Custom Widgets page.
<CustomJsxComponentReference />
For example, installing **Tautulli Activity** asks for both the user's Tautulli URL and API key. A stable public source such as PokéAPI keeps its published URL without creating unnecessary setup work.
This table and the searchable component list in the editor are generated from Homarr's runtime registry.
Workshop publishes technically valid widgets immediately; users can vote or report them, and Workshop moderators can
delete submissions. Community moderation is separate from Homarr administration and is not a security boundary.
Publishers should replace personal private-network hosts with a clear service placeholder before publishing. Homarr
performs definitive validation again at install time.
Component props are filtered. Polymorphic roots, portals, refs, unsafe positioning, raw event handlers, and `dangerouslySetInnerHTML` are not exposed. Direct browser globals and direct `fetch` calls are unavailable, while ordinary text containing the word “fetch” is valid.
Connected agents can search, inspect, and install Workshop widgets with `customWidget_workshopSearch`, `customWidget_workshopGet`, and `customWidget_workshopInstall`. After installation they can set a source URL and credentials with `customWidget_sourceConfigure`, or create a secure user-facing setup URL with `customWidget_configurationRequestUser`. Secret values are never returned through MCP.
Safe bindings include conversion helpers, a restricted `Math` and `JSON` surface, object and array helpers, URI encoding helpers, and pure date formatting helpers. Collection callbacks and rendered output are subject to interpreter budgets.
These tools are only exposed to API keys owned by Homarr administrators. Opening and completing a source-configuration
URL also requires the same authenticated administrator; the URL is short-lived, single-use, and redacted.
## HTTP protections and limits
## Reliability and rollback
All base, preview, query, and action requests use the same server executor:
Custom Widget response caches and action invalidation use shared Redis state, so multiple Homarr replicas see the same
versioned cache generation. Deployments must point every replica at the same Redis service.
- Only HTTP and HTTPS are accepted. URL credentials and fragments are rejected.
- Named paths must stay on the base endpoint origin.
- DNS results are validated against the selected network scope and pinned for the request.
- Queries can follow up to three revalidated same-origin redirects. Actions do not follow redirects.
- Authorization, cookie, host, forwarding, proxy, content-length, and hop-by-hop headers cannot be supplied by a template.
- Request bodies are limited to 10 KiB. Decompressed responses are limited to 1 MiB, 32 JSON levels, and 50,000 JSON nodes.
- GET requests can be cached and deduplicated. Actions are never cached, polled, or retried automatically.
Before upgrading, back up the database and `SECRET_ENCRYPTION_KEY`; a v1 binary can still read preserved v1 tables
during application rollback. Widgets created only in v2 may be unavailable to the rolled-back binary, but do not
replace the v1 backup.
## Import and export
## Bundled examples
New exports use `"$schema": "homarr-custom-widget-v3"`. Version 2 imports remain accepted. Secrets are never exported and must be entered again after import.
Homarr seeds five disabled examples without credentials and never overwrites an edited seed. They use the same public
contract. Tautulli Activity is one of the bundled examples preserved from Homarr's AI authoring evaluation:
Review the base origin, network scope, named request methods, inherited authentication, and required permissions before saving an imported widget. A version 1 template that contains inline network components stays disabled until it is converted to named requests.
AI-generated Custom JSX can also be pasted as a Homarr authoring bundle: one fenced JSON block with `"template": "__HOMARR_TEMPLATE__"`, followed by one fenced JSX block containing the readable multiline template. Homarr combines the blocks before validation, avoiding JSON newline and quote escaping. Raw JSON imports remain supported.
On the custom-widget list, paste a complete export with Ctrl+V or Command+V to open the import review and create it as a new definition. On an edit page, the same shortcut opens a replacement review and fills the existing form after confirmation. Pasted definitions never include secrets.
<BundledCustomWidgetGallery />
@@ -1,19 +0,0 @@
---
sidebar_label: Compatibility and migration
---
# Compatibility and migration
Homarr preserves existing display definitions while moving network-enabled Custom JSX to the named-request model.
## JSX API versions
Missing `jsxApiVersion` means v1. Display-only v1 templates can be opened and saved as v2 with an empty request manifest. Inline `url`, `method`, `headers`, and body props are inert in v2.
Static v1 network components can be converted by creating a named request and replacing inline capability props with `requestId` and typed `params`. Dynamic target construction requires an administrator to design an explicit path template and parameter list. Existing non-GET base definitions stop polling and show a network-review state.
## Import versions
Exports use `homarr-custom-widget-v3`; v2 imports remain accepted. Import migration normalizes display configuration, preserves v1 semantics, and never imports secret values. The review screen must be accepted before persistence.
Test migration with the same base response and compare rendered output, query results, action simulation, permissions, and journal entries before enabling the migrated definition on shared boards.
@@ -1,32 +1,67 @@
---
sidebar_label: Requests and security
---
# Configure requests and security
# Named requests and security
Most widgets use a single source named `default`; the manifest must contain it. Extra keyed sources are available for widgets
that combine APIs. Source IDs are case-insensitively unique. Base URLs must use HTTP(S), contain no embedded credentials,
query string, or fragment, and sources can be `public`, `private`, or `loopback`. Authentication is `none`, `bearer`,
`basic`, an API-key header, or an API-key query parameter. Credentials belong to the widget but remain outside its manifest.
Source origins are fixed by the definition and source-setup flow; widget options and invocation parameters cannot redirect a
request to a different origin.
Custom JSX v2 uses a saved request manifest. Runtime components pass only a `requestId` and declared primitive parameters; they cannot change the URL, method, headers, authentication, or body shape.
## Named requests
## Capability kinds
Requests are keyed by ID. `source`, `kind`, `method`, `trigger`, `auth`, and `permission` have sensible defaults, so a load query can be as small as:
`SubFetch` references a GET `query`. `ActionButton` and `ToggleSwitch` reference a POST, PUT, PATCH, or DELETE `action`. Base data is GET-only. Actions require an explicit gesture and are never polled, cached, retried automatically, or run on mount.
```json
{ "status": { "path": "/api/status" } }
```
Default board permissions are view for queries, modify for POST/PUT/PATCH, and full for DELETE. Viewer actions require explicit high-risk approval; anonymous users cannot execute actions. DELETE always requires confirmation.
The request `path` is appended to the source `baseUrl`, including a base path such as `/api/v2`.
## Parameter substitution
GET, POST, PUT, and PATCH work for queries and actions. DELETE is restricted to actions so it can never run while a
board loads. Kind controls the remaining behavior: load queries run automatically, manual queries run through
`SubFetch`, and actions run only from `ActionButton` or `ToggleSwitch`.
Path placeholders use `{name}` and are URL-encoded by the server. Runtime parameters must exactly match all declared names and types. JSON bodies substitute `{ "$param": "name" }` recursively and preserve string, number, or boolean types.
Bind values directly where they are used:
## Network boundary
```json
{
"containers": {
"path": "/api/endpoints/{option:endpointId}/docker/containers/json",
"query": { "all": { "$option": "showAll" } }
},
"restart": {
"kind": "action",
"method": "POST",
"path": "/api/endpoints/{option:endpointId}/docker/containers/{param:id}/restart",
"invalidates": ["containers"]
}
}
```
Named paths stay on the base URL origin. HTTP and HTTPS are accepted; URL credentials and fragments are rejected. DNS answers are classified and pinned. Public, private, and loopback scopes progressively allow destinations, but link-local, metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped addresses are always blocked.
`{option:name}` and `$option` read saved widget options. `{param:name}` and `$param` read invocation-time `params`. Load queries cannot use invocation parameters. Homarr infers the exact parameter names and primitive values from these references, rejecting missing or extra invocation params at the request boundary.
Queries may follow three revalidated same-origin redirects. Actions do not follow redirects. Credentials never cross origins. Reserved authentication, cookie, host, forwarding, proxy, content-length, security, and hop-by-hop headers cannot be declared.
Path placeholders and query values must resolve to text, numbers, or booleans. `$option` and `$param` references may appear
recursively in JSON bodies; invocation parameters remain primitive, and load queries cannot use `$param`. JSON bodies can
also use structured `json` and `multiSelect` option values, for example `{ "entity_ids": { "$option": "entities" } }`.
## Limits
## Protection
- Request body: 10 KiB UTF-8.
- Decompressed response: 1 MiB, 32 JSON levels, 50,000 JSON nodes.
- Per user/item: 60 queries, 10 actions, and 3 deletes per minute.
- Concurrency: 4 per user/item and 8 per definition.
Every request uses Homarr's existing server executor: same-origin source enforcement, network scope, DNS and redirect validation, SSRF protection, board permissions, rate and request limits, body and response limits, timeouts, and encrypted credential injection. Static headers cannot override credentials, cookies, routing, proxy, forwarding, or hop-by-hop headers.
Successful GETs can use manifest cache TTLs and single-flight deduplication. Logs contain redacted origin/method/error metadata, never secrets, request bodies, or query values.
The executor caps request bodies at 10 KiB and response bodies at 1 MiB. Parsed JSON is limited to depth 32 and 50,000 nodes;
each network hop has a ten-second timeout, queries follow at most three same-origin redirects, actions do not follow redirects,
and the total request deadline is 45 seconds.
The limiter allows four concurrent requests per user/item and eight per definition. Per minute, a user/item can make up to 60
queries, 10 actions, or 3 DELETE actions; a definition can receive up to 240, 40, or 12 of those request categories.
Queries default to view permission and actions to modify. DELETE always requires full permission and confirmation. Actions are never cached, retried, or run on load.
Management and runtime are intentionally separate. A Homarr administrator with board-modify permission places and
configures the widget. After placement, query and action authorization follows the request's board permission. Public
boards may execute queries for anonymous viewers, but mutations stay authenticated. This includes shared-cache
invalidation: `RefreshButton` is disabled only without an active item or preview session, or in edit mode, while ordinary
scheduled query rendering remains available to anonymous viewers.
Responses use bounded, versioned shared-Redis cache keys. Action invalidation advances the shared version so every
Homarr replica stops serving the previous response without scanning or deleting a key prefix.
@@ -1,33 +1,84 @@
---
sidebar_label: Troubleshooting
---
# Correct Custom Widget problems
# Troubleshooting
## Correct an unavailable widget
## The preview says the template is required
The definition does not exist. If it was deleted, remove the unavailable tile from the board.
New Custom JSX forms always include the starter fixture. If an imported definition has an empty template, reopen its import review; malformed imports must be corrected rather than previewed as JSX.
If the Custom Widgets page shows the definition with a **Migration required** badge, an administrator can open its
action menu and select **Copy migration prompt**. Give that redacted prompt to an LLM, then import the resulting v2 JSON
from the same menu. The LLM returns one complete JSON block; copy it once and select **Paste migrated widget**. Homarr
places its `template` value into the JSX editor automatically. Because arbitrary v1 display text or JSX may contain
credentials, the prompt omits that display configuration; describe the old widget's visible layout and behavior without
including secrets. Other viewers see **Contact an administrator** instead of a migration link.
## A prop is ignored
Homarr keeps the v1 definition and encrypted secret after migration. The validated v2 replacement uses the same ID and
takes precedence; the archived v1 record reappears if the v2 replacement is deleted. There is no v1 permanent-delete
action during the v2 stabilization period.
The diagnostics name the component and prop. Check the generated component reference. Unsupported props are intentionally removed; use a supported layout primitive or wrapper prop instead of polymorphic, portal, ref, positioning, or raw handler props.
Otherwise, install or create a Custom JSX v2 definition.
## A request does not run
## Correct an invalid saved definition
Confirm that the component kind matches the manifest: `SubFetch` needs a query; action controls need an action. Parameter names and primitive types must match exactly. Base requests and queries require GET. Actions only run from a gesture, and edit mode disables controls.
The Custom Widgets page isolates a definition that does not conform to the current schema. The other definitions remain available.
## Network scope errors
An invalid definition shows its validation errors. You can delete it.
Resolve the hostname from the Homarr server environment. Select private or loopback scope only when required and understood. Link-local and metadata destinations cannot be enabled by any scope. Redirects must remain on the base origin.
You cannot edit, enable, export, duplicate, publish, or select an invalid v2 definition. Recreate the definition or import a valid definition. Preserved v1 definitions use the separate **Migration required** workflow described above.
## Permission errors
## Correct validation errors
The placed board item is the authorization anchor. A definition ID alone cannot authorize credential use. Verify board view/modify/full access, explicit viewer-action approval, and that the caller is not anonymous.
Read the first schema or JSX diagnostic. Use its path to find the invalid value.
## Stale or failed preview data
Make sure keyed API source and request names are valid and unique.
Connection, authentication, body, method, secret, or manifest changes mark samples stale. Run the GET test again. Background query failures keep prior content visible and offer retry; actions roll toggles back and show error feedback.
Make sure that each named request references an API source. Use explicit `$option` and `$param` references.
## Import paste is ignored
Make sure every option default matches its control. Use the correct request kind for each runtime component.
Paste a complete v2/v3 JSON export or authoring bundle while the list or edit page is focused. Ordinary text and partial JSON are ignored so normal typing is not intercepted.
An incomplete API source URL or icon URL causes a form validation error. The workbench keeps the draft open.
The workbench marks the invalid field while you type. An invalid URL does not replace the form with the error boundary.
An invalid definition disables widget preview and Save/Create. The Data, Options, Actions, and Diagnostics tabs remain available so you can correct it.
The Diagnostics tab shows current schema and JSX errors, accessibility checks, and (after a preview) the redacted request
journal. It does not expose credentials or raw secret values.
## Workshop unavailable
If Workshop is unavailable, check `WORKSHOP_API_URL` and PocketBase health. Widgets that are already installed continue
to work.
## Correct request errors
Check the API source network scope. Check the base URL and credentials.
Check the request method, path, body, direct value bindings, and board permission.
For “invalid placeholder,” use `{option:name}` or `{param:name}`. Load queries can only reference options. The preview journal does not show credentials.
The journal shows the method, request ID, path template, status, and duration.
In a multi-instance deployment, all Homarr replicas must share Redis. Versioned cache invalidation is distributed
through Redis; replicas connected to different Redis databases can serve stale query responses after an action.
## Correct a Mantine prop warning
`UNKNOWN_MANTINE_PROP` is a warning. Homarr passes the prop to Mantine.
Check the prop in the documentation for the installed Mantine version. `BLOCKED_CAPABILITY` identifies an unsafe capability.
Use safe widget options, temporary inputs, or named requests instead.
## Repair an existing item
The widget option definitions changed and the saved values are invalid. Open the item settings.
Correct the marked configuration values. Homarr does not infer widget option migrations.
## Roll back safely
Before an application upgrade, back up the database and preserve the exact `SECRET_ENCRYPTION_KEY`. The v2 schema keeps
the physical v1 definition and secret tables readable by a v1 binary. Roll back the application without running
manual cleanup, verify legacy widgets, and restore the database backup if the migration itself must be reversed.
PocketBase Workshop data is a separate backup and restore operation.
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+129 -69
View File
@@ -8,15 +8,24 @@ tags:
# Model Context Protocol (MCP)
Homarr exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at `/api/mcp/mcp`, enabling AI assistants like Claude, Cursor, VS Code Copilot, and Claude Code to interact with your Homarr instance using natural language.
Homarr exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at `/api/mcp`, enabling AI assistants like Claude, Cursor, VS Code Copilot, and Claude Code to interact with your Homarr instance using natural language.
The endpoint implements the stateless MCP 2026-07-28 protocol and accepts 2025 Streamable HTTP clients through a
stateless compatibility path. It does not create protocol sessions or require sticky sessions. Homarr creates the
server and extracts its tools only after an authenticated request reaches the route. The previous `/api/mcp/mcp` URL
remains available as a compatibility alias, but new clients should always use `/api/mcp`.
With MCP, you can ask your AI assistant to manage apps, integrations, users, boards, Docker containers, media servers, DNS settings, and more - all through Homarr's existing tRPC API, now exposed as 50+ AI-callable tools.
The optional [Homarr Assistant](./assistant) uses these same tool definitions internally.
Unlike external MCP clients, it uses the current Homarr session directly and asks for in-conversation
approval before running mutating procedures.
## Getting Started
The MCP endpoint lives under the **API** page at `Management` → `Tools` → `API` → **MCP** tab.
The MCP endpoint lives under the **API** page at `Management` → `Tools` → `API` → **AI / MCP** tab.
![MCP Instructions Page](./api/img/mcp-instructions.png)
![AI / MCP tab showing the endpoint, API-key warning, and client configuration](./api/img/mcp-instructions.png)
### 1. Create an API Key
@@ -42,7 +51,7 @@ Add the following to your client's MCP configuration file:
{
"mcpServers": {
"homarr": {
"url": "https://your-homarr-instance.com/api/mcp/mcp",
"url": "https://your-homarr-instance.com/api/mcp",
"headers": {
"ApiKey": "<your-api-key>"
}
@@ -73,7 +82,7 @@ Replace `<your-api-key>` with your actual API key. Never commit API keys to vers
"mcpServers": {
"homarr": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-homarr-instance.com/api/mcp/mcp", "--header", "ApiKey:<your-api-key>"]
"args": ["-y", "mcp-remote", "https://your-homarr-instance.com/api/mcp", "--header", "ApiKey:<your-api-key>"]
}
}
}
@@ -92,6 +101,14 @@ MCP supports two authentication methods:
API key authentication is simpler and recommended for most setups. The API key inherits the permissions of the user who created it. Tools that require higher privileges will return permission errors if the key lacks access.
Custom Widget and Workshop authoring are different: their tools, prompts, and resources are exposed only when the API
key owner is a Homarr administrator. Non-administrator keys cannot discover or invoke those capabilities. Creating an
API key never elevates the owner.
OAuth authorization and token requests bind issued credentials to the canonical `/api/mcp` resource. Dynamic client
registration remains available for compatibility with current MCP clients while the ecosystem transitions toward
pre-registered clients and Client ID Metadata Documents.
## Testing the Connection
Verify your MCP endpoint is working:
@@ -99,13 +116,17 @@ Verify your MCP endpoint is working:
```bash
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Accept: application/json" \
-H "ApiKey: <your-api-key>" \
-d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
https://your-homarr-instance.com/api/mcp/mcp
-H "Mcp-Method: server/discover" \
-H "Mcp-Name: homarr" \
-d '{"jsonrpc":"2.0","method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"test","version":"1.0"},"io.modelcontextprotocol/clientCapabilities":{}}},"id":1}' \
https://your-homarr-instance.com/api/mcp
```
A successful response includes the server capabilities and available tools. Use `tools/list` to see all available tools:
A successful response includes the supported protocol versions and server capabilities. Use `tools/list` to see all
available tools. MCP v2 clients add the per-request metadata and `Mcp-Method` / `Mcp-Name` headers automatically; the
abbreviated request below uses the stateless 2025 compatibility path for readability:
```bash
curl -s -X POST \
@@ -113,33 +134,36 @@ curl -s -X POST \
-H "Accept: application/json, text/event-stream" \
-H "ApiKey: <your-api-key>" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
https://your-homarr-instance.com/api/mcp/mcp
https://your-homarr-instance.com/api/mcp
```
Tool catalogs are returned in deterministic order and may be cached privately by the client for five minutes. The
removed HTTP+SSE `/sse` and `/message` transports return `410 Gone`; use the single Streamable HTTP endpoint instead.
## Available Tools
Homarr exposes 50+ tools organized by namespace. Each tool is either a **query** (GET, read-only) or **mutation** (POST, write action).
| Namespace | Example Tools | Type |
| ------------------ | --------------------------------------------------------- | ---------- |
| `app` | List all apps, get app by ID, create/update/delete app | GET + POST |
| `apiKeys` | List API keys | GET |
| `beszel` | Systems status, alerts, historical system metrics | GET |
| `board` | List boards, get board by ID, create/update/delete board | GET + POST |
| `docker` | List containers, inspect container, start/stop/restart | GET + POST |
| `icon` | Search icons | GET |
| `info` | Server health, version info | GET |
| `integration` | List integrations, test connection, delete integration | GET + POST |
| `invite` | List invitations, delete invite | GET + POST |
| `calendar` | Calendar events from media servers | GET |
| `customWidget` | Validate, preview, create, and iteratively update widgets | GET + POST |
| `dnsHole` | DNS hole summary, toggle blocking | GET + POST |
| `downloads` | Active downloads, pause/resume/delete | GET + POST |
| `healthMonitoring` | Resource usage stats | GET |
| `patchmon` | Host patch and security update statistics | GET |
| `mediaRequests` | Approve/decline media requests | GET + POST |
| `mediaServer` | Media server sessions and stats | GET |
| `smartHome` | Execute automations, device states | GET + POST |
| Namespace | Example Tools | Type |
| ------------------ | ---------------------------------------------------------- | ---------- |
| `app` | List all apps, get app by ID, create/update/delete app | GET + POST |
| `apiKeys` | List API keys | GET |
| `beszel` | Systems status, alerts, historical system metrics | GET |
| `board` | List boards, read settings/CSS, create/update/delete board | GET + POST |
| `docker` | List containers, inspect container, start/stop/restart | GET + POST |
| `icon` | Search icons | GET |
| `info` | Server health, version info | GET |
| `integration` | List integrations, test connection, delete integration | GET + POST |
| `invite` | List invitations, delete invite | GET + POST |
| `calendar` | Calendar events from media servers | GET |
| `customWidget` | Validate, preview, create, and iteratively update widgets | GET + POST |
| `dnsHole` | DNS hole summary, toggle blocking | GET + POST |
| `downloads` | Active downloads, pause/resume/delete | GET + POST |
| `healthMonitoring` | Resource usage stats | GET |
| `patchmon` | Host patch and security update statistics | GET |
| `mediaRequests` | Approve/decline media requests | GET + POST |
| `mediaServer` | Media server sessions and stats | GET |
| `smartHome` | Execute automations, device states | GET + POST |
The tools table on the MCP tab is dynamically generated - it always reflects the latest available procedures from your Homarr instance.
@@ -162,58 +186,94 @@ The tools table on the MCP tab is dynamically generated - it always reflects the
- **"How many users are on my Homarr instance?"** - AI lists all users (admin only)
- **"Toggle Pi-hole blocking for 5 minutes"** - AI interacts with DNS controls
- **"How are my servers doing?"** - AI fetches Beszel system stats including CPU, memory, disk, and alerts
- **"Build and refine a custom widget for this API"** - AI validates the draft, tests GET data and named queries, simulates actions, and creates or updates the widget
- **"Build a Custom Widget for this API"** - AI validates and previews one definition before it saves the definition
## Custom Widget Workflow for AI Agents
## Create a Custom Widget with an AI agent
AI agents can create and iterate on custom widgets using these tools in sequence.
An agent creates one `homarr-custom-widget-v2` definition at a time. Start with the native prompt `homarr-custom-widget-author`.
:::tip Editing an existing widget?
Skip to step 5. Use `customWidget_byId` to fetch it and `customWidget_readTemplate` / `customWidget_writeTemplate` to edit its template directly.
:::
The API key owner must be a Homarr administrator. The same rule applies to management, import, migration, preview,
secrets, publication, Workshop installation, and the HTTP authoring resources.
### 1. Get the schema
Load the installed skill before authoring. `customWidget_getSkill` returns the complete `SKILL.md` entrypoint and every
bundled reference in one response, so an MCP client does not need filesystem access or a second documentation fetch.
Then read the live schema and only the component resources required by the design. These resources describe the
installed Mantine components.
Call `customWidget_schema` to receive the JSON Schema for the `homarr-custom-widget-v3` format. This tells the agent what fields are required and their types.
1. Call `customWidget_getSkill`, `customWidget_getAuthoringPrompt`, `customWidget_schema`, and
`customWidget_getComponentCatalog`.
2. Fetch only the relevant installed component documentation with `customWidget_getComponent` and, when useful,
one interaction example with `customWidget_getExample`. Component results contain component-specific props; collect
any needed shared prop names from the compact catalog and fetch them together once with `customWidget_getSharedProps`.
3. Build one Custom Widget definition. Use `templateLines` for generated multiline JSX to avoid a fragile JSON-escaped
template string.
4. Call `customWidget_validate` without saving the definition and repair every returned issue path.
5. Call `customWidget_previewCreate`.
6. Call `customWidget_previewQuery` for every entry in the returned `queries` list. Supply the listed invocation
parameters for manual queries and inspect the real HTTP status and response shape before binding template fields.
7. Test relevant actions with `customWidget_previewAction` in simulation mode.
8. Open the returned `previewPath` or `previewUrl`.
9. Examine `customWidget_previewJournal`.
10. Revise the local candidate and create another preview session until validation, query data, actions, and rendering
are ready.
11. Call `customWidget_create`, supplying any credentials and deployment-specific source URLs the user already provided.
12. Open the returned `managementPath` to review the exact saved definition.
13. If the user must enter the source URL or credentials, call `customWidget_configurationRequestUser` with the new definition ID and wait for completion.
14. Create a final preview with that definition ID so Homarr loads its stored credentials, then test it again.
15. Use `customWidget_update` or `customWidget_templatePatch` for any final saved-definition corrections.
16. Repeat this procedure for the next Custom Widget.
### 2. Draft and validate
Validation and preview creation return compact lifecycle metadata. They do not echo the submitted widget or duplicate
its template as both a string and line array; the agent should retain and reuse its local candidate.
Build a widget JSON object and call `customWidget_validate` with it. The tool returns structured issues (path + message) so the agent can fix and re-validate until the draft is clean.
The agent receives the configuration-request status. The agent does not receive plaintext credentials. A preview-scoped setup URL configures only that temporary preview; use a definition-scoped URL when the values must persist.
### 3. Create the widget
Discovery resources:
Call `customWidget_import` with the validated `homarr-custom-widget-v3` draft. This returns the new widget `id`. Use `customWidget_create` only when you intentionally need its lower-level creation input, including secret fields.
- `homarr://custom-widgets/schema`
- `homarr://custom-widgets/components`
- `homarr://custom-widgets/components/{name}`
- `homarr://custom-widgets/examples/{name}`
- `homarr://custom-widgets/skill`
### 4. Test with real data
An authenticated administrator can use the matching `/api/custom-widgets/authoring-prompt`, `/schema`, `/components`,
and `/skill` endpoints instead of MCP resource URIs. These endpoints are not public schema downloads.
Call `customWidget_preview` with the widget's connection info. This makes a real GET request through Homarr's hardened executor and returns the sanitized response plus a preview session ID.
The Custom Widget tools are:
For Custom JSX v2 widgets with named requests, use `customWidget_previewQuery` to test each named GET query, and `customWidget_simulatePreviewAction` to dry-run actions without network calls.
- `customWidget_schema`
- `customWidget_validate`
- `customWidget_list`
- `customWidget_get`
- `customWidget_create`
- `customWidget_update`
- `customWidget_delete`
- `customWidget_templatePatch`
- `customWidget_previewCreate`
- `customWidget_previewQuery`
- `customWidget_previewAction`
- `customWidget_previewJournal`
- `customWidget_secretSet`
- `customWidget_sourceConfigure`
- `customWidget_configurationRequestUser`
- `customWidget_workshopSearch`
- `customWidget_workshopGet`
- `customWidget_workshopInstall`
- `customWidget_getAuthoringPrompt`
- `customWidget_getComponentCatalog`
- `customWidget_getComponent`
- `customWidget_getSharedProps`
- `customWidget_getExample`
- `customWidget_getSkill`
### 5. Iterate on the template
Custom Widget permissions are not assignable group capabilities. Administrator status is required for the complete
authoring surface. A source-configuration URL is short-lived and single-use, requires the same logged-in administrator
to open and complete it, and never returns plaintext credentials to the agent.
Use `customWidget_readTemplate` to get the current JSX template as plain text, then `customWidget_writeTemplate` to push updates. The write tool accepts either:
Adding a `customApi` item through `board_addItem`, changing its definition or options, and duplicating a board that
contains one also require an administrator with board-modify access. Existing placements continue to render and run
authorized requests according to normal board permissions.
- `template` — a single string (newlines as `\n`)
- `templateLines` — an array of strings (joined with newlines server-side, avoids `\n` escaping)
Homarr applies its request security controls during request execution. These controls include SSRF, DNS, redirect, size, timeout, rate, and permission controls.
Both options validate the template AST before saving. This avoids re-sending the full widget JSON for template-only edits.
For small changes, prefer `customWidget_patchTemplate`. Pass the `revision` returned by `customWidget_readTemplate` and one or more line edits. Homarr rejects stale revisions, applies all edits atomically, and validates the complete template against its named request manifest before saving.
### 6. Update non-template fields
Call `customWidget_byId` to get the full widget, modify fields, and call `customWidget_update` with only the changed fields.
### Available components in templates
Custom JSX templates support 100+ Mantine components plus:
- `<TablerIcon name="server" size={20} color="blue" />` — 100+ curated Tabler icons by kebab-case name (e.g. `"player-play"`, `"alert-triangle"`)
- `<SubFetch>` / `<ActionButton>` / `<ToggleSwitch>` — network-capable components for named requests
- Array methods: `map`, `filter`, `find`, `findIndex`, `some`, `every`, `reduce`, `sort`, `slice`, `flat`, `reverse`, `at`, `pop`, `join`, `includes`, `indexOf` (note: `pop` returns last element without mutating)
- String methods: `toUpperCase`, `toLowerCase`, `trim`, `trimStart`, `trimEnd`, `slice`, `substring`, `includes`, `startsWith`, `endsWith`, `split`, `replace`, `replaceAll`, `repeat`, `padStart`, `padEnd`, `charAt`, `indexOf`, `lastIndexOf`
:::note
All `customWidget_*` tools require admin-level API key permissions. The preview executor enforces rate limits (60 queries/min, 10 actions/min).
:::
Read [Use agents and skills.sh](./custom-widgets/agent-authoring) for the complete procedure and the portable Agent Skill.
+70 -53
View File
@@ -10,109 +10,126 @@ tags:
# Settings
The settings page has a few options that will be applied to the entire Homarr application.
They may require a full page reload for changes to apply.
The settings page applies options to the whole Homarr application.
Some changes need a full page reload before they take effect.
All changes are collected in a single form. Click the **Save changes** button in the save bar at the bottom of the page to apply them. The save bar only appears when you have unsaved changes, and the **Discard** button reverts your changes.
## Analytics
By default, Homarr is collecting anonymous analytics to identify issues and prioritize bugfixes and improvements.
The data is never shared with any third-parties and is fully GDPR & CCPA compliant.
The software being used is [Umami Analytics](https://umami.is/) which is fully open source.
The main server software can be found [here](https://github.com/umami-software/umami) and [this node library](https://github.com/umami-software/node) to perform server side calls to the API.
Homarr uses [PostHog](https://posthog.com/) to collect anonymous usage data.
We collect this data to see what features people ACTUALLY use.
We use this data to decide where we spend our development time.
A full list of the collected data by default can be found here: https://umami.is/docs/metric-definitions.
We do not sell the data. We do not share the data with third parties.
The data does not contain personal information and does not identify you or your installation.
We understand that you might not want Homarr to communicate with our services.
By unchecking the `Send anonymous analytics` checkbox and reloading the full page, all communication by Umami will be disabled and no data will be collected.
If you are paranoid, you can permanently block `umami.homarr.dev` from your DNS / firewall.
Homarr sends the data to our own PostHog server at `https://hog.homarr.dev`.
The server sends the data once a week and when the server starts.
Additionally, you opt in to more data collection to help us provide more useful data.
### What we collect
### Integration data
Homarr collects technical data about your installation:
Enabling this optional data will send the types of integrations (e.g. SABnzbd, Sonarr, ...) and how many you have configured of them.
This will not include any URLs, names, secrets, errors and others. This is disabled by default.
- Your Homarr version
- Your database type (SQLite, MySQL, or PostgreSQL)
- Whether Docker or Kubernetes is enabled
- Your enabled authentication providers
- Your operating system and CPU architecture
- Your default language
- The number of boards, apps, sections, layouts, users, groups, search engines, icon repositories, cron jobs, API keys, invites, sessions, and accounts
- The integration types you use and how many you have of each (for example, Sonarr or SABnzbd)
- The widget types you use and how many you have of each
### Widget data
Homarr does NOT collect:
Enabling this optional data will send the types of widgets (e.g. downloads, media streams, iframe, ...) and how many you have configured of them.
This will not include any URLs, names, secrets, errors and others. This is disabled by default.
- URLs
- Names
- Usernames, emails, or passwords
- Integration secrets or API keys
- Error messages or logs
- Any data that can identify you
### Users data
Each installation gets a random ID. We use this ID only to count unique installations. We cannot use this ID to identify you.
Enabling this optional data will send the count of users and whether you activated SSO.
This will not include any usernames, passwords, emails, URLs, authentication flows or client IDs.
This is disabled by default.
Homarr does not use cookies for analytics. Homarr does not track page views automatically. Homarr only sends the data listed above.
### Disable analytics
Analytics are enabled by default. To disable analytics:
1. Open the Settings page.
2. Uncheck the "Help us improve Homarr" checkbox.
3. Reload the full page.
When analytics are disabled, Homarr does not send any data to PostHog.
You can also set the `NO_EXTERNAL_CONNECTION` environment variable to `true`. This stops all external requests, including analytics. See the [environment variables](/docs/advanced/environment-variables) page for more information.
You can block `hog.homarr.dev` in your DNS or firewall. Homarr cannot send data when this address is blocked.
## Search Engine Optimization (SEO)
Big companies [like Google, Microsoft, Baidu and Yandex](https://de.wikipedia.org/wiki/Web-Index) will use [web crawlers](https://de.wikipedia.org/wiki/Webcrawler) to index the Internet for new websites & pages.
This is desirable in many cases (for example when selling products in an online shop) but can lead to unwanted traffic and attention to your personal dashboard.
Homarr is requesting the crawlers to not index Homarr instances by default for this reason.
If you use Homarr as a public website and want it to be searchable on these search engines, you can disable these settings.
Please note that Homarr doesn't have control over what the crawlers do; it is a mere request to them to not perform these actions but will not guarantee that your website doesn't land on some index.
Search engines use web crawlers to index websites. This can cause unwanted traffic to your dashboard.
Homarr asks crawlers to not index your instance by default. Use these settings to change this request.
Homarr cannot control what the crawlers do. A crawler can ignore the request. Your website can still appear in an index.
### No index
This will ask crawlers to not index your Homarr instance on any indexes.
It is up to the crawler to properly respect this request.
By default, this setting is enabled. Disabling it will allow indexing.
This setting asks crawlers to not index your instance.
This setting is enabled by default. Disable this setting to allow indexing.
### No follow
If indexing is allowed, crawlers will also attempt to follow any links on your page to further "discover" new parts of the Internet and on your website.
By default, this setting is enabled and will ask crawlers to not follow any links.
Crawler of different companies may behave differently when indexing is prohibited and following is allowed.
Crawlers follow links to discover new pages.
This setting asks crawlers to not follow links. This setting is enabled by default.
Crawler behavior can differ between search engines.
### No translate
Google and Chrome on Android will prompt users to translate pages that are not in their device language.
This can lead to some shifting and can be undesired in some cases. By enabling this,
you ask crawlers to never translate and never prompt users for translation.
It should be noted that some browsers will ignore this and translate regardless.
Some browsers offer to translate pages.
This setting asks crawlers to not offer translation. Some browsers ignore this setting.
### No site links search box
This setting is exclusive to Google.
[Google will create a box below your domain](https://developers.google.com/search/docs/appearance/structured-data/sitelinks-searchbox) that lists the paths that were discovered by the "folowing".
The user can search them directly in Google.
You can ask the crawlers to not allow searches by enabling this setting.
This setting is disabled by default.
This setting applies to Google only.
Google can show a search box below your domain.
This setting asks Google to not show this box. This setting is disabled by default.
## Boards
### Global home board
You can set a global home board that will be used for all users that have not set a home board themselves.
It will also be used for anonymous users.
Note that the board must be public to be used as a global home board.
You can set a global home board.
This board is the fallback for users without their own home board. It is also the fallback for anonymous users.
The board must be public.
### Global mobile board
You can set a global mobile board. It will be shown instead of the global home board on mobile devices that can be detected by Homarr. (Using the User-Agent header).
It will similar to the global home board be as fallback for normal users and for any anonymous users.
It also has to be public.
You can set a global mobile board.
Homarr shows this board on mobile devices instead of the global home board.
Homarr detects mobile devices with the User-Agent header.
The board must be public.
### App status
#### Enable status by default
You can decide if the app status should be enabled by default when adding a new app to the board.
You can decide if app status is enabled when you add a new app.
#### Force disable status
You can decide if the app status should be disabled for all boards.
###
You can decide if app status is disabled on all boards.
## Appearance
### Default color scheme
You can set the default color scheme for all users that have not set a color scheme themselves.
You can set the default color scheme for users without their own color scheme.
## Culture
### Default language
You can set the default language for all users that have not set a language themselves.
You can set the default language for users without their own language.
@@ -177,6 +177,11 @@ Currently, the following permissions are available:
- **Full integration access**: Allow members to manage, use and interact with any integration
- **Administrator**: Members with this permission have full access to all features and settings
Custom Widget authoring, import, migration, preview, secrets, Workshop access, and placement configuration are reserved
for administrators and are not separate assignable group permissions. Board modifiers can still move, resize, change
presentation settings, or remove an existing Custom Widget placement, and board viewers can use placed widgets as
allowed by each request.
When you changed permissions, you must confirm your changes by clicking the banner at the bottom:
![](./img/changing-group-permissions.png)
@@ -0,0 +1,169 @@
---
tags:
- Workshop
- Custom Widgets
- Custom CSS
---
# Community Workshop
:::warning Experimental feature
This feature is experimental and will probably change. If you encounter bugs, need more capabilities, or find something confusing, tell us in the [official discussion thread](https://github.com/homarr-labs/homarr/discussions/categories/custom-widgets). Your feedback directly shapes what comes next. Happy hacking!
:::
The [Community Workshop](pathname:///workshop) is where Homarr users share and discover
**custom CSS themes** and **custom widgets**. You can browse submissions, upvote the ones
you like, report abuse, and install entries directly into your own Homarr instance.
The central `homarr.dev` Workshop is a public community. Browsing it on the website is public. Browsing, installing,
publishing, or importing Workshop content **inside a Homarr instance is restricted to Homarr administrators**.
:::warning
The Workshop contains community-made content. Moderation is not a security boundary, and PocketBase does not interpret
or validate submission content. Always review widget capabilities, network origins, actions, and CSS before installing.
Homarr's installation-time validator is definitive and can reject content that the public service accepted earlier.
Back up your instance before installing untrusted content.
:::
## Browsing and voting
- Filter by **Widgets** or **CSS**, show or hide outdated entries, and sort by popularity, creation date, recent updates, or discussion activity.
- 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.
- Open report counts are public on submission cards so visitors can make an informed installation decision. The submission author and Workshop moderators can read each report category and explanation; reporter identities remain moderator-only.
- 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
Comments are publicly readable and appear in chronological conversation bubbles with the author's avatar and GitHub profile. Signed-in users can post; authors can edit or delete their own comments, and Workshop moderators can delete any comment. Failed posts keep the text you typed. When SMTP and `WORKSHOP_PUBLIC_ORIGIN` are configured, the submission author receives one email for a comment from another user. Self-comments, edits, and deletions do not send mail.
## Submitting
1. Sign in with GitHub.
2. Click **Share yours** and choose the type:
- **Custom widget** — paste the JSON exported from **Manage → Custom Widgets → Export**.
It must match the `homarr-custom-widget-v2` schema (validated before submission).
- **Custom CSS** — paste your stylesheet (max 16384 characters).
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.
Custom widget JSON is normalized to readable multi-line formatting when it is published, and the Workshop detail page
also formats older compact submissions before displaying them.
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.
When SMTP and `WORKSHOP_PUBLIC_ORIGIN` are configured, every current Workshop moderator receives an email when a report is submitted. If a moderator deletes the reported submission, its author receives a separate removal email. Author self-deletion does not send a moderation email.
:::info Separate roles
A Workshop moderator is a user with PocketBase's internal `isAdmin` field. This role moderates the central community
and is unrelated to a Homarr instance administrator.
:::
## Submission guidelines
Submissions must be useful to Homarr users and safe to install. Do not submit content that is
malicious, deceptive, illegal, hateful, sexually explicit, gore, gambling-focused, spam, or content
that primarily promotes unrelated products or services.
## Installing into Homarr
You can install workshop entries without leaving Homarr:
- **Custom widgets (Homarr administrators only)** — go to **Manage → Custom Widgets**, or use **Import from Workshop** in the
board widget picker. Choose **Install widget**, review the complete source and capabilities, then confirm API sources and any local secrets in the second modal step.
- **Custom CSS from Workshop (Homarr administrators only)** — open a board's **Settings → Custom CSS**, click **Import from Workshop**, choose a theme, review its full CSS in the second same-size modal step, and click **Import CSS** once. The board setting is still saved separately.
Alternatively, open a submission in the Workshop and use **Download widget JSON** or **Copy CSS**. The submission page shows the matching manual installation steps before the source code.
Custom Widget imports detect required bearer, basic, header, or query credentials. You can enter them during import or explicitly defer them; secret values are never stored in Workshop content. Missing credentials remain visible on the local Custom Widget afterward.
## Workshop deployment
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,
irreversible development reset.
GitHub needs only the central callback `https://<workshop-host>/api/oauth2-redirect`. Keep PocketBase origins unrestricted
unless every self-hosted Homarr caller is explicitly listed. Configure both `GITHUB_CLIENT_ID` and
`GITHUB_CLIENT_SECRET`; supplying only one is a startup error. Set `WORKSHOP_PUBLIC_ORIGIN` in production so generated
links and email use the public host. Workshop stores only the OAuth provider username in `users.name`, and
users cannot rename it. Map **OAuth2 username** to `name`; leave OAuth2 id, full name, and avatar mappings
empty. Profile and avatar URLs are derived from `https://github.com/<username>` and
`https://github.com/<username>.png`, so PocketBase does not store duplicate profile metadata or avatar files. Configure
SMTP for comment, report, and removal notices.
The public URL contract is:
| Variable | Purpose | Default |
| ------------------------ | ------------------------------------------------- | ----------------------------- |
| `HOMARR_WEBSITE_URL` | Documentation/site base used by Homarr links | `https://homarr.dev` |
| `WORKSHOP_API_URL` | PocketBase API used by Homarr and standalone docs | `HOMARR_WEBSITE_URL` |
| `WORKSHOP_WEB_URL` | Public Workshop page linked from Homarr | `HOMARR_WEBSITE_URL/workshop` |
| `WORKSHOP_PUBLIC_ORIGIN` | PocketBase-generated links and email | Required in production |
| `PB_ALLOWED_ORIGINS` | PocketBase browser origins | `*` for the central community |
The deprecated docs-only `WORKSHOP_URL` alias remains for one release; `WORKSHOP_API_URL` takes precedence. The
combined Workshop image writes these public URLs into its browser runtime configuration whenever the container starts;
the Docker build arguments remain fallbacks. Restart the container after changing these values, without rebuilding it.
### Staging and demo deployment
Use `apps/workshop/docker-compose.staging.yml` with the published `ghcr.io/homarr-labs/workshop:v2` image for a v2
preview. Copy `apps/workshop/.env.staging.example` to `.env.staging`; its default public contract is:
```dotenv
HOMARR_WEBSITE_URL=https://v2.preview.homarr.dev
WORKSHOP_API_URL=https://v2.preview.homarr.dev
WORKSHOP_WEB_URL=https://v2.preview.homarr.dev/workshop
WORKSHOP_PUBLIC_ORIGIN=https://v2.preview.homarr.dev
PB_ALLOWED_ORIGINS=*
```
Start or update it from the repository root:
```sh
docker compose --env-file apps/workshop/.env.staging \
-f apps/workshop/docker-compose.staging.yml pull
docker compose --env-file apps/workshop/.env.staging \
-f apps/workshop/docker-compose.staging.yml up --detach
curl --fail http://127.0.0.1:8090/api/health
```
The service binds to `127.0.0.1:8090`. Terminate TLS at a reverse proxy and forward the **entire hostname**, including
`/api`, `/_/`, and `/workshop-runtime-config.js`; forwarding only `/workshop` breaks PocketBase and runtime URL loading.
This combined image serves both the documentation site and PocketBase, so use a dedicated Workshop hostname if the
chosen hostname already serves Homarr itself. In that case, replace the hostname in all four URL values and keep
`/workshop` only on `WORKSHOP_WEB_URL`.
The Compose volume preserves PocketBase data across pulls and container recreation. The mutable `v2` tag is suitable
for previews; use the `sha-<commit>` tag for promotion. For the public central Workshop, `PB_ALLOWED_ORIGINS=*` allows
self-hosted Homarr origins to use the API and OAuth. A private demo may use a comma-separated origin allowlist instead.
GitHub OAuth requires both credentials and the callback
`https://v2.preview.homarr.dev/api/oauth2-redirect`. Restart after changing environment values, then verify the health
endpoint, `/workshop`, a public listing request, GitHub sign-in, and an installation from the Homarr preview.
Back up application and PocketBase data before deployment. Publish the combined image by immutable digest, test the
exact digest in staging, rehearse application rollback and PocketBase restore, then promote that same digest.
## Client architecture
Workshop access is centralized in `@homarr/workshop/backend`. It exports the typed `WorkshopBackend`, its typed PocketBase instance, stable TanStack Query option factories, and the `useWorkshop…` query and mutation hooks used by Homarr. The documentation site uses the same backend rather than maintaining a second API contract.
Listing fields derived by the `workshop_listings` view—such as submission type, score, and counts—are normalized by the shared client. Healthy deployments keep filtering, sorting, and pagination in PocketBase. If a persistent development volume still has an older view that rejects those fields, the client retries without them and applies the same operations locally; it can also fall back to the base submissions collection. Production deployments should nevertheless apply the current migration so the view exposes the complete metadata.
+5 -1
View File
@@ -4,6 +4,8 @@ description: "Display and edit a selected note from Anchor"
hide_title: true
---
import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
@@ -33,10 +35,12 @@ import anchorNoteWidgetScreenshot from "./img/widget.png";
### Interactions
- Click **Edit** to start editing the selected note.
- Hover the widget, focus within it, or use a touch device to reveal **Edit**, then select it to start editing the note.
- Click **Save** to persist changes to Anchor.
- Click **Cancel** to discard unsaved changes.
<WidgetInteractionGuide />
### Adding the widget
<AddingWidget />
+6
View File
@@ -25,6 +25,12 @@ import piHoleApp from "./img/pi-hole-ping-icon.png";
<img src={piHoleApp} alt="app pi-hole with-status-check status-check-icon" width={128 * 1.5} height={128 * 1.5} />
<img src={jellyseerrApp} alt="app jellyseerr with-description-tooltip" height={128 * 1.5} />
</div>
### Interactions
App tiles stay compact and open their configured destination directly. They do not use the shared widget context menu;
configure the app and its status check from the tile's edit actions.
### Adding the widget
<AddingWidget />
@@ -0,0 +1,40 @@
---
title: "ArchiveTeam Warrior"
description: "Monitor ArchiveTeam Warrior status, projects, bandwidth, and active archival jobs."
hide_title: true
---
import { archiveTeamWarriorIntegration } from "@site/docs/integrations/archiveteam-warrior";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetHeader } from "@site/src/components/widgets/header";
import { WidgetIntegrations } from "@site/src/components/widgets/integrations";
import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
import { archiveTeamWarriorWidget } from ".";
<WidgetHeader widget={archiveTeamWarriorWidget} categories={["Archiving", "Monitoring"]} />
Use this widget to see what your ArchiveTeam Warrior is preserving without leaving Homarr. The compact view prioritizes the
selected project and Warrior status; shorter tiles can hide secondary counts, bandwidth, or the broadcast message. Advanced
view adds the runner status, exact update time, and a scrollable list of reported work items.
### Supported integration
<WidgetIntegrations
items={[
{
integration: archiveTeamWarriorIntegration,
note: "Shows status and active archival work from one Warrior instance",
},
]}
/>
<WidgetInteractionGuide />
### Adding the widget
<AddingWidget />
### Configuration
<WidgetConfig configuration={archiveTeamWarriorWidget.configuration} />
@@ -0,0 +1,19 @@
import type { WidgetDefinition } from "@site/src/types";
import { IconArchive } from "@tabler/icons-react";
export const archiveTeamWarriorWidget: WidgetDefinition = {
icon: IconArchive,
name: "ArchiveTeam Warrior",
description: "Monitor ArchiveTeam Warrior status, projects, bandwidth, and active archival jobs.",
path: "../../widgets/archive-team-warrior",
configuration: {
items: [
{
name: "Show broadcast message",
description: "Show the message broadcast by the selected Warrior project when space allows.",
values: { type: "boolean" },
defaultValue: "yes",
},
],
},
};
@@ -0,0 +1,79 @@
---
title: "Assistant"
description: "Have a complete assistant conversation directly on a board."
hide_title: true
---
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetHeader } from "@site/src/components/widgets/header";
import { assistantWidget } from ".";
import assistantWidgetImage from "../../management/img/assistant-widget.png";
<WidgetHeader widget={assistantWidget} categories={["Productivity"]} />
The Assistant widget brings the same conversation available from the user menu and command menu onto a
board. Messages, conversations, tool calls, approvals, files, mentions, model controls, reasoning controls,
usage details, and response actions remain synchronized with the app-wide assistant.
The assistant must be enabled by an administrator in **Management → Assistant**. Signed-out users and users
on an instance without an enabled model see an unavailable state instead of a broken composer. Signed-out users see
**Sign in**; a configured provider without a saved model or with **Enable Assistant** off remains unavailable until
the administrator saves a working connection, selects a model, and enables the assistant.
<img
src={assistantWidgetImage}
alt="Assistant widget embedded on a dashboard with its conversation header, automatic approvals control, model selector, and composer"
style={{ maxWidth: "100%" }}
/>
The current preview capture labels the board-embedded surface: its header exposes conversation history, refresh,
automatic approvals, and new-conversation controls, while the footer exposes the model, thinking mode, and composer.
### Adding the widget
<AddingWidget />
The default widget size is ten columns by four rows and automatically fits boards with fewer columns. Controls
and labels condense as the widget becomes narrower, while the conversation remains scrollable and usable on
touch devices. When a board layout does not provide enough height for conversation controls, the widget shows
a compact **Open assistant** action instead of squeezing an unusable composer into the card. Increase its height
in that layout to keep the complete conversation inline.
### Configuration
<WidgetConfig configuration={assistantWidget.configuration} />
**Current conversation** follows whichever conversation is active in the user-menu panel, `Shift + A`, or
the command menu. This is the default and keeps every assistant entry point synchronized.
**Pinned conversation** reveals a searchable conversation selector. A pinned widget never changes the
active conversation silently: select **Open conversation** inside the widget before continuing that thread. If the pinned
thread was deleted or cannot be opened, the widget keeps the pinned selection, shows the same recovery action and a
retry state, and retries that thread when **Open conversation** is selected again.
### Conversation features
- Create, switch, rename, and delete conversations.
- Send text and supported file or image attachments by picker, drag and drop, or image paste.
- Mention permitted apps, integrations, boards, widgets, and tools with `@`.
- Use `/` shortcuts for common dashboard tasks.
- Review tool calls, approve mutations, and answer structured assistant questions inline.
- Answer pending structured questions from the dock above the composer without scrolling through the conversation.
- Enable conversation-scoped automatic approvals from the shield control to approve protected mutations, use presented form details, and choose answers categorized as affirmative without individual clicks.
- Edit user messages and copy, regenerate, branch, quote, export, or rate responses.
- Inspect syntax-highlighted Markdown, sources, generated files, tool results, streaming chain-of-thought,
cumulative conversation usage, per-turn token and context use, timing, speed, provider routing, and cost.
- Change the model and reasoning effort for the active conversation. A model choice is saved per persisted thread,
does not change the administrator's default, and is restored when that thread is selected again; a new thread
starts from the configured default.
- Refresh the current board from the conversation header. Approved mutations also refresh visible board data automatically.
The expanded assistant and board widget use the same persisted thread runtime. A response continues in the
background when the widget leaves the viewport or the expanded panel is minimized. Each visible surface keeps an
independent draft, attachments, selection, and focus, so typing in a board widget cannot move or overwrite the
floating panel composer.
Automatic approvals are off by default, reset when switching conversations, and never select a neutral choice. A
structured question shows **Other** only when the tool allows it (`allowOther` is not `false`); selecting **Other**
opens a manual freeform response even when automatic approvals are enabled.
+25
View File
@@ -0,0 +1,25 @@
import type { WidgetDefinition } from "@site/src/types";
import { IconRobot } from "@tabler/icons-react";
export const assistantWidget: WidgetDefinition = {
icon: IconRobot,
name: "Assistant",
description: "Have a complete assistant conversation directly on a board.",
path: "../../widgets/assistant",
configuration: {
items: [
{
name: "Conversation",
description: "Follow the active conversation or attach the widget to one persisted conversation.",
values: { type: "select", options: ["Current conversation", "Pinned conversation"] },
defaultValue: "Current conversation",
},
{
name: "Pinned conversation",
description: "Search for a conversation. This field is hidden when Current conversation is selected.",
values: { type: "string" },
defaultValue: "",
},
],
},
};
+7 -3
View File
@@ -1,9 +1,11 @@
---
title: "Audio Stats"
description: "Displays library statistics from Navidrome or Audiobookshelf, adapting its display based on the linked integration."
description: "Displays media and listening statistics from Navidrome or Audiobookshelf, adapting its display based on the linked integration."
hide_title: true
---
import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
import { WidgetHeader } from "@site/src/components/widgets/header";
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
@@ -16,7 +18,7 @@ import { audiobookshelfIntegration } from "@site/docs/integrations/audiobookshel
This widget adapts its display based on the linked integration:
- **Navidrome**: Shows artist, album, and song counts, plus a now playing section with active streams
- **Navidrome**: Shows artist, album, and song counts
- **Audiobookshelf**: Shows library count, audiobook/podcast totals, listening time, and active sessions
Only options relevant to your linked integration are shown in the configuration panel.
@@ -27,7 +29,7 @@ Only options relevant to your linked integration are shown in the configuration
items={[
{
integration: navidromeIntegration,
note: "Displays music library stats and now playing",
note: "Displays music library statistics",
},
{
integration: audiobookshelfIntegration,
@@ -36,6 +38,8 @@ Only options relevant to your linked integration are shown in the configuration
]}
/>
<WidgetInteractionGuide />
### Adding the widget
<AddingWidget />
Loaded 100 of 1585 files, more files were not shown because too many files have changed in this diff. Show more