Compare commits

..
Author SHA1 Message Date
ajnart 7bafe1ce75 test: align release v2 contracts 2026-08-14 02:18:46 +02:00
ajnart ca95112466 docs(api): refresh OpenAPI schema 2026-08-14 02:18:46 +02:00
ajnart 32732680e5 fix: address release v2 reviews 2026-08-14 02:18:46 +02:00
ajnart 4905ea6738 fix release v2 recovery flows 2026-08-14 02:18:46 +02:00
ajnart ca856f9f54 fix release v2 review issues 2026-08-14 02:18:46 +02:00
ajnart ef2ed6c602 enhance release v2 experience 2026-08-14 02:18:46 +02:00
ajnart 9b4970583e style: format board preview query 2026-08-14 02:18:33 +02:00
Thomas Camlong 4641839fa4 fix(boards): preview container contents 2026-08-14 02:16:14 +02:00
ajnart 00e9dbe268 style: format restored board creator 2026-08-14 02:13:37 +02:00
Thomas Camlong 34a17932ae fix(onboarding): preserve restored board login target 2026-08-14 02:11:43 +02:00
Thomas Camlong c224e62f30 fix(onboarding): streamline backup restore 2026-08-14 02:10:33 +02:00
ajnart 25c2fbab51 style: format onboarding setup studio 2026-08-14 02:02:46 +02:00
Thomas Camlong 5b58be347e fix(onboarding): accept self-hosted addresses 2026-08-14 02:00:35 +02:00
Thomas Camlong 7fcc36adff fix(onboarding): simplify detected app cards 2026-08-14 01:57:20 +02:00
ajnart d11f34a6f2 test: skip unavailable advanced focus flows 2026-08-14 01:50:11 +02:00
ajnart ff8ef7a403 test: align onboarding focus contract 2026-08-14 01:20:28 +02:00
ajnart 76758ff7bc style(docs): format Docker labels guide 2026-08-14 01:20:28 +02:00
ajnart 45c1507bfa style(onboarding): format app URL 2026-08-14 01:20:28 +02:00
ajnart 1c34d95976 fix(onboarding): restore e2e flow 2026-08-14 01:20:28 +02:00
Thomas Camlong eb33ff1372 fix(onboarding): guard detected app type 2026-08-14 01:20:28 +02:00
Thomas Camlong c3a090e880 fix(onboarding): align detected app fields 2026-08-14 01:20:28 +02:00
Thomas Camlong def3a3a6fa fix(onboarding): align detected app setup 2026-08-14 01:20:28 +02:00
Thomas Camlong ad0bb41fe4 fix(onboarding): highlight detected apps 2026-08-14 01:20:28 +02:00
Thomas Camlong 6e76e8c0dd docs(onboarding): document Docker label discovery 2026-08-14 01:20:28 +02:00
Thomas Camlong e06d95ee7a fix(onboarding): show integration picker 2026-08-14 01:20:28 +02:00
Thomas Camlong 865e96b5b1 feat: consolidate onboarding 2026-08-14 01:20:28 +02:00
Thomas Camlong 66313055df feat: consolidate onboarding 2026-08-14 01:20:28 +02:00
Thomas Camlong 2283ec7113 feat: enhance onboarding 2026-08-14 01:20:28 +02:00
Thomas Camlong e5d6b6be87 test(onboarding): cover integration checks 2026-08-14 01:20:28 +02:00
Thomas Camlong e3e5730a39 feat(onboarding): test integration connections 2026-08-14 01:20:28 +02:00
Thomas Camlong d776078ecf fix(onboarding): skip unfinished integrations 2026-08-14 01:20:27 +02:00
Thomas Camlong 4ca02a9133 feat(onboarding): polish setup experience 2026-08-14 01:20:27 +02:00
Thomas Camlong f4891447d3 fix(onboarding): use preset board colors 2026-08-14 01:20:27 +02:00
Thomas Camlong 2067d851cf fix(onboarding): collapse manual integrations 2026-08-14 01:20:27 +02:00
Thomas Camlong 3aedd312b7 fix(onboarding): harden release acceptance 2026-08-14 01:20:27 +02:00
Thomas Camlong e8ca63d9c0 style: format stacked changes 2026-08-14 01:20:27 +02:00
Thomas Camlong d89bf25e21 fix: address onboarding review feedback 2026-08-14 01:20:27 +02:00
Thomas Camlong 07770e862c fix: harden onboarding completion 2026-08-14 01:20:27 +02:00
Thomas Camlong 576cc0b418 feat: rebuild onboarding experience 2026-08-14 01:20:27 +02:00
ajnart 81d5070a8b Merge rebased assistant stack parent 2026-08-13 20:03:12 +02:00
ajnart 44df54ecb8 Merge rebased mobile stack parent 2026-08-13 20:03:12 +02:00
ajnart 07867cc3db Merge rebased widget stack parent 2026-08-13 20:03:12 +02:00
ajnart 818e9bb0c5 Merge rebased grid stack parent 2026-08-13 20:03:12 +02:00
ajnart 3b73f0bc4c Merge rebased v2 stack parent 2026-08-13 20:03:12 +02:00
ajnart c92ca2af5f chore: reconcile performance layer after stack rebase 2026-08-13 20:01:46 +02:00
ajnart 124a5526b4 chore: reconcile assistant layer after stack rebase 2026-08-13 20:00:57 +02:00
ajnart b52b2b5f3c chore: reconcile mobile layer after stack rebase 2026-08-13 20:00:18 +02:00
ajnart 1f3e7afd5e chore: reconcile widget layer after stack rebase 2026-08-13 20:00:10 +02:00
ajnart 2c8134ee02 chore: reconcile grid layer after stack rebase 2026-08-13 20:00:04 +02:00
ajnart 0482c040ec chore: reconcile v2 after stack rebase 2026-08-13 19:59:46 +02:00
ajnart 1088f058e3 perf: trim widget loading work 2026-08-13 19:57:13 +02:00
ajnart 47861c0194 perf: reduce widget render overhead 2026-08-13 19:57:13 +02:00
ajnart 0e7d7e0a2f perf: simplify widget optimizations 2026-08-13 19:57:13 +02:00
ajnart 5ca5bd3bda perf: remove global V8 memory cap 2026-08-13 19:57:13 +02:00
ajnart bcb732a26c perf: optimize widget integration requests 2026-08-13 19:57:13 +02:00
ajnart 8c9e2c8bfa fix: refresh docker modem patch hash 2026-08-13 19:57:13 +02:00
ajnart 14c7e58e17 perf: reduce widget and runtime overhead 2026-08-13 19:57:13 +02:00
Thomas Camlong 2eea916013 chore: refresh stack checks 2026-08-13 19:57:04 +02:00
ajnart 5a7607ddf7 chore: scope performance harness ignores 2026-08-13 19:57:04 +02:00
ajnart e466208c09 docs: simplify assistant guides 2026-08-13 19:57:04 +02:00
ajnart cfce8ada4e chore: ignore local performance harness 2026-08-13 19:57:04 +02:00
Thomas Camlong 36661e810f chore: refresh stack merge state 2026-08-13 19:57:04 +02:00
ajnart 308e478733 feat: improve assistant panel context 2026-08-13 19:57:04 +02:00
ajnart 59ce3c7158 fix: default Homarr provider URL in CI 2026-08-13 19:57:03 +02:00
Thomas 6ea7a6ff09 feat: add the community-funded Homarr assistant provider (#6563)
* Add Homarr provider proxy foundation

* Complete Homarr provider integration

* Harden Homarr provider integration

* Address Homarr provider review feedback

* Address CodeRabbit provider feedback

* Localize provider errors

* Handle provider activity load failures

* docs: improve workshop and homepage

* Harden Homarr provider limits

* fix: harden the Homarr provider

* fix: address provider review feedback

* fix: clarify provider request accounting

* feat: complete the Homarr provider endpoint

* fix: terminate invalid provider streams
2026-08-13 19:57:03 +02:00
Thomas Camlong 1611a9033b test: align assistant credential flow 2026-08-13 19:57:03 +02:00
ajnart 20f4f87361 fix assistant focus and settings 2026-08-13 19:57:03 +02:00
Thomas Camlong 4130b62f24 fix: simplify assistant key management 2026-08-13 19:57:02 +02:00
ajnart a3b7b0835e test custom widget assistant authoring 2026-08-13 19:57:02 +02:00
ajnart e225424ab1 fix assistant custom widget creation 2026-08-13 19:57:02 +02:00
ajnart 237b6cb119 fix assistant tool result streaming 2026-08-13 19:57:02 +02:00
ajnart 557b3f8d5f test assistant v7 request payload 2026-08-13 19:57:02 +02:00
ajnart 8f0efd508e test assistant AI SDK v7 transport 2026-08-13 19:57:02 +02:00
ajnart 876f312a2e chore upgrade assistant to AI SDK v7 2026-08-13 19:57:02 +02:00
ajnart e662a175c0 fix assistant exports and tool calls 2026-08-13 19:57:01 +02:00
ajnart 52c261c034 refine assistant window controls 2026-08-13 19:57:00 +02:00
ajnart d8108f3375 fix assistant dashboard placement 2026-08-13 19:57:00 +02:00
ajnart 0ae2203aaa feat: add assistant window controls 2026-08-13 19:57:00 +02:00
ajnart 6b30ba3707 fix assistant tool continuation 2026-08-13 19:57:00 +02:00
Thomas Camlong 2e945805b2 chore: leave en-GB to Crowdin 2026-08-13 19:56:58 +02:00
ajnart 780e5928cd feat assistant context and agent trace 2026-08-13 19:56:58 +02:00
ajnart f61e07ae58 fix assistant reasoning mount lifecycle 2026-08-13 19:56:58 +02:00
ajnart 7a8b37c906 feat: improve assistant thinking states 2026-08-13 19:56:58 +02:00
ajnart 70b0df45b3 fix: refine assistant controls 2026-08-13 19:56:58 +02:00
ajnart fa946e1b33 fix: adjust assistant response limits 2026-08-13 19:56:57 +02:00
ajnart f01969b9fc fix: stabilize custom widget authoring 2026-08-13 19:56:57 +02:00
ajnart 11b941285b fix: harden assistant authoring and UI 2026-08-13 19:56:56 +02:00
Thomas Camlong 3148ecb823 fix: validate MCP v2 routes 2026-08-13 19:56:55 +02:00
Thomas Camlong 71811dda55 test: keep assistant widget compact 2026-08-13 19:56:55 +02:00
ajnart e4cb8da5e2 feat: migrate MCP server to v2 2026-08-13 19:56:55 +02:00
Thomas Camlong aab12bbe64 Rename Assistant migrations 2026-08-13 19:56:55 +02:00
Thomas Camlong 12c436cd5c fix(assistant): preserve lazy board boundaries 2026-08-13 19:56:53 +02:00
Thomas Camlong 14d426764b docs: refresh assistant OpenAPI schema 2026-08-13 19:56:53 +02:00
ajnart b3b5a58116 fix(assistant): prevent panel focus capture 2026-08-13 19:56:52 +02:00
Thomas 5f7429063b fix(assistant): mysql message storage, migration metadata and per-page bundle cost (#6530) 2026-08-13 19:56:52 +02:00
Thomas Camlong 2ffb05e746 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-13 19:56:44 +02:00
Thomas Camlong 921aac0560 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-13 19:56:43 +02:00
Thomas Camlong 24bd63d216 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-13 19:56:41 +02:00
Thomas Camlong 8b87967a12 chore: shrink demo assistant widget to 3x3 2026-08-13 19:56:41 +02:00
Thomas d44db87d43 feat: add assistant widget to demo dashboard (#6529) 2026-08-13 19:56:41 +02:00
Thomas 73b7df44d7 fix: handle missing message in assistant generation telemetry (#6528) 2026-08-13 19:56:41 +02:00
ajnart c6cf75e561 style: auto-fix lint and format 2026-08-13 19:56:41 +02:00
Thomas Camlong 3c3232e85a 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-13 19:56:41 +02:00
Thomas c1aaa1a955 feat: mock Homarr Assistant in demo mode and fix board page crash (#6524) 2026-08-13 19:56:41 +02:00
ajnart 5e6ebef961 feat: polish assistant conversations 2026-08-13 19:56:40 +02:00
ajnart c3436d43e6 feat: improve assistant interactions 2026-08-13 19:56:39 +02:00
ajnart d825cb7051 stabilize hydrated login actions 2026-08-13 19:56:38 +02:00
ajnart de682abadc stabilize integration router tests 2026-08-13 19:56:37 +02:00
ajnart 6afa4d2f4a clarify future Homarr provider 2026-08-13 19:56:36 +02:00
ajnart 95473d1b7f fix integration permission checks 2026-08-13 19:56:35 +02:00
ajnart fd58f9a358 fix assistant provider icon contrast 2026-08-13 19:56:34 +02:00
ajnart a89877b461 add OpenRouter web search option 2026-08-13 19:56:34 +02:00
ajnart b362ed1e68 fix assistant widget configuration 2026-08-13 19:56:33 +02:00
ajnart 83355510c0 enhance assistant batch responses 2026-08-13 19:56:32 +02:00
ajnart 6cf34af845 show assistant icon search previews 2026-08-13 19:56:31 +02:00
ajnart 0f2f9e6e59 fix assistant streaming scroll lock 2026-08-13 19:56:31 +02:00
ajnart 2f5f89426a integrate assistant custom widget workflow 2026-08-13 19:56:31 +02:00
ajnart 427db07a95 fix assistant prompt submission 2026-08-13 19:56:30 +02:00
ajnart fc07e911d1 refresh views after assistant changes 2026-08-13 19:56:29 +02:00
ajnart 94ec244fad improve assistant question placement 2026-08-13 19:56:29 +02:00
ajnart 4184721b9a fix assistant automatic approvals 2026-08-13 19:56:29 +02:00
ajnart 8dc5a0540f chore: remove unused ai sdk dependency 2026-08-13 19:56:29 +02:00
ajnart 71d1cef139 fix: address assistant review feedback 2026-08-13 19:56:28 +02:00
ajnart a97605e0f7 fix: isolate assistant conversation state 2026-08-13 19:56:27 +02:00
ajnart 0876992a9a fix: harden assistant interactive tools 2026-08-13 19:56:27 +02:00
ajnart 5a65ff47bf feat: improve assistant tool workflows 2026-08-13 19:56:27 +02:00
ajnart 4060f9299f fix: secure assistant generation telemetry 2026-08-13 19:56:27 +02:00
ajnart ca8163c061 feat: enrich assistant request telemetry 2026-08-13 19:56:27 +02:00
ajnart cb1a73a6ac fix: sync assistant runtime preferences 2026-08-13 19:56:27 +02:00
ajnart 7899d8677c fix: align assistant management layout 2026-08-13 19:56:27 +02:00
ajnart 16e3f54512 fix: hide inactive assistant scroll control 2026-08-13 19:56:27 +02:00
ajnart 39a14a18f9 fix: polish assistant widget interactions 2026-08-13 19:56:27 +02:00
ajnart ed705c9e34 fix: correct assistant widget docs metadata 2026-08-13 19:56:27 +02:00
ajnart b0ec746081 style: auto-fix lint and format 2026-08-13 19:56:27 +02:00
ajnart 00fa1192d7 feat: add assistant conversation widget 2026-08-13 19:56:26 +02:00
ajnart c7b0c1bcf0 fix: simplify assistant shortcut 2026-08-13 19:56:25 +02:00
ajnart 9da3ff3e9d feat: add assistant board styling tools 2026-08-13 19:56:25 +02:00
ajnart 93113234a7 fix: restore assistant approval flows 2026-08-13 19:56:24 +02:00
ajnart 9ef4bf803d feat: expose assistant global shortcuts 2026-08-13 19:56:24 +02:00
ajnart 4920063318 fix: keep assistant menu selection visible 2026-08-13 19:56:23 +02:00
ajnart 68d236fd63 refactor: group assistant runtime controls 2026-08-13 19:56:23 +02:00
ajnart ebce24ba67 fix: invoke mutations before approval 2026-08-13 19:56:23 +02:00
ajnart 039bfb9b93 feat: redesign assistant management 2026-08-13 19:56:23 +02:00
ajnart faf01e82c8 docs: explain assistant search focus 2026-08-13 19:56:23 +02:00
ajnart 54d7821e24 style: auto-fix lint and format 2026-08-13 19:56:23 +02:00
ajnart f29e604bc1 feat: improve assistant confirmations 2026-08-13 19:56:23 +02:00
ajnart 50deb638fb feat: improve assistant background controls 2026-08-13 19:56:22 +02:00
ajnart b1f25f779d refactor: simplify assistant message controls 2026-08-13 19:56:22 +02:00
ajnart df874dd2db feat: render assistant content 2026-08-13 19:56:21 +02:00
ajnart 26ede97407 fix: send provider model ids 2026-08-13 19:56:20 +02:00
ajnart b5fbf0085f fix: preserve assistant trigger focus 2026-08-13 19:56:20 +02:00
ajnart a65dc2841f fix: address assistant capability review 2026-08-13 19:56:20 +02:00
ajnart 09a85e69c1 feat: expand assistant interaction capabilities 2026-08-13 19:56:19 +02:00
ajnart e53f63b373 refactor: remove assistant chat archiving 2026-08-13 19:56:18 +02:00
ajnart ea1e07c74f fix: polish assistant mobile layout 2026-08-13 19:56:17 +02:00
ajnart 4635e3ce4e fix: harden assistant floating panel 2026-08-13 19:56:17 +02:00
ajnart 345d9e6fa4 feat: streamline assistant interactions 2026-08-13 19:56:17 +02:00
ajnart f86120016c fix: surface assistant stream errors 2026-08-13 19:56:16 +02:00
ajnart 68b70ac01a fix: keep assistant in user menu 2026-08-13 19:56:16 +02:00
ajnart c65e6cdbd4 fix: clarify assistant credential removal 2026-08-13 19:56:16 +02:00
ajnart 073d9ce127 fix: invalidate persisted assistant model state 2026-08-13 19:56:16 +02:00
ajnart 6da471d24a fix: invalidate assistant models on discovery changes 2026-08-13 19:56:16 +02:00
ajnart c157001455 fix: gate assistant models on saved connection 2026-08-13 19:56:16 +02:00
ajnart 00e4cc8100 fix: clear assistant credentials on destination changes 2026-08-13 19:56:16 +02:00
ajnart d3205d886a feat: promote assistant configuration 2026-08-13 19:56:16 +02:00
ajnart 97d26bde5e feat: expand assistant providers and polish experience 2026-08-13 19:56:14 +02:00
ajnart 12f950ce4a fix: derive assistant approval key at runtime 2026-08-13 19:56:13 +02:00
ajnart 381a257dbc fix: hide closed assistant history 2026-08-13 19:56:13 +02:00
ajnart 16c86079e6 fix: harden assistant integration 2026-08-13 19:56:13 +02:00
ajnart fd69cd28f0 feat: add Homarr assistant 2026-08-13 19:56:10 +02:00
Thomas 3b6de1f91f fix(boards): disable mobile sidebars (#6565) 2026-08-13 19:56:00 +02:00
Thomas Camlong f0747fd28b fix: refine responsive layout settings 2026-08-13 19:56:00 +02:00
Thomas Camlong 5c96c52e64 Rename layout role migrations 2026-08-13 19:55:59 +02:00
Thomas Camlong bc2aff17f3 test(boards): include layout roles in gutter fixtures 2026-08-13 19:55:56 +02:00
Thomas Camlong 90e30ce577 style: format mobile migration metadata 2026-08-13 19:55:56 +02:00
ajnart 485a54d6d8 fix: repair restored board layouts 2026-08-13 19:55:55 +02:00
ajnart 45954837ed fix: guard recursive section rendering 2026-08-13 19:55:55 +02:00
ajnart aef0a6d340 fix: stabilize responsive layout editing 2026-08-13 19:55:54 +02:00
ajnart e74d5e2aae fix: improve layout preview density 2026-08-13 19:55:53 +02:00
ajnart 7453e6c559 fix: address responsive layout review 2026-08-13 19:55:53 +02:00
ajnart b3ec1381e9 fix: avoid nested board settings forms 2026-08-13 19:55:52 +02:00
ajnart 30c00ea30d feat: protect responsive board layouts 2026-08-13 19:55:38 +02:00
ajnart 4df62221c7 refactor: simplify mobile board layouts 2026-08-13 19:55:37 +02:00
ajnart dec04da806 fix: refine mobile board preview 2026-08-13 19:55:34 +02:00
ajnart 866b3ab78b fix: address mobile board review 2026-08-13 19:55:34 +02:00
ajnart 92c6e4668d test: cover tablet mobile boards 2026-08-13 19:55:34 +02:00
ajnart df70d06606 feat: add mobile layout preview 2026-08-13 19:55:34 +02:00
ajnart 4d0c2f8eec fix: label board edit mode control 2026-08-13 19:55:32 +02:00
ajnart 9887c018d2 fix: keep mobile board widgets unchanged 2026-08-13 19:55:30 +02:00
ajnart e93f14acdf Revert "feat: improve mobile board experience"
This reverts commit 7c0e98c55b.
2026-08-13 19:55:28 +02:00
ajnart 85db534fa7 Revert "feat: refine mobile dashboard experience"
This reverts commit 315520beca.
2026-08-13 19:55:26 +02:00
ajnart 8ac35083e6 Revert "style: auto-fix lint and format"
This reverts commit 4bd0d8e8dd.
2026-08-13 19:55:24 +02:00
ajnart f481007b9a Revert "fix: safely extract notebook summaries"
This reverts commit fa3d0375c5.
2026-08-13 19:55:24 +02:00
ajnart f88fbb5a19 Revert "fix: address mobile dashboard review findings"
This reverts commit 1039bb18b3.
2026-08-13 19:55:23 +02:00
ajnart d4cecde0f0 Revert "fix: collect mobile board e2e test"
This reverts commit 6dbfbb1417.
2026-08-13 19:55:23 +02:00
ajnart 0356eb427c Revert "fix: layer mobile widget details above header"
This reverts commit a540f07920.
2026-08-13 19:55:22 +02:00
ajnart b3e6d35b79 Revert "fix: keep interface layers server safe"
This reverts commit cf83e0d849.
2026-08-13 19:55:21 +02:00
ajnart be9c69f325 Revert "feat: complete automatic mobile board UX"
This reverts commit 7959c20d88.
2026-08-13 19:55:21 +02:00
ajnart e93293f8a9 Revert "fix: harden automatic mobile boards"
This reverts commit d0b9348b86.
2026-08-13 19:55:19 +02:00
ajnart 967ed8dd73 fix: harden automatic mobile boards 2026-08-13 19:55:17 +02:00
ajnart 5454bcc8ac feat: complete automatic mobile board UX 2026-08-13 19:55:15 +02:00
ajnart ec54ddcfa6 fix: keep interface layers server safe 2026-08-13 19:55:13 +02:00
ajnart 25a4aca8c0 fix: layer mobile widget details above header 2026-08-13 19:55:13 +02:00
ajnart d1f76b24c0 fix: collect mobile board e2e test 2026-08-13 19:55:12 +02:00
ajnart 6a2e7d0610 fix: address mobile dashboard review findings 2026-08-13 19:55:12 +02:00
ajnart 438f0d80eb fix: safely extract notebook summaries 2026-08-13 19:55:11 +02:00
ajnart 673b1a74d6 style: auto-fix lint and format 2026-08-13 19:55:11 +02:00
ajnart 84db2f9d49 feat: refine mobile dashboard experience 2026-08-13 19:55:11 +02:00
ajnart 2e3dca3e87 feat: improve mobile board experience 2026-08-13 19:55:07 +02:00
ajnart 7d5ec15017 fix: align mobile root section ordering 2026-08-13 19:55:06 +02:00
ajnart f6baaa0c3b fix: address board review and CI feedback 2026-08-13 19:55:06 +02:00
ajnart 9d3c0acb95 feat: add automatic mobile board layout 2026-08-13 19:55:05 +02:00
Thomas Camlong 3079eb1767 fix: limit advanced widget focus 2026-08-13 19:54:56 +02:00
Thomas Camlong 5059a16181 fix: address widget review findings 2026-08-13 19:54:56 +02:00
Thomas Camlong 444ae82352 test: align widget modernization fixtures 2026-08-13 19:54:56 +02:00
Thomas Camlong 65a7e84bea feat: expand advanced widget experiences 2026-08-13 19:54:55 +02:00
Thomas Camlong 55226842f9 chore: drop manual translations 2026-08-13 19:54:54 +02:00
Thomas Camlong e84b05e91c fix: rework media session details 2026-08-13 19:54:54 +02:00
Thomas Camlong d6359d595c fix: refine Beszel stats period controls 2026-08-13 19:54:53 +02:00
Thomas Camlong f876b2fd06 fix: show complete Immich photos in advanced mode 2026-08-13 19:54:53 +02:00
Thomas Camlong c256bd644c test: preserve advanced preview hover ownership 2026-08-13 19:54:53 +02:00
Thomas Camlong c9fe69c5ec feat: improve advanced preview activation 2026-08-13 19:54:53 +02:00
Thomas Camlong 64a57bac82 fix: smooth advanced focus entrance 2026-08-13 19:54:52 +02:00
Thomas Camlong 39e408c53f Remove Docker advanced focus 2026-08-13 19:54:52 +02:00
Thomas Camlong f404f77002 fix: require advanced widget focus opt-in 2026-08-13 19:54:52 +02:00
Thomas Camlong 21a38b3004 test(widgets): stabilize advanced focus hover 2026-08-13 19:54:52 +02:00
Thomas Camlong 3b76381262 test(mcp): review smart home entity details tool 2026-08-13 19:54:51 +02:00
Thomas Camlong bb97ce5a01 style(widgets): split query matcher type imports 2026-08-13 19:54:51 +02:00
Thomas Camlong 2e323677f4 fix(widgets): keep board graph imports scoped 2026-08-13 19:54:51 +02:00
Thomas Camlong f2dd7c10a2 fix(ci): make modernization independently installable 2026-08-13 19:54:51 +02:00
Thomas Camlong ccabc2dab6 style: format modernization notes 2026-08-13 19:54:51 +02:00
Thomas Camlong 5633bc4775 fix: preserve app links and hover previews 2026-08-13 19:54:51 +02:00
Thomas Camlong d20bb18b08 fix: stabilize advanced widget interactions 2026-08-13 19:54:51 +02:00
Thomas Camlong 747ccdfa9e fix: finish widget modernization audit 2026-08-13 19:54:50 +02:00
Thomas Camlong bbcfc505bc fix: address widget review feedback 2026-08-13 19:54:50 +02:00
Thomas Camlong 2b1053e243 fix: scope advanced preview controls 2026-08-13 19:54:50 +02:00
Thomas Camlong 154753ddda ci: limit turbo concurrency 2026-08-13 19:54:49 +02:00
Thomas Camlong 74599b0b65 fix: restore notifications query scope 2026-08-13 19:54:49 +02:00
Thomas Camlong 7bfd10210e fix: remove stale notifications import 2026-08-13 19:54:49 +02:00
ajnart 19befee8a5 style: auto-fix lint and format 2026-08-13 19:54:49 +02:00
Thomas Camlong 77cbd9f723 fix: refine widget interactions 2026-08-13 19:54:49 +02:00
Thomas Camlong 1fa345edd1 feat: modernize widget experience 2026-08-13 19:54:48 +02:00
ajnart 6331c88911 docs: add widget modernization audit 2026-08-13 19:54:46 +02:00
Thomas Camlong 4c69c4ec45 restore context menu focus 2026-08-13 19:54:46 +02:00
Thomas Camlong ff31208c37 fix context menu keyboard focus 2026-08-13 19:54:46 +02:00
Thomas Camlong 177ea49469 fix board E2E login race 2026-08-13 19:54:46 +02:00
Thomas Camlong f03bf6a917 fix SVG focus scrolling 2026-08-13 19:54:46 +02:00
Thomas Camlong 80d1416ad6 fix focused pane scrolling 2026-08-13 19:54:46 +02:00
Thomas Camlong d2a935b6c1 fix focus scroll layering 2026-08-13 19:54:46 +02:00
Thomas Camlong 758c300d9e fix widget focus interactions 2026-08-13 19:54:45 +02:00
Thomas Camlong 53185701ce fix: align responsive widget actions 2026-08-13 19:54:44 +02:00
Thomas Camlong 21cc0b19b3 fix: simplify widget feedback 2026-08-13 19:54:43 +02:00
Thomas Camlong a882a227f1 fix: harden widget error feedback 2026-08-13 19:54:43 +02:00
Thomas Camlong e535ff393e fix: address final widget review 2026-08-13 19:54:42 +02:00
Thomas Camlong 349dfa6d41 fix: localize widget chart formatting 2026-08-13 19:54:42 +02:00
Thomas Camlong 6c8dbc182f fix: align widget accessibility and locales 2026-08-13 19:54:42 +02:00
Thomas Camlong d880c4a281 fix: canonicalize widget date locales 2026-08-13 19:54:42 +02:00
Thomas Camlong d7083fc14c fix: close final widget review gaps 2026-08-13 19:54:41 +02:00
Thomas Camlong c7431ef1f5 fix: harden widget review follow-ups 2026-08-13 19:54:40 +02:00
Thomas Camlong 419c8434f9 fix: address widget review findings 2026-08-13 19:54:39 +02:00
Thomas Camlong 49ef7748ad feat: refine widget interactions 2026-08-13 19:54:38 +02:00
Thomas Camlong a9f1293077 fix: secure widget navigation links 2026-08-13 19:54:37 +02:00
Thomas Camlong 2344896ba9 fix: polish widget failure feedback 2026-08-13 19:54:36 +02:00
Thomas Camlong 0aa3b98675 fix: address final widget review findings 2026-08-13 19:54:35 +02:00
Thomas Camlong f003d9ddc9 fix: complete widget review cleanup 2026-08-13 19:54:35 +02:00
Thomas Camlong 56346b2aa8 fix: address remaining widget review feedback 2026-08-13 19:54:34 +02:00
Thomas Camlong e321ee27a2 fix: allow dynamic database schema access 2026-08-13 19:54:33 +02:00
Thomas Camlong afd73565c9 fix: make integration warnings keyboard accessible 2026-08-13 19:54:32 +02:00
Thomas Camlong 36d7ef0562 fix: complete widget modernization review 2026-08-13 19:54:32 +02:00
Thomas Camlong 99a871344c fix: address widget review feedback 2026-08-13 19:54:31 +02:00
ajnart 010ba45468 style: auto-fix lint and format 2026-08-13 19:54:30 +02:00
Thomas Camlong 777bbd89a1 fix: refine widget modernization 2026-08-13 19:54:29 +02:00
Thomas Camlong 6f2cd7fc2b feat: modernize dashboard widgets 2026-08-13 19:54:28 +02:00
Thomas Camlong 1a5e083fd0 fix: simplify grid interaction previews 2026-08-13 19:54:12 +02:00
Thomas Camlong 9607bf65cd perf: isolate board grid previews 2026-08-13 19:54:12 +02:00
Thomas Camlong 6caa1c8224 fix: disable app context menu 2026-08-13 19:54:12 +02:00
Thomas Camlong 556179deb5 perf: isolate grid interaction renders 2026-08-13 19:54:12 +02:00
Thomas Camlong ab012b4a32 fix: stabilize rail and resize interactions 2026-08-13 19:54:12 +02:00
Thomas Camlong d635e4d80f chore: leave translations to Crowdin 2026-08-13 19:54:12 +02:00
Thomas Camlong 6afd21fe98 fix: prevent horizontal rail auto-scroll 2026-08-13 19:54:12 +02:00
Thomas Camlong cf7ec0a31f Revert "ci: use self-hosted runners for stack"
This reverts commit a1d80515b0e00240bb662180626a44bfb205656e.
2026-08-13 19:54:12 +02:00
Thomas Camlong 5be2226c39 ci: use self-hosted runners for stack 2026-08-13 19:54:12 +02:00
Thomas Camlong 082f2271c5 fix: anchor dashboard rail labels 2026-08-13 19:54:12 +02:00
Thomas Camlong 5a03640a1b fix: size dashboard rails to viewport 2026-08-13 19:54:11 +02:00
Thomas Camlong 80132ae71f style: format board grid test 2026-08-13 19:54:11 +02:00
Thomas Camlong 9b8b6c7cfe test: allow dnd-kit DOM modifier import 2026-08-13 19:54:11 +02:00
Thomas Camlong 282997e016 fix: preserve narrow board interactions 2026-08-13 19:54:10 +02:00
Thomas Camlong c973f54c8e fix: keep scaled board rails sticky 2026-08-13 19:54:10 +02:00
Thomas Camlong 8dd984fcb3 test: assert clipped zoom overflow 2026-08-13 19:54:10 +02:00
Thomas Camlong 2d17e9f58a test: avoid physical zoom size assumption 2026-08-13 19:54:10 +02:00
Thomas Camlong 3fe3d1714a test: assert zoomed canvas fit invariant 2026-08-13 19:54:10 +02:00
Thomas Camlong add61ce9a3 style: format board grid coverage 2026-08-13 19:54:10 +02:00
Thomas Camlong 1332f5626b fix: stabilize board rails 2026-08-13 19:54:10 +02:00
Thomas Camlong 203711e570 Fix board grid bounds and live growth 2026-08-13 19:54:09 +02:00
Thomas Camlong 2cb69c4ea3 fix: accept legacy dynamic sections 2026-08-13 19:54:08 +02:00
Thomas Camlong 7368262149 Rename layout gutter migrations 2026-08-13 19:54:08 +02:00
Thomas Camlong b9858b5ba4 style: format dnd migration metadata 2026-08-13 19:54:07 +02:00
Thomas Camlong b84502ef9b test(e2e): wait for settled grid geometry 2026-08-13 19:54:06 +02:00
Thomas Camlong b39bff43a9 fix container header spacing 2026-08-13 19:54:06 +02:00
Thomas Camlong d5dbf6fc17 fix: contain board canvas overflow 2026-08-13 19:54:06 +02:00
Thomas Camlong c6c96c4870 fix: contain collapsed layout and widget overflow 2026-08-13 19:53:52 +02:00
Thomas Camlong f438ea8aa9 test: wait for resized grid geometry 2026-08-13 19:53:42 +02:00
Thomas Camlong 279aeb3269 test: stabilize board grid authentication 2026-08-13 19:53:42 +02:00
ajnart f5ac662aeb style: auto-fix lint and format 2026-08-13 19:53:42 +02:00
Thomas Camlong 0b1c2f856f fix: smooth container collapse behavior 2026-08-13 19:53:42 +02:00
ajnart dd11fd8381 style: auto-fix lint and format 2026-08-13 19:53:32 +02:00
Thomas Camlong 07e6598f75 fix: refine container interactions 2026-08-13 19:53:32 +02:00
Thomas Camlong b7eb099977 fix: polish board grid interactions 2026-08-13 19:53:22 +02:00
Thomas Camlong 5ab3ee7d02 fix: address board grid CI and review 2026-08-13 19:53:10 +02:00
ajnart 59106053f2 style: auto-fix lint and format 2026-08-13 19:52:59 +02:00
Thomas Camlong 078c41b6ec fix: address board grid review 2026-08-13 19:52:59 +02:00
Thomas Camlong 1f5311e93b feat: rebuild board grid with dnd-kit 2026-08-13 19:52:59 +02:00
ajnart 5bde4d3cc5 test: wait for grid reconciliation between drags 2026-08-13 19:52:47 +02:00
ajnart 94074c368d fix: clarify board grid editing affordances 2026-08-13 19:52:47 +02:00
ajnart bf91f92d56 fix: improve board grid interactions 2026-08-13 19:52:37 +02:00
Thomas Camlong b7693c4238 test: target visible layout preview 2026-08-13 19:52:27 +02:00
Thomas Camlong fb18ac8fc5 test: tolerate sticky rail subpixel rounding 2026-08-13 19:52:27 +02:00
Thomas Camlong 336ba76fef test: avoid grid drag rounding boundary 2026-08-13 19:52:27 +02:00
ajnart 0110a2ef63 feat: rebuild board grid experience 2026-08-13 19:52:09 +02:00
Thomas Camlong 5dc4dbeaef style: format v2 workshop files 2026-08-13 19:51:44 +02:00
Thomas f3f2778f1f test: cover public custom widgets for guests (#6558) 2026-08-13 19:51:44 +02:00
Thomas c08b8979ee 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-13 19:51:44 +02:00
Thomas Camlong 9a7a828618 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-13 19:51:44 +02:00
Thomas Camlong 352a8ed0d0 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-13 19:51:44 +02:00
ajnart 6d3f7e3e36 test: wait for Home Assistant API readiness 2026-08-13 19:51:44 +02:00
Thomas Camlong 7833cca86b feat(workshop): add social previews 2026-08-13 19:51:44 +02:00
Thomas Camlong 6edf78acd7 fix: persist Workshop PocketBase data 2026-08-13 19:51:44 +02:00
Thomas Camlong 7d43f6e5d9 fix: defer Spotlight without loading feedback 2026-08-13 19:51:44 +02:00
Thomas Camlong 1723a9e744 fix: polish settings and command menu 2026-08-13 19:51:44 +02:00
Thomas Camlong 2ba31fd37b fix(workshop): streamline import review 2026-08-13 19:51:44 +02:00
Thomas Camlong 327e5c15ae fix(workshop): stack widget install flow 2026-08-13 19:51:44 +02:00
Thomas Camlong 3efe64a062 fix(workshop): expose moderation 2026-08-13 19:51:44 +02:00
Thomas Camlong 40b726b8bc fix: simplify custom widget preview controls 2026-08-13 19:51:44 +02:00
Thomas Camlong d2d0d16e4d fix(workshop): use PocketBase name 2026-08-13 19:51:44 +02:00
Thomas Camlong 63ad879022 fix(workshop): use GitHub handles in Homarr 2026-08-13 19:51:44 +02:00
Thomas Camlong 2ae0c2ab43 fix(docs): exclude generated build from typecheck 2026-08-13 19:51:44 +02:00
Thomas Camlong 2410ba5f5c fix(workshop): simplify GitHub identities 2026-08-13 19:51:25 +02:00
Thomas Camlong 8f0039245d docs: update WUD OpenAPI schema 2026-08-13 19:51:25 +02:00
Thomas Camlong 0871e4cf0d test: validate Workshop source locale 2026-08-13 19:51:25 +02:00
Thomas Camlong db9f42520a fix: stabilize v2 CI 2026-08-13 19:51:25 +02:00
Thomas Camlong 3ebcedec26 feat: add Workshop staging deployment 2026-08-13 19:51:25 +02:00
Thomas Camlong c41a3d427e fix: allow Workshop runtime config writes 2026-08-13 19:51:25 +02:00
Thomas Camlong 71c39aa18e fix: restore Workshop runtime URLs 2026-08-13 19:51:25 +02:00
Thomas Camlong 000c51a131 test: harden integration icon checks 2026-08-13 19:51:25 +02:00
Thomas Camlong 2c44c7f2b8 test: fix custom option repair fixture 2026-08-13 19:51:25 +02:00
Thomas Camlong c2cf25b4fb fix: repair stale custom widget options 2026-08-13 19:51:25 +02:00
Thomas Camlong 03ebb8c2db test(e2e): serialize container specs 2026-08-13 19:51:25 +02:00
Thomas Camlong f0b153aa37 fix(settings): avoid nested forms 2026-08-13 19:51:25 +02:00
Thomas Camlong 965d130ce7 fix(clock): defer live time until mount 2026-08-13 19:51:25 +02:00
Thomas Camlong a87a16edb1 fix(e2e): stabilize initial board hydration 2026-08-13 19:51:25 +02:00
Thomas Camlong 1493252e28 fix(clock): suppress live timestamp hydration noise 2026-08-13 19:51:25 +02:00
Thomas Camlong 46ff20b0a0 test(v2): make board tour dismissal optional 2026-08-13 19:51:25 +02:00
Thomas Camlong 1bc84dd90e test(v2): stabilize production E2E flows 2026-08-13 19:51:24 +02:00
Thomas Camlong 14cbc03621 fix(ci): exclude e2e project from unit gate 2026-08-13 19:51:08 +02:00
Thomas Camlong 9ec35b0cda fix(ci): exclude e2e tests from unit gate 2026-08-13 19:51:08 +02:00
Thomas Camlong 2d6dd2f0d0 fix(ci): detect custom widget runtime chunks 2026-08-13 19:51:08 +02:00
Thomas Camlong 572bc71396 fix(ci): refresh v2 generated artifacts 2026-08-13 19:51:08 +02:00
Thomas Camlong d4b1739c46 fix(ci): align Workshop pnpm version 2026-08-13 19:50:46 +02:00
ajnart 76495948e2 ci(workshop): restore image publishing 2026-08-13 19:50:46 +02:00
Thomas a95052c787 fix(workshop): harden deployment and deep links (#6542) 2026-08-13 19:50:46 +02:00
Thomas 400127b45c fix: harden v2 custom widgets and Workshop (#6480) 2026-08-13 19:50:46 +02:00
Thomas f77c47b94b Delete review.md 2026-08-13 19:49:25 +02:00
Thomas 825636c4f6 fix(custom-widgets): address final v2 review feedback (#6451) 2026-08-13 19:49:25 +02:00
Thomas 3aa14e22ed fix(custom-widgets): address v2 merge review (#6446) 2026-08-13 19:49:25 +02:00
Thomas acdfcbb18e feat(custom-widgets): complete Custom JSX v2 (#6390) 2026-08-13 19:48:57 +02:00
Thomas 3a1245cbc5 fix(custom-widgets): fix v2 editor crash and definition-not-found race (#6377) 2026-08-13 19:47:16 +02:00
Thomas Camlong f0963405ca test(integrations): wait for Home Assistant restart 2026-08-13 19:47:16 +02:00
Thomas Camlong d078d761d9 ci: publish multi-platform preview images 2026-08-13 19:47:16 +02:00
Thomas Camlong 84fcf02729 test(integrations): allow recurring calendar setup 2026-08-13 19:47:06 +02:00
Thomas Camlong c308ae5c6e fix(ci): prevent autofix race 2026-08-13 19:46:51 +02:00
Thomas Camlong 93192cfedc fix(ci): autofix commits skip CI to prevent race cancellation 2026-08-13 19:46:45 +02:00
Thomas Camlong 0f9ae13560 fix(ci): remove coverage step, fix auto-fix to format all affected workspaces 2026-08-13 19:46:13 +02:00
Thomas Camlong f4a6f37415 style: fix formatting in v2 files 2026-08-13 19:46:00 +02:00
homarr-renovate[bot] 95bfaf5127 chore(deps): update dependency @gfazioli/mantine-onboarding-tour to ^4.0.7 (#6343)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2026-08-13 19:46:00 +02:00
Thomas 83e3b22f9a feat(custom-widgets): Full Mantine v9 + SubFetch support for custom JSX (#6304) 2026-08-13 19:45:44 +02:00
Thomas cd71404288 Delete .opencode directory 2026-08-13 19:44:18 +02:00
Thomasandcopilot-swe-agent[bot] 7a089ab177 feat: community workshop for custom widgets and CSS (#5984)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-08-13 19:44:18 +02:00
2153 changed files with 190002 additions and 48505 deletions

No files matched your search

@@ -1,44 +0,0 @@
---
name: documentation-sync
description: Keep Homarr documentation in sync with code changes. Use when modifying code that affects user-facing behavior, or when adding/modifying widgets, integrations, cron jobs, API routes, env vars, permissions, auth providers, or UI in apps/docs. Covers the docs/ mapping table, integration doc pattern, and widget doc pattern.
---
# Documentation Sync
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
## What triggers a doc update
| Code change | Doc location |
| ---------------------- | ----------------------------------------------------------- |
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
| Changed API surface | `apps/docs/docs/management/api.mdx` |
| New/changed env vars | `apps/docs/docs/advanced/` |
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
| UI/UX changes | Relevant getting-started or management pages |
| New cron job | `apps/docs/docs/management/tasks.mdx` |
| Changed permissions | `apps/docs/docs/management/users.mdx` |
## Integration doc pattern
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
- Import metadata from `@homarr/definitions` where possible
## Widget doc pattern
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
## Development
- Run docs locally: `pnpm dev:docs` from root
- Build docs: `pnpm turbo build --filter=@homarr/docs`
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
- Docusaurus strict mode throws on broken links — fix any broken references before committing
@@ -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.
-1
View File
@@ -1 +0,0 @@
../.agents/skills
@@ -1,6 +1,6 @@
---
name: codebase-context
description: Comprehensive Homarr codebase architecture and conventions reference. Use when orienting in the monorepo, understanding package dependency layers, the three runtime services and ports, tRPC router structure, multi-database support, widget/integration/cron system architecture, routing, auth providers, env vars, or key patterns before writing code.
description: Comprehensive Homarr codebase architecture and conventions reference
alwaysApply: true
---
# Homarr Codebase Context
@@ -27,7 +27,7 @@ Homarr is an open-source, self-hosted dashboard for managing homelab services. I
## Repository Structure
```text
```
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
@@ -77,11 +77,11 @@ homarr/
## Three Runtime Services
| Service | Port | Role |
| --------- | ---- | -------------------------------------------- |
| Next.js | 3000 | Main web app (SSR + API routes) |
| WebSocket | 3001 | tRPC subscriptions via `ws` |
| Tasks | 3002 | Cron job runner + management API via Fastify |
| Service | Port | Role |
|---------|------|------|
| Next.js | 3000 | Main web app (SSR + API routes) |
| WebSocket | 3001 | tRPC subscriptions via `ws` |
| Tasks | 3002 | Cron job runner + management API via Fastify |
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
@@ -110,7 +110,6 @@ In Docker, nginx listens on **7575** and proxies to all three. The tasks service
### Multi-Database Support
Drizzle schemas exist in three parallel implementations:
- `packages/db/schema/sqlite.ts`
- `packages/db/schema/mysql.ts`
- `packages/db/schema/postgresql.ts`
@@ -120,7 +119,6 @@ Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-pos
### Widget System
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
- Loads dynamically via `next/dynamic`
@@ -133,7 +131,6 @@ Integrations are defined in `packages/definitions/src/integration.ts` (`integrat
### Cron Job System
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
- Uses `createCronJob` with a cron expression
- Publishes results to Redis channels
- Widget subscriptions consume those channels via tRPC subscriptions
@@ -161,7 +158,6 @@ Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `o
## Environment Variables
Key env vars (from `.env.example`):
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
- `DB_URL`: Database connection string
- `AUTH_SECRET`: NextAuth secret
@@ -193,3 +189,16 @@ Key env vars (from `.env.example`):
- Drag-and-drop via `@dnd-kit/*`
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
## Documentation (`apps/docs`)
Docusaurus 3 site at `apps/docs/`, package name `@homarr/docs`.
- Run with `pnpm dev:docs` from root
- Build with `pnpm turbo build --filter=@homarr/docs`
- Uses Tailwind CSS 3 for custom pages (not Mantine)
- Imports `@homarr/definitions` for type-safe integration/widget metadata
- When adding or modifying widgets, integrations, cron jobs, API routes, env vars, or user-facing features, you MUST also update corresponding docs in `apps/docs/docs/`
- Integration docs: `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
- Widget docs: `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
- Strict link checking: broken links/anchors cause build failure
+50
View File
@@ -0,0 +1,50 @@
---
description: Keep documentation in sync with code changes
globs:
- packages/definitions/src/**
- packages/widgets/src/**
- packages/integrations/src/**
- packages/api/src/**
- apps/nextjs/src/**
- apps/docs/**
---
# Documentation Sync Rule
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
## What triggers a doc update
| Code change | Doc location |
|---|---|
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
| Changed API surface | `apps/docs/docs/management/api.mdx` |
| New/changed env vars | `apps/docs/docs/advanced/` |
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
| UI/UX changes | Relevant getting-started or management pages |
| New cron job | `apps/docs/docs/management/tasks.mdx` |
| Changed permissions | `apps/docs/docs/management/users.mdx` |
## Integration doc pattern
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
- Import metadata from `@homarr/definitions` where possible
## Widget doc pattern
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
## Development
- Run docs locally: `pnpm dev:docs` from root
- Build docs: `pnpm turbo build --filter=@homarr/docs`
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
- Docusaurus strict mode throws on broken links — fix any broken references before committing
@@ -1,8 +1,3 @@
---
name: datatable-migration
description: Playbook for migrating tables from mantine-react-table (MRT) to mantine-datatable. Use when converting any table component from mantine-react-table to mantine-datatable, working on DataTable column/sort/expansion/context-menu code, or implementing responsive column visibility, column persistence, or transparent tables in widgets.
---
# 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.
@@ -12,12 +7,10 @@ This document captures every pattern, fix, and decision from the downloads widge
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`
@@ -26,13 +19,13 @@ Current `mantine-react-table` consumers that could be migrated:
- `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
@@ -74,7 +67,6 @@ const columns: DataTableColumn<MyRow>[] = [
```
Key differences:
- `accessorKey` → `accessor`
- `header` → `title`
- `size` → `width`
@@ -126,6 +118,8 @@ const sortedData = useMemo(() => {
<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.
@@ -150,21 +144,13 @@ Create a CSS file alongside the component:
/* 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;
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;
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
backdrop-filter: blur(8px);
}
@@ -186,16 +172,14 @@ Create a CSS file alongside the component:
/* Row expansion area */
.my-table .mantine-datatable-row-expansion-cell {
background-color: color-mix(
in srgb,
var(--mantine-color-body) 40%,
transparent
) !important;
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.
@@ -211,38 +195,11 @@ interface SizeConfig {
}
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,
},
},
{ 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,
};
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) {
@@ -258,9 +215,7 @@ const size = useMemo(() => getSizeConfig(width), [width]);
Then use a visibility check lookup table for columns:
```typescript
const columnVisibilityChecks: Partial<
Record<string, (ctx: ColumnContext) => boolean>
> = {
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,
@@ -275,6 +230,8 @@ function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
}
```
---
## 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.
@@ -319,13 +276,8 @@ const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted
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 { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } =
useDataTableColumns<MyRow>({ key: storeKey, columns });
const lastStoreKey = useRef(storeKey);
const hydrated = useRef(false);
@@ -339,22 +291,11 @@ useEffect(() => {
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
if (Object.keys(savedWidths).length > 0) {
setMultipleColumnWidths(
Object.entries(savedWidths).map(([accessor, w]) => ({
accessor,
width: w,
})),
Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w })),
);
}
requestAnimationFrame(() => {
hydrated.current = true;
});
}, [
storeKey,
savedOrder,
savedWidths,
setColumnsOrder,
setMultipleColumnWidths,
]);
requestAnimationFrame(() => { hydrated.current = true; });
}, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]);
```
### Persist localStorage → server
@@ -367,26 +308,22 @@ 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);
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 (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 v = entry[key as keyof typeof entry];
if (typeof v === "number") widthMap[key] = v;
else if (typeof v === "string" && v.endsWith("px"))
widthMap[key] = parseInt(v, 10);
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) });
}
@@ -395,6 +332,8 @@ useEffect(() => {
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:
@@ -426,41 +365,15 @@ const handleContextMenu = useCallback(
The menu component uses `Portal` for correct z-index:
```tsx
function RowContextMenu({
state,
onClose,
}: {
state: ContextMenuState;
onClose: () => void;
}) {
function RowContextMenu({ state, onClose }: { state: ContextMenuState; onClose: () => void }) {
return (
<Portal>
<Menu
opened
onClose={onClose}
closeOnItemClick={false}
position="right-start"
offset={0}
>
<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,
}}
/>
<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.Item onClick={() => { /* action */ onClose(); }}>Action</Menu.Item>
</Menu.Dropdown>
</Menu>
</Portal>
@@ -470,6 +383,8 @@ function RowContextMenu({
**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
@@ -489,6 +404,8 @@ function RowContextMenu({
**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
@@ -549,11 +466,8 @@ 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;
}
try { return JSON.parse(value) as T; }
catch { return fallback; }
}
```
@@ -571,19 +485,20 @@ If the widget has configurable default sort, sync it:
```typescript
useEffect(() => {
setSortStatus({
columnAccessor: options.defaultSort,
direction: defaultSortDirection,
});
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`
@@ -594,14 +509,12 @@ useEffect(() => {
```typescript
const mutationOptions = {
onSettled: () => void utils.myQuery.invalidate(),
onError: () =>
showErrorNotification({
title: t("errors.actionFailed"),
message: t("errors.actionFailedMessage"),
}),
onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }),
};
```
---
## 10. i18n
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
@@ -612,6 +525,8 @@ const mutationOptions = {
- 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:
@@ -626,6 +541,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
<DataTable style={{ pointerEvents: tablePointerEvents }} />
```
---
## 12. DataTable Props Reference (Commonly Used)
```typescript
@@ -664,6 +581,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
/>
```
---
## 13. Layout Stability
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
@@ -672,6 +591,8 @@ if (isEditMode) rowContextMenuHandler = undefined;
- 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`)
@@ -1,14 +1,13 @@
---
name: mcp-integration
description: Expose tRPC procedures as MCP tools for AI clients. Use when modifying procedures in packages/api/src/router, adding a new tRPC procedure or router, or deciding what to expose via Homarr's MCP interface. Covers .meta({ mcp }), router registration in packages/api/src/mcp.ts, input schema rules, and what to expose vs skip.
description: How to expose tRPC procedures as MCP tools for AI clients
globs: packages/api/src/router/**/*.ts
alwaysApply: false
---
# MCP Integration
# MCP Integration Guide
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
Exposing a procedure makes it callable by any AI client with an API key. Before adding `.meta({ mcp })`, review what the tool exposes: authorization requirements, sensitive data, tenant isolation, auditability, and whether the action is destructive. Prefer exposing read-only queries over mutations unless the mutation is safe and permission-scoped. When in doubt, ask for a security review before shipping a new MCP tool.
## Adding MCP to a procedure
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
@@ -17,7 +16,7 @@ Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure cha
// Read-only tool (query)
myProcedure: protectedProcedure
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
.input(z.object({ limit: z.number().default(10) }))
.input(z.object({ limit: z.number().default(50) }))
.query(async ({ ctx, input }) => { ... })
// Write tool (mutation)
@@ -39,27 +38,23 @@ The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()`
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
**Must include:**
- What the tool returns or does
- Which integrations/services it works with (if applicable)
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
- Permission requirements (if non-obvious)
**Good:**
```text
```
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
```
**Bad:**
```text
```
"Get calendar events"
```
**For tools that return permission fields**, explain what they mean:
```text
```
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
```
@@ -82,13 +77,11 @@ export const mcpRouter = createTRPCRouter({
## What to expose vs. what to skip
**Expose:**
- Data queries (list, search, get by ID)
- Actions users would ask an AI to do (toggle, create, delete, approve)
- Status checks (health, stats, summaries)
**Skip:**
- Subscriptions (WebSocket-only, not supported by MCP)
- Internal procedures (session management, onboarding steps)
- File upload/download procedures
+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
-1
View File
@@ -33,7 +33,6 @@ body:
options:
# The below comment is used to insert a new version with on-release.yml
#NEXT_VERSION#
- 1.75.0
- 1.74.0
- 1.73.0
- 1.72.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)
+9 -21
View File
@@ -1,4 +1,4 @@
name: Auto-fix lint and format
name: Formatting
on:
pull_request:
@@ -6,42 +6,30 @@ on:
branches: ["*"]
permissions:
contents: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
autofix:
name: Auto-fix
if: github.event.pull_request.head.repo.id == github.repository_id
format:
name: Check formatting
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
persist-credentials: false
- name: Setup
uses: ./tooling/github/setup
- name: Get changed files
id: changed
- name: Check changed files
env:
BASE_REF: ${{ github.base_ref }}
run: |
git diff --name-only --diff-filter=ACMR "origin/${BASE_REF}...HEAD" -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
- name: Auto-fix lint + format
if: steps.changed.outputs.count != '0'
run: |
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
- name: Commit fixes
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: "style: auto-fix lint and format"
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 --
+167 -18
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: ${{ github.ref != 'refs/heads/main' }}
# 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
@@ -87,6 +89,23 @@ jobs:
- 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 feature contracts
run: pnpm check:feature-contracts
- name: Test feature platform
run: pnpm test:feature-platform
- name: Validate Custom Widget bundle budget
run: |
pnpm --filter nextjs build
pnpm check:custom-widget-bundle
- name: Run unit tests
run: pnpm test
@@ -102,6 +121,8 @@ jobs:
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.preview-build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
@@ -184,38 +205,166 @@ 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')
)
env:
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
run: echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
if: steps.preview.outputs.tag != ''
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push preview image
if: steps.preview.outputs.tag != ''
env:
PREVIEW_TAG: ${{ steps.preview.outputs.tag }}
run: |
docker tag homarr-ci "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
docker push "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
- name: Push amd64 preview image by digest
id: preview-build
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/build-push-action@v7
with:
context: .
network: host
platforms: linux/amd64
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=homarr-linux-amd64
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
- name: Show Act publication result
if: env.ACT
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
preview-arm64:
name: Build arm64 preview image
needs: fast-gate
if: |
(
(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
timeout-minutes: 20
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.preview-build.outputs.digest }}
steps:
- uses: actions/checkout@v7
if: "!env.ACT"
with:
persist-credentials: false
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
- name: Log in to GHCR
if: "!env.ACT"
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: "!env.ACT"
uses: docker/setup-buildx-action@v4
- name: Build and push arm64 preview image by digest
id: preview-build
if: "!env.ACT"
uses: docker/build-push-action@v7
with:
context: .
network: host
platforms: linux/arm64
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=homarr-linux-arm64
cache-to: type=gha,scope=homarr-linux-arm64,mode=max
secrets: |
TURBO_API=${{ secrets.TURBO_API }}
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
publish-preview:
name: Publish multi-platform preview image
needs: [container, preview-arm64]
if: |
(
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
)
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:
- name: Log in to GHCR
if: "!env.ACT"
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
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:
AMD64_DIGEST: ${{ needs.container.outputs.digest }}
ARM64_DIGEST: ${{ needs.preview-arm64.outputs.digest }}
run: |
docker buildx imagetools create -t "${PREVIEW_IMAGE}:${PREVIEW_TAG}" \
"${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 }}
+12
View File
@@ -16,6 +16,7 @@ out/
next-env.d.ts
# artifacts
.ai-evaluations/
packages/db/migrations/*/migrate.cjs
# nitro
@@ -73,6 +74,9 @@ apps/nextjs/public/images/background.png
.opencode
.agents/*
!.agents/skills/
.agents/skills/*
!.agents/skills/homarr-custom-widget/
!.agents/skills/homarr-custom-widget/**
skills-lock.json
# docusaurus
@@ -80,3 +84,11 @@ apps/docs/.docusaurus
apps/docs/build
.pnpm-store
benchmark-results
# local performance investigation (never ship benchmark data or restored backups)
.widget-performance/
.bench/
homarr-backup*.zip
*.sqlite-wal
*.sqlite-shm
/scripts/benchmarks/
@@ -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?
-8
View File
@@ -1,8 +0,0 @@
{
"mcpServers": {
"mantine": {
"command": "npx",
"args": ["-y", "@mantine/mcp-server"]
}
}
}
+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" }
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Loaded 100 of 2153 files, more files were not shown because too many files have changed in this diff. Show more