Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8d12c19e | ||
|
|
10ba298233 | ||
|
|
7855278ed1 | ||
|
|
aa179f1ec0 | ||
|
|
f455e71be5 | ||
|
|
f221a5b4d3 | ||
|
|
c18fa8cf77 | ||
|
|
5f572f1b92 | ||
|
|
a7aa89fafe | ||
|
|
e723cbe6dd | ||
|
|
411e75e799 | ||
|
|
0553d60a58 | ||
|
|
7da2e10064 | ||
|
|
a002b91add | ||
|
|
9c441571bc | ||
|
|
643ecf3df7 | ||
|
|
1b2f1bd720 | ||
|
|
c82965067c | ||
|
|
8abc9cae6c | ||
|
|
becc6a71a9 | ||
|
|
78a4f0e513 | ||
|
|
70e1b29913 | ||
|
|
73de83b48f | ||
|
|
26d12d9fad | ||
|
|
f0eddda573 | ||
|
|
b2c72d54c2 | ||
|
|
a56ec5d06a | ||
|
|
40e34181e7 | ||
|
|
6548da3772 | ||
|
|
3ee9b4f0c4 | ||
|
|
9303dbd284 | ||
|
|
97d8390b20 | ||
|
|
c9a48fa23f | ||
|
|
e7c8e2e01e | ||
|
|
fcca57ddf7 | ||
|
|
8b6387f321 | ||
|
|
b8bc304c5c | ||
|
|
ec295fe26e | ||
|
|
e56112cfd7 | ||
|
|
449adc5b12 | ||
|
|
240397e7ac | ||
|
|
3efb719eee | ||
|
|
382cffc015 | ||
|
|
0073dfe044 | ||
|
|
a22fb60647 | ||
|
|
89e4cb843d | ||
|
|
65a96b6b14 | ||
|
|
357355283c | ||
|
|
a2a408410a | ||
|
|
0d9eeb5752 | ||
|
|
bf0878c68b | ||
|
|
eb6d337dfb | ||
|
|
978b3c7f33 | ||
|
|
07547320f1 | ||
|
|
02870fc392 | ||
|
|
b6fc27e44d | ||
|
|
d5cf1b82f2 | ||
|
|
e022c3f735 | ||
|
|
14cc3e8a44 | ||
|
|
93d160aebd | ||
|
|
493e93ea42 | ||
|
|
85c20702cd | ||
|
|
91b248d395 | ||
|
|
b7eadc7867 | ||
|
|
1861d8415c | ||
|
|
7fe5f8f81c | ||
|
|
b3a30e0cf6 | ||
|
|
763e1324e8 | ||
|
|
5188ed89d6 | ||
|
|
9eafd4f7a7 | ||
|
|
71ffa5d1be | ||
|
|
1b843817a4 | ||
|
|
8ae9630b3e | ||
|
|
63f5c43b69 | ||
|
|
e97fe7861d | ||
|
|
19760e5722 | ||
|
|
2570de25a6 | ||
|
|
f46ccadbe4 | ||
|
|
6297ca4b16 | ||
|
|
2accdc2fad | ||
|
|
8a08293016 | ||
|
|
7591f7e313 | ||
|
|
011ce8e5a3 | ||
|
|
b2fa46c760 | ||
|
|
9e14a07e64 | ||
|
|
81aa704537 | ||
|
|
f0f9539466 | ||
|
|
3c2f95d529 | ||
|
|
64e3d04517 | ||
|
|
002381d294 | ||
|
|
9b406d5d83 | ||
|
|
4c475b8c0a | ||
|
|
f3014fe271 | ||
|
|
bd4f6a92c6 | ||
|
|
7e8c139590 | ||
|
|
d241bac55d | ||
|
|
58a24569ce | ||
|
|
53b518960c | ||
|
|
ddf8ba9948 | ||
|
|
a2219b72f6 | ||
|
|
7150cdbbab | ||
|
|
ff14613f95 | ||
|
|
78b3dc1922 | ||
|
|
2e97b337d2 | ||
|
|
369a2660b1 | ||
|
|
60aec370d6 | ||
|
|
6a76b9eafe | ||
|
|
76ba13fbad | ||
|
|
150d38be62 | ||
|
|
a4bbed0cb8 | ||
|
|
90fa9e8741 | ||
|
|
4b20d6ecd0 | ||
|
|
6cac34e968 | ||
|
|
c6f77cec2e | ||
|
|
acc97da84b | ||
|
|
6b10ac2ab5 | ||
|
|
c7563f2c39 | ||
|
|
96676182f4 | ||
|
|
3769d88ebb | ||
|
|
d9c35744f6 | ||
|
|
33991f4e22 | ||
|
|
df300919fc | ||
|
|
315245e048 | ||
|
|
ebdfeb724b | ||
|
|
e842177d9b | ||
|
|
e3a713d2df | ||
|
|
e7879ed3a2 | ||
|
|
19e72fc757 | ||
|
|
38fc5dd582 | ||
|
|
6d284adb98 | ||
|
|
bd171bf73b | ||
|
|
1ee519bb7a | ||
|
|
c53852fdb6 | ||
|
|
206905ea2d | ||
|
|
50537d077c | ||
|
|
229a559aff | ||
|
|
4e514ab151 | ||
|
|
960551f573 | ||
|
|
304ea089a2 | ||
|
|
199f6ffc3b | ||
|
|
7e569bff4c | ||
|
|
0ec5f36050 | ||
|
|
e22be4dca6 | ||
|
|
3237d5953f | ||
|
|
1325418300 | ||
|
|
4505ce96c9 | ||
|
|
b491d45fdc | ||
|
|
168091134d | ||
|
|
28e58a5a5c | ||
|
|
3c533f2d96 | ||
|
|
2e3e654748 | ||
|
|
409e8ed71a | ||
|
|
95489b692c | ||
|
|
b8c06a8622 | ||
|
|
f62add8ae4 | ||
|
|
498d781e19 | ||
|
|
0afed2c3f6 | ||
|
|
efc0ee470b | ||
|
|
fe41382074 | ||
|
|
c851609041 | ||
|
|
03338b1755 | ||
|
|
8a44451c72 | ||
|
|
d434953478 | ||
|
|
7aaa646b82 | ||
|
|
c4f6d98300 | ||
|
|
81fdc5856b | ||
|
|
4e193da3a4 | ||
|
|
29f7c35fc1 | ||
|
|
8775597d5a | ||
|
|
339a0d8e2b | ||
|
|
4ddc7266e9 | ||
|
|
42bf55641b | ||
|
|
c92e074a6f | ||
|
|
741e641bd2 | ||
|
|
9b7f189b02 | ||
|
|
0070538857 | ||
|
|
a11c53ebf7 | ||
|
|
e55def9b60 | ||
|
|
fc66211dd2 | ||
|
|
174e0a7606 | ||
|
|
30cb066b7f | ||
|
|
f1ce0fffad | ||
|
|
9c5ab62b3a | ||
|
|
6adb49eedf | ||
|
|
6482ea6ae8 | ||
|
|
f7d5fde8a9 | ||
|
|
de368a6969 | ||
|
|
e1280f311c | ||
|
|
517dcfa64e | ||
|
|
6faba5f772 | ||
|
|
cca6de2b86 | ||
|
|
c24ccde404 | ||
|
|
d28dd380e5 | ||
|
|
1de167c98d | ||
|
|
7b1a801ab8 | ||
|
|
8239a631f2 | ||
|
|
775ae0147c | ||
|
|
ece6757979 | ||
|
|
dd707bed7e | ||
|
|
276431ef4c | ||
|
|
c25c1dbac7 | ||
|
|
66ad01e7ce | ||
|
|
d4ffd19f01 | ||
|
|
4f693b0465 | ||
|
|
6437e57565 | ||
|
|
2566be2838 | ||
|
|
dc71084323 | ||
|
|
e41605640b | ||
|
|
d091362db2 | ||
|
|
653dadaeff | ||
|
|
af6fb75e6b | ||
|
|
b2fbd10965 | ||
|
|
63dca7d1b6 | ||
|
|
22d5770977 | ||
|
|
dcef36ffa7 | ||
|
|
f65969053a | ||
|
|
8eb6735387 | ||
|
|
7841359917 | ||
|
|
a45b077b7c | ||
|
|
111a72c283 | ||
|
|
b988e431e8 | ||
|
|
87f264a286 | ||
|
|
83f80eb9a7 | ||
|
|
3c6b2a5d49 | ||
|
|
1b658b631c | ||
|
|
384dc46f26 | ||
|
|
48abdb52af | ||
|
|
45d8bc9443 | ||
|
|
b8f360c04e | ||
|
|
fc5ba7d888 | ||
|
|
b7fdff9f7f | ||
|
|
dfafad15bf | ||
|
|
ceed0979a1 | ||
|
|
474d9968b5 | ||
|
|
295eb836bb | ||
|
|
7b11d6673b | ||
|
|
ce48cabb36 | ||
|
|
443fbe0c36 | ||
|
|
9c64f8772d | ||
|
|
c0596d1b5e | ||
|
|
517d9320dc | ||
|
|
bb6e8e5c5a | ||
|
|
f4f6773f26 | ||
|
|
aea2832370 | ||
|
|
59cb3a4d99 | ||
|
|
ebd63bea74 | ||
|
|
ef9c37d5ec | ||
|
|
a1c2559541 | ||
|
|
d80e883479 | ||
|
|
3cef3cbdbd | ||
|
|
ae68d599be | ||
|
|
e4bf2aa13d | ||
|
|
4ed1eb5d59 | ||
|
|
66043eb78d | ||
|
|
8a1b2a5cd6 | ||
|
|
0298256b62 | ||
|
|
a9b0c2ae47 | ||
|
|
f6f755d5c9 | ||
|
|
b3e75df7fa | ||
|
|
bcd3d901d2 | ||
|
|
30ca26cb1a | ||
|
|
bb5a9562d5 | ||
|
|
5501eb555e | ||
|
|
28a65d61f9 | ||
|
|
f98766873f | ||
|
|
7bf22639ba | ||
|
|
f991dc6334 | ||
|
|
1ad1439828 | ||
|
|
9918386257 | ||
|
|
97a399d0fa | ||
|
|
f357eb4da6 | ||
|
|
d6a259bdbe | ||
|
|
9588c3860d | ||
|
|
c0cdedc039 | ||
|
|
1f60b4b4be | ||
|
|
8d06201b41 | ||
|
|
edf3f6cd84 | ||
|
|
9f2b52dd7f | ||
|
|
87a42f09be | ||
|
|
d40e6852e6 | ||
|
|
37f5473054 | ||
|
|
3d7c95ddcc | ||
|
|
454a35d1a9 | ||
|
|
a979e8d78a | ||
|
|
5ceef06b4b | ||
|
|
33695753dd | ||
|
|
3faac7a96d | ||
|
|
38440a2e57 | ||
|
|
c5800785c9 | ||
|
|
b66c73141f | ||
|
|
0a88db1296 | ||
|
|
23e4fae275 | ||
|
|
1e058c6ed1 | ||
|
|
ebf6c0d97b | ||
|
|
5ede7ef4b9 | ||
|
|
165d148cfc | ||
|
|
8e0a52fcd0 | ||
|
|
f3420a4149 | ||
|
|
6dc9a565b0 | ||
|
|
07547a462c | ||
|
|
73e4b80a26 | ||
|
|
4b647ec2c9 | ||
|
|
03c448f1f8 | ||
|
|
e77174ff15 | ||
|
|
18ed16d07e | ||
|
|
9c57e0cff9 | ||
|
|
c45a9d047d | ||
|
|
efe477e5b5 | ||
|
|
41389b61e2 | ||
|
|
0c5fc1d331 | ||
|
|
6eb75ca559 | ||
|
|
1656890b77 | ||
|
|
3ed839334b | ||
|
|
b5162a9b95 | ||
|
|
40c7b1523b | ||
|
|
d37a2ba963 | ||
|
|
26a9752206 | ||
|
|
3e1257bafc | ||
|
|
63699b1610 | ||
|
|
27448868dd | ||
|
|
8a8e222da3 | ||
|
|
f3360e0d97 | ||
|
|
8d9d2fe802 | ||
|
|
96d2c78879 | ||
|
|
e9e3a523dc | ||
|
|
0ef0e32e18 | ||
|
|
cba2df72e2 | ||
|
|
c0445cdca2 | ||
|
|
5f3d676af0 | ||
|
|
e9b24f1242 | ||
|
|
5db95f2def | ||
|
|
10ef871777 | ||
|
|
854b59ede4 | ||
|
|
eb918b805a | ||
|
|
4a4b04d471 | ||
|
|
3943741838 | ||
|
|
e425e060d9 | ||
|
|
6754e53f56 | ||
|
|
357adffb30 | ||
|
|
d24eca12ef | ||
|
|
dfcd1dbb64 | ||
|
|
252ce998ae | ||
|
|
a940a8223d | ||
|
|
bbd1abd4f0 | ||
|
|
7adc18b1ce | ||
|
|
6a5162d615 | ||
|
|
02fe99d5aa | ||
|
|
7bbca3bd40 | ||
|
|
923a528038 | ||
|
|
bb599e7fae | ||
|
|
bb5df3a2e6 | ||
|
|
c805d5c631 | ||
|
|
747234c040 | ||
|
|
b828f59568 | ||
|
|
60ea03e72e | ||
|
|
e9853f1fb1 | ||
|
|
f8b364dce2 | ||
|
|
cd8773681f | ||
|
|
2f99f1f34b | ||
|
|
6fc851f054 | ||
|
|
7bedf4a13d | ||
|
|
a9c8b65c14 | ||
|
|
e336059079 | ||
|
|
708105ab3f | ||
|
|
e6bd1adfc2 | ||
|
|
b217d8e026 | ||
|
|
35b7c4fc2d | ||
|
|
251a4c8ae8 | ||
|
|
b4404113c4 | ||
|
|
c1efaabd36 | ||
|
|
b75483982f | ||
|
|
c1da19c9b2 | ||
|
|
46898cd0ea | ||
|
|
6f4cd7522f | ||
|
|
eaaeaaccdb | ||
|
|
d63653142d | ||
|
|
08588da0d1 | ||
|
|
464f30799a | ||
|
|
bd4bbcaca2 | ||
|
|
09b82ba171 | ||
|
|
f38eb034da | ||
|
|
a3a89db727 | ||
|
|
c099a8eef6 | ||
|
|
9437d211c8 | ||
|
|
096cf56ac9 | ||
|
|
851129e62e | ||
|
|
cc1b22f052 | ||
|
|
5df4358f0d | ||
|
|
821b5a555d | ||
|
|
a0f4a58386 | ||
|
|
7c840bcfa0 | ||
|
|
38d30acb87 | ||
|
|
ae515584cc | ||
|
|
9338d6d4ec | ||
|
|
d98b7797c1 | ||
|
|
3c739dc636 | ||
|
|
639cb8d35f | ||
|
|
f1ac482684 | ||
|
|
9cc7fd2051 | ||
|
|
d4d951b99c | ||
|
|
a4f115064e | ||
|
|
07e0642192 | ||
|
|
965ccb32d6 | ||
|
|
be08f70c8d | ||
|
|
4892c399ba | ||
|
|
687fb323f7 | ||
|
|
56cc021f98 | ||
|
|
836a4cd46b | ||
|
|
a607dff50a | ||
|
|
68751d85dc | ||
|
|
869dd45de5 | ||
|
|
352aa7dd64 | ||
|
|
801221d2da | ||
|
|
861538e9a0 | ||
|
|
cb7b49b3e7 | ||
|
|
f5cced4080 | ||
|
|
e429d7540a | ||
|
|
3bd80dcc58 | ||
|
|
0e01ac67df | ||
|
|
8259df58f6 | ||
|
|
3485ef0877 | ||
|
|
2214acd17e | ||
|
|
25b835dca9 | ||
|
|
2efa6bcd4c | ||
|
|
c67c893f8f | ||
|
|
e7e44375ef | ||
|
|
842e86012d | ||
|
|
a8a7545f40 | ||
|
|
ed7b30dc5f | ||
|
|
c09ed96978 | ||
|
|
44b55a401c | ||
|
|
f00f5b83b5 | ||
|
|
5553c4e0c5 | ||
|
|
6cdda54e10 | ||
|
|
0f9d038aaf | ||
|
|
a38e731209 | ||
|
|
b1b17ad69f | ||
|
|
add40370aa | ||
|
|
a2763d1f6f | ||
|
|
f3048f4804 | ||
|
|
6bfa9c33d0 | ||
|
|
16982aa178 | ||
|
|
a2400f8412 | ||
|
|
6e455760a3 | ||
|
|
0b9b77c632 | ||
|
|
346bff540f | ||
|
|
f8771c371f | ||
|
|
71cc3abbe9 | ||
|
|
a098c42013 | ||
|
|
4cf83443fd | ||
|
|
f5b4889663 | ||
|
|
06aa5caf0b | ||
|
|
d3534dfcf8 | ||
|
|
f7591dd196 | ||
|
|
0c5f7a45bb | ||
|
|
cdbba92f31 | ||
|
|
3b0633f6a8 | ||
|
|
d0880aa04c | ||
|
|
7cf6a5d5ab | ||
|
|
d5763737c3 | ||
|
|
00b0b215b0 | ||
|
|
5f08327c5c | ||
|
|
2645cb426a | ||
|
|
65e866064e | ||
|
|
392df12d8a | ||
|
|
6209980b27 | ||
|
|
3f04cf83a2 | ||
|
|
e5a8194c03 | ||
|
|
90cf0e1127 | ||
|
|
498481d539 | ||
|
|
65da74858b | ||
|
|
af2b7b3157 | ||
|
|
db40779492 | ||
|
|
91aeac8377 | ||
|
|
dfe2ddf34b | ||
|
|
34f2a0bd24 | ||
|
|
4227e392b5 | ||
|
|
6893205ee0 | ||
|
|
28ac23e66b | ||
|
|
cddae6719e | ||
|
|
0cb75982fc | ||
|
|
36de8131c8 | ||
|
|
2f28f45ff1 | ||
|
|
915af9ae96 | ||
|
|
db5d088330 | ||
|
|
d1a2ed8fd7 | ||
|
|
d6b114e423 | ||
|
|
e41d938a5f | ||
|
|
afa54755fd | ||
|
|
0482c040ec | ||
|
|
5dc4dbeaef | ||
|
|
f3f2778f1f | ||
|
|
c08b8979ee | ||
|
|
9a7a828618 | ||
|
|
352a8ed0d0 | ||
|
|
6d3f7e3e36 | ||
|
|
7833cca86b | ||
|
|
6edf78acd7 | ||
|
|
7d43f6e5d9 | ||
|
|
1723a9e744 | ||
|
|
2ba31fd37b | ||
|
|
327e5c15ae | ||
|
|
3efe64a062 | ||
|
|
40b726b8bc | ||
|
|
d2d0d16e4d | ||
|
|
63ad879022 | ||
|
|
2ae0c2ab43 | ||
|
|
2410ba5f5c | ||
|
|
8f0039245d | ||
|
|
0871e4cf0d | ||
|
|
db9f42520a | ||
|
|
3ebcedec26 | ||
|
|
c41a3d427e | ||
|
|
71c39aa18e | ||
|
|
000c51a131 | ||
|
|
2c44c7f2b8 | ||
|
|
c2cf25b4fb | ||
|
|
03ebb8c2db | ||
|
|
f0b153aa37 | ||
|
|
965d130ce7 | ||
|
|
a87a16edb1 | ||
|
|
1493252e28 | ||
|
|
46ff20b0a0 | ||
|
|
1bc84dd90e | ||
|
|
14cbc03621 | ||
|
|
9ec35b0cda | ||
|
|
2d6dd2f0d0 | ||
|
|
572bc71396 | ||
|
|
d4b1739c46 | ||
|
|
76495948e2 | ||
|
|
a95052c787 | ||
|
|
400127b45c | ||
|
|
f77c47b94b | ||
|
|
825636c4f6 | ||
|
|
3aa14e22ed | ||
|
|
acdfcbb18e | ||
|
|
3a1245cbc5 | ||
|
|
f0963405ca | ||
|
|
d078d761d9 | ||
|
|
84fcf02729 | ||
|
|
c308ae5c6e | ||
|
|
93192cfedc | ||
|
|
0f9ae13560 | ||
|
|
f4a6f37415 | ||
|
|
95bfaf5127 | ||
|
|
83e3b22f9a | ||
|
|
cd71404288 | ||
|
|
7a089ab177 |
No files matched your search
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: codebase-context
|
||||
description: Navigate Homarr's monorepo architecture and reuse shared packages. Use when locating code, choosing a package boundary, understanding runtime services, tRPC, databases, widgets, integrations, cron jobs, Custom Widgets, Workshop, or checking @homarr/common before creating a utility.
|
||||
---
|
||||
|
||||
# Homarr Codebase Context
|
||||
|
||||
Orient from the current checkout before editing. Treat package manifests, exports, and source as authoritative when they differ from this snapshot.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Confirm the branch and inspect the nearest `package.json`, existing sibling modules, and package exports.
|
||||
2. Search `@homarr/common` before creating a general-purpose helper. Read [references/common.md](references/common.md) for every public entrypoint and export.
|
||||
3. Read [references/architecture.md](references/architecture.md) when the task crosses packages, runtimes, routing, data, widgets, integrations, cron jobs, Custom Widgets, or Workshop.
|
||||
4. Keep the change in the deepest package that owns the behavior. Reuse public entrypoints rather than deep-importing implementation files.
|
||||
5. Re-run targeted searches before documenting counts, consumers, or dependency edges; these change faster than the architectural seams.
|
||||
|
||||
## Current toolchain
|
||||
|
||||
- Use Node `24.18.0` for application runtimes and Bun `1.4.2` for package management, pinned in `mise.toml` and `package.json`.
|
||||
- Use Bun workspaces with Turborepo and a shared hoisted install. Workspace packages use the `@homarr/` scope and catalog-managed dependencies.
|
||||
- Use Next.js App Router, TypeScript, tRPC, Drizzle, Mantine v9, Tabler icons, Jotai, TanStack Query, next-intl, Vitest, and Playwright.
|
||||
- Use oxlint and oxfmt. Mantine is the application UI system; Tailwind is limited to the docs app.
|
||||
- `bun run dev` starts only `@homarr/nextjs`. Start a standalone runtime explicitly when the task needs it.
|
||||
|
||||
## High-value seams
|
||||
|
||||
- Server/RSC tRPC: `@homarr/api/server`
|
||||
- Client tRPC hooks: `@homarr/api/client`
|
||||
- Main router: `packages/api/src/root.ts`
|
||||
- MCP eager router: `packages/api/src/mcp.ts`
|
||||
- Database schemas: `packages/db/schema/{sqlite,postgresql}.ts`
|
||||
- Widget registry: `packages/widgets/src/registry.ts`
|
||||
- Widget loading manifest: `packages/widgets/src/manifest.ts`
|
||||
- Integration definitions: `packages/definitions/src/integration.ts`
|
||||
- Integration factory: `packages/integrations/src/base/creator.ts`
|
||||
- Cron jobs: `packages/cron-jobs/src/jobs/`
|
||||
- App routes: `apps/nextjs/src/app/[locale]/`
|
||||
|
||||
Invoke `documentation-sync` only when users need new or corrected information to set up, use, or troubleshoot Homarr; follow the Documentation Sync criteria in `AGENTS.md`. When exposing tRPC through MCP, invoke `mcp-integration`.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Homarr Codebase Context"
|
||||
short_description: "Navigate Homarr architecture and shared APIs"
|
||||
default_prompt: "Use $codebase-context to locate the right Homarr package and reuse existing shared APIs."
|
||||
@@ -1,110 +0,0 @@
|
||||
# Homarr architecture
|
||||
|
||||
## Contents
|
||||
|
||||
- [Applications and runtimes](#applications-and-runtimes)
|
||||
- [Package map](#package-map)
|
||||
- [Dependency direction](#dependency-direction)
|
||||
- [Core patterns](#core-patterns)
|
||||
- [Commands and conventions](#commands-and-conventions)
|
||||
|
||||
## Applications and runtimes
|
||||
|
||||
| Path | Role |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `apps/nextjs` | Main Next.js application and HTTP APIs on port 3000 |
|
||||
| `apps/docs` | Next.js and Fumadocs documentation site on port 3003 |
|
||||
| `apps/tasks` | Cron-job initialization and scheduling runtime; it does not expose the old port-3002 API |
|
||||
| `apps/websocket` | tRPC WebSocket subscriptions on port 3001 |
|
||||
| `apps/workshop` | Go/PocketBase Workshop service and provider integration |
|
||||
|
||||
`bun run dev` runs only `@homarr/nextjs`. Use `bun run --filter @homarr/tasks dev`, `bun run --filter @homarr/websocket dev`, or `bun run dev:docs` when a task needs those runtimes.
|
||||
|
||||
In production, `apps/nextjs/src/instrumentation-node.ts` starts the tasks and WebSocket runtimes inside the Bun process. Nginx listens on port 7575, proxies `/websockets` to port 3001, and sends other traffic to port 3000. Redis can run internally or externally.
|
||||
|
||||
## Package map
|
||||
|
||||
### Foundation and domain
|
||||
|
||||
- `core`: environment validation, database and Redis factories, logging, infrastructure.
|
||||
- `definitions`: domain enums, integration definitions, widget kinds, permissions.
|
||||
- `common`: shared utilities with environment-specific entrypoints. Search it before adding helpers.
|
||||
- `validation`: shared Zod schemas.
|
||||
- `server-settings`: typed server-setting keys and access.
|
||||
|
||||
### Data, identity, and transport
|
||||
|
||||
- `db`: Drizzle schemas, queries, migrations, and SQLite and PostgreSQL drivers.
|
||||
- `auth`: Auth.js/NextAuth configuration, sessions, providers, and API keys.
|
||||
- `api`: tRPC routers, contexts, MCP metadata extraction, OpenAPI, and client/server entrypoints.
|
||||
- `redis`: Redis clients, pub/sub channels, and caches.
|
||||
- `cron-jobs-core`, `cron-jobs`, `cron-job-status`: scheduling primitives, job implementations, and status transport.
|
||||
|
||||
### Backends and external systems
|
||||
|
||||
- `integrations`: typed clients for external applications.
|
||||
- `request-handler`: shared server request flows.
|
||||
- `docker`, `ping`, `image-proxy`, `icons`, `analytics`: bounded infrastructure capabilities.
|
||||
- `custom-widgets`: Custom JSX v2 schema, validation, runtime, and security contracts.
|
||||
- `workshop`: TypeScript Workshop client, backend contracts, and schemas used by Homarr.
|
||||
|
||||
### UI and composed features
|
||||
|
||||
- `ui`: shared Mantine components, theme, and hooks. Search here before creating a primitive.
|
||||
- `form`, `forms-collection`: Zod-backed form setup and reusable forms.
|
||||
- `modals`, `modals-collection`: modal primitives and feature modals.
|
||||
- `boards`: board state, edit mode, and cache coordination.
|
||||
- `settings`, `spotlight`, `notifications`, `onboarding`: cross-feature UI flows.
|
||||
- `widgets`: widget definitions, dynamic component loaders, and shared widget UI.
|
||||
- `translation`: next-intl configuration and locale catalogs.
|
||||
- `cli`: Bun operations CLI. `tools/homarr-dev` is the separate Go developer CLI.
|
||||
|
||||
## Dependency direction
|
||||
|
||||
Prefer these directions; verify the exact dependency in package manifests before changing a boundary.
|
||||
|
||||
1. Put infrastructure in `core` and stable domain names in `definitions`.
|
||||
2. Put reusable, dependency-light helpers in `common`; use its public environment-specific entrypoints.
|
||||
3. Build persistence in `db`, identity in `auth`, transport in `redis`, and external clients in `integrations`.
|
||||
4. Compose server behavior in `api`, `cron-jobs`, and request-handler packages.
|
||||
5. Build primitives in `ui`, `form`, and `modals`; compose product surfaces in widgets and collection packages.
|
||||
6. Keep app entrypoints in `apps/*` thin. Avoid moving reusable behavior into `apps/nextjs` when a package already owns it.
|
||||
|
||||
## Core patterns
|
||||
|
||||
### tRPC and MCP
|
||||
|
||||
`packages/api/src/root.ts` lazily composes the application router. Server components use `@homarr/api/server`; client components use `@homarr/api/client`. MCP extraction requires the eager router in `packages/api/src/mcp.ts`, so MCP-enabled subrouters must be imported and registered there directly. Use the `mcp-integration` skill for exposure and security rules.
|
||||
|
||||
### Databases
|
||||
|
||||
Drizzle schemas live in `packages/db/schema/`. `DB_DRIVER` selects `better-sqlite3` or `node-postgres`; MySQL and MariaDB are no longer runtime drivers in v2. Existing MySQL installations must use the documented converter before upgrading. Migrations live under `packages/db/migrations/sqlite/` and `packages/db/migrations/postgresql/`.
|
||||
|
||||
### Widgets
|
||||
|
||||
`packages/widgets/src/registry.ts` owns the explicit widget-module and component-loader registries. `packages/widgets/src/manifest.ts` caches those loaders and exposes definitions, components, and combined resources. Widget modules use `createWidgetDefinition` and typed option builders. Data commonly comes from tRPC queries or Redis-backed cron results. Use existing shared widget components before creating local variants.
|
||||
|
||||
### Integrations
|
||||
|
||||
`packages/definitions/src/integration.ts` owns `integrationDefs` and `IntegrationKind`. `packages/integrations/src/base/creator.ts` owns `createIntegrationAsync`; concrete integrations implement the shared base contract. Keep credentials and permission checks on server-owned paths.
|
||||
|
||||
### Cron jobs
|
||||
|
||||
Jobs live in `packages/cron-jobs/src/jobs/` and use `createCronJob`. `apps/tasks` initializes and starts the job group. Job results and status can flow through Redis and tRPC; the retired port-3002 management-API model is no longer part of the architecture.
|
||||
|
||||
### Routing and localization
|
||||
|
||||
User routes live below `apps/nextjs/src/app/[locale]/`; the locale segment does not add a URL prefix. Route groups organize home and board surfaces. Administrative routes live under `manage`, Custom Widget configuration has a token route, and Workshop has a management surface.
|
||||
|
||||
### Custom Widgets and Workshop
|
||||
|
||||
`packages/custom-widgets` owns the Custom JSX v2 contract. `packages/workshop` is the Homarr-side client/contract package; `apps/workshop` is the separate Go/PocketBase service. Use the `homarr-custom-widget` skill for authoring and security constraints.
|
||||
|
||||
## Commands and conventions
|
||||
|
||||
- Read current scripts from the root or package `package.json`; avoid caching commands that do not exist.
|
||||
- Use `bun run dev` for Next.js, `bun run dev:docs` for docs, and `bun run dev:cli -- <args>` for the Go developer CLI.
|
||||
- Use `bun run docker:dev:up` for the Redis development dependency.
|
||||
- Follow the Testing section in `AGENTS.md` for test creation and validation scope.
|
||||
- Use `~/*` only inside the Next.js app. Use public `@homarr/*` entrypoints across package boundaries.
|
||||
- Keep Mantine application styles separate from the Tailwind-based docs app.
|
||||
@@ -1,85 +0,0 @@
|
||||
# `@homarr/common` public API
|
||||
|
||||
## Contents
|
||||
|
||||
- [Reuse workflow](#reuse-workflow)
|
||||
- [Entrypoints](#entrypoints)
|
||||
- [Root exports](#root-exports)
|
||||
- [Types exports](#types-exports)
|
||||
- [Server exports](#server-exports)
|
||||
- [Client and environment exports](#client-and-environment-exports)
|
||||
|
||||
## Reuse workflow
|
||||
|
||||
Before writing a utility, search both the public name and the behavior:
|
||||
|
||||
```bash
|
||||
rg -n "export (const|function|class|type|interface)|export \*" packages/common
|
||||
rg -n "@homarr/common" apps packages
|
||||
```
|
||||
|
||||
Import only from an exported package entrypoint. Do not deep-import `@homarr/common/src/*`. Check `packages/common/package.json` and the source again when adding or renaming an export; this inventory describes the current repository tree.
|
||||
|
||||
## Entrypoints
|
||||
|
||||
| Import | Source | Environment |
|
||||
| ----------------------- | ---------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `@homarr/common` | `index.ts` -> `src/index.ts` | Shared root; includes React hooks and some Next/Mantine-aware helpers |
|
||||
| `@homarr/common/types` | `src/types.ts` | Type-only utilities |
|
||||
| `@homarr/common/server` | `src/server.ts` | Node/server-only security, encryption, request, error, and bounded-response helpers |
|
||||
| `@homarr/common/client` | `src/client.ts` | Client-facing Next server action export |
|
||||
| `@homarr/common/env` | `env.ts` | Validated package environment |
|
||||
|
||||
## Root exports
|
||||
|
||||
| Area | Public exports | Purpose |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| Objects | `objectKeys`, `objectEntries`, `isRecord`, `hashObjectBase64` | Typed key/entry iteration, record guards, and stable query-key hashing |
|
||||
| Strings | `capitalize`, `isNullOrWhitespace`, `bestMatch`, `normalizeImageName`, `getImageMatchRank` | Text normalization and matching |
|
||||
| Cookies | `parseCookies`, `setClientCookie` | Cookie parsing and browser cookie serialization |
|
||||
| Arrays | `splitToNChunks`, `splitToChunksWithNItems` | Split by number of groups or maximum group size |
|
||||
| Dates | `formatDuration`, `toValidDate`, `isDateWithin` | Duration formatting, safe date coercion, relative-window checks |
|
||||
| Timing | `Stopwatch` | Elapsed-time measurement and formatting |
|
||||
| React hooks | `useTimeAgo`, `useIntegrationConnected` | Relative time and integration freshness state |
|
||||
| URLs | `removeTrailingSlash`, `extractBaseUrlFromHeaders`, `getPortFromUrl`, `isAbsoluteUrl`, `SAFE_NEW_TAB_REL`, `getSafeApplicationUrl`, `getSafeAppHref` | URL normalization, safe navigation, ports, and new-tab policy |
|
||||
| Numbers and bytes | `formatNumber`, `formatBytes`, `formatBytesPair`, `formatByteRate`, `formatBitRate`, `defaultByteUnitSystem`, `metricToImperial` | Compact numbers, byte displays, byte/bit rates, and unit conversion |
|
||||
| Byte types | `ByteUnitSystem`, `FormatBytesOptions`, `FormatBitRateOptions` | Options for byte and bit-rate formatting |
|
||||
| Errors | `extractErrorMessage`, `FlattenError` | Unknown-error messages and flattenable domain errors |
|
||||
| Theme | `getMantineColor` | Resolve a Mantine color and shade |
|
||||
| Functions | `isFunction` | Unknown-value function guard |
|
||||
| Grid | `GridAlgorithmItem`, `generateResponsiveGridFor` | Responsive nested-section grid placement |
|
||||
| IDs | `createId` | CUID2 ID creation |
|
||||
|
||||
The root deliberately does not re-export `types`, `server`, `client`, or `env`; use their named entrypoints.
|
||||
|
||||
## Types exports
|
||||
|
||||
`@homarr/common/types` exports:
|
||||
|
||||
- `MaybePromise<T>`
|
||||
- `AtLeastOneOf<T>`
|
||||
- `Modify<T, R>`
|
||||
- `RemoveReadonly<T>`
|
||||
- `MaybeArray<T>`
|
||||
- `Inverse<T>`
|
||||
- `inferSearchParamsFromSchema<TSchema>`
|
||||
|
||||
## Server exports
|
||||
|
||||
`@homarr/common/server` exports:
|
||||
|
||||
- Security: `generateSecureRandomToken`.
|
||||
- Encryption: `encryptSecret`, `decryptSecret`, `decryptSecretWithKey`, `createKeyedFingerprint`, `verifyKeyedFingerprint`.
|
||||
- Requests: `userAgent`, `DeviceType`, `ipAddressFromHeaders`.
|
||||
- Bounded bodies: `readBoundedJsonResponseAsync`.
|
||||
- Parse errors: `ParseError`, `ParseErrorHandler`, `ZodParseErrorHandler`, `JsonParseErrorHandler`.
|
||||
- HTTP handlers: `HttpErrorHandler`, `FetchHttpErrorHandler`, `matchErrorCode`, `OFetchHttpErrorHandler`, `AxiosHttpErrorHandler`, `TsdavHttpErrorHandler`, `OctokitHttpErrorHandler`.
|
||||
- HTTP errors: `RequestError`, `ResponseError`, `requestErrorMap`.
|
||||
- HTTP error types: `AnyRequestError`, `AnyRequestErrorInput`, `RequestErrorInput`, `RequestErrorType`, `RequestErrorReason`, `AnyRequestErrorReason`, `RequestErrorCode`.
|
||||
|
||||
`NodeFetchHttpErrorHandler` exists as an implementation file but is not exported by the public server entrypoint. Add an intentional public export before using it across packages.
|
||||
|
||||
## Client and environment exports
|
||||
|
||||
- `@homarr/common/client` exports `revalidatePathActionAsync`, a Next server action for page revalidation.
|
||||
- `@homarr/common/env` exports `env` with validated `NODE_ENV`, `SECRET_ENCRYPTION_KEY`, and `NO_EXTERNAL_CONNECTION` values.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
name: documentation-sync
|
||||
description: Write Homarr docs only when users need information to set up, use, or troubleshoot the app. Skip routine, obvious, or self-explanatory changes.
|
||||
---
|
||||
|
||||
# Documentation Sync
|
||||
|
||||
Apply the audience and scope rules in `AGENTS.md` first. Documentation exists only for information users need to set up, use, or troubleshoot Homarr. A code, UI, or API change alone does not warrant documentation; if a user does not need to know it, do not add it.
|
||||
|
||||
For hidden capabilities such as the advanced widget feature, explain discovery, activation, and non-obvious behavior. For visible standard controls such as Delete, omit narration of what the label already conveys. Use the shortest explanation that preserves necessary technical detail; remove redundant prose in the passage being edited.
|
||||
|
||||
When an update is warranted, inspect adjacent pages for structure and types, not as a verbosity target. The mappings below locate needed information; they do not require a page for every feature or code change.
|
||||
|
||||
## Locate a needed update
|
||||
|
||||
These are destinations for warranted updates, not triggers to add documentation:
|
||||
|
||||
| Code change | Documentation target |
|
||||
| ----------------------------------- | -------------------------------------------------------------------- |
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` and `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` and `index.ts` |
|
||||
| Changed public or management API | `apps/docs/docs/management/api/index.mdx` |
|
||||
| New or changed environment variable | Relevant page under `apps/docs/docs/advanced/` or installation docs |
|
||||
| New or changed CLI command | `apps/docs/docs/advanced/command-line/` |
|
||||
| Authentication/provider change | Relevant SSO or authentication page under `apps/docs/docs/advanced/` |
|
||||
| UI or workflow change | Relevant getting-started, management, widget, or integration page |
|
||||
| Cron-job behavior | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Permission behavior | `apps/docs/docs/management/users.mdx` and the affected feature page |
|
||||
| Custom Widget or Workshop behavior | Relevant Custom Widget, Assistant, or Workshop documentation |
|
||||
|
||||
If no existing row fits, search by the user-visible term and update the page where a user would look for the behavior:
|
||||
|
||||
```bash
|
||||
rg -n "<term>" apps/docs/docs apps/docs/src
|
||||
```
|
||||
|
||||
## Follow existing typed patterns
|
||||
|
||||
For an integration:
|
||||
|
||||
- Add or update `apps/docs/docs/integrations/<slug>/index.ts` with the local `IntegrationDefinition` pattern.
|
||||
- Add or update `index.mdx` with the established `IntegrationHeader`, `IntegrationCapabilites`, and `IntegrationSecrets` components as applicable.
|
||||
- Reuse metadata from `@homarr/definitions` when the docs app already exposes it.
|
||||
|
||||
For a widget:
|
||||
|
||||
- Add or update `apps/docs/docs/widgets/<slug>/index.ts` with the local `WidgetDefinition` pattern.
|
||||
- Add or update `index.mdx` with the established `WidgetHeader`, `WidgetConfig`, and `AddingWidget` components as applicable.
|
||||
- Explain only non-obvious option semantics, integration prerequisites, permission constraints, or behavior that the interface does not make clear.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
1. Every passage adds information a user needs and cannot reasonably infer from the interface. Remove obvious, repetitive, or unnecessary prose.
|
||||
2. Keep names, defaults, paths, screenshots, links, and prerequisites consistent with code.
|
||||
3. Update every affected page and remove superseded guidance.
|
||||
4. Run the narrowest useful docs validation. Use `bun run turbo build --filter=@homarr/docs` when links, MDX, generated definitions, or navigation can fail; otherwise run the docs package formatter on touched files.
|
||||
5. Treat broken links and anchors as failures; run `bun run --filter @homarr/docs validate:links` after the static build.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Homarr Documentation Sync"
|
||||
short_description: "Document only what users need to know"
|
||||
default_prompt: "Use $documentation-sync only when users need new or corrected information to set up, use, or troubleshoot Homarr."
|
||||
@@ -1,58 +1,36 @@
|
||||
---
|
||||
name: homarr-custom-widget
|
||||
description: Author, validate, preview, test, install, or configure API-backed Homarr Custom JSX v2 widgets.
|
||||
description: Author safe Homarr Custom JSX v2 widgets from API documentation.
|
||||
---
|
||||
|
||||
# Homarr Custom Widget
|
||||
|
||||
Author requested widgets with release context; validate, test, persist, and return artifacts.
|
||||
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.
|
||||
|
||||
- Read primary API docs when missing/changed. Samples and successful previews are binding; load only needed schema, runtime,
|
||||
security, or component context.
|
||||
- Batch unknown component searches/details. `contextAlreadyLoaded` reuses context; `phaseComplete` advances. Stop only for
|
||||
genuine provider/model, lifecycle-service, or workbench-closure failure.
|
||||
- Community widgets use `customWidget_workshopSearch`, `customWidget_workshopGet`, and
|
||||
`customWidget_workshopInstall`; configure and persist.
|
||||
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.
|
||||
|
||||
Return one fenced `json` block with the complete definition; keep evidence prose outside it. The definition has keyed
|
||||
`sources`, `requests`, `template`, and optional `options`; actions are requests with `kind: "action"`.
|
||||
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.
|
||||
|
||||
- `sources.default` is required. HTTP has `baseUrl`, `networkScope`, and credential-free `auth`; localhost/loopback requires
|
||||
`networkScope: "loopback"`; never widen an explicit scope. Saved sources use `type: "integration"`/`integrationKind`;
|
||||
Homarr holds credentials.
|
||||
- Saved integrations: discover kinds/full-access entries with `integration_getKinds`/`integration_all`, bind `integrationId`
|
||||
before preview, omit URL/auth, and keep non-GET requests as actions.
|
||||
- Paths are slash-prefixed: strings use `{option:name}`/`{param:name}`; query/body uses `{"$option":"name"}`/`{"$param":"name"}`.
|
||||
Loads use `trigger: "load"`; manual helpers use `trigger: "manual"`.
|
||||
- Actions stay manual; preserve `confirmation`, `permission`, and `invalidates`; DELETE requires full permission/confirmation.
|
||||
`$param` is manual-only; `$option` may drive loads.
|
||||
- Load data.requestId/status.requestId with RefreshButton; status.requestId?.ok === false is error. Manual SubFetch never
|
||||
populates data/status; its child receives (result, metadata) and renders its fields.
|
||||
- Options have `label`, `control`, `default`; installation config is `options.name`, never `inputs`. Request-bound TextInput,
|
||||
Select, NumberInput, Pagination use literal `bind` + default and manual `SubFetch params` map `inputs.<name>` to `$param`.
|
||||
Dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove controls without an option/request/helper; guard arrays/nested with `??`;
|
||||
preserve documented timezone values; use UTC only when the contract says UTC.
|
||||
- Templates are one expression: no imports, hooks, refs, raw HTML/events, browser requests, eval, recursion, IIFEs,
|
||||
statement blocks, or arbitrary functions. Use registered component names; `Icon` may alias `TablerIcon`. Keep hierarchy,
|
||||
theme tokens, useful states, and narrow/wide layouts purposeful.
|
||||
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.
|
||||
|
||||
## Bounded lifecycle
|
||||
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`.
|
||||
|
||||
1. Build a credential-free definition from request, verified context, and sample. Preserve a migration's API path, method,
|
||||
body, options, and behavior; omit unknown requests rather than guessing.
|
||||
2. Send the coherent complete definition directly to `customWidget_previewCreate`; it validates both manifest and JSX.
|
||||
Use `customWidget_validateTemplate` only for isolated JSX diagnostics, never as a preview prerequisite. Use
|
||||
`customWidget_previewReviseTemplate` for JSX-only corrections after a preview exists. In the Assistant wrapper, multiline
|
||||
JSX uses `templateLines` and preview creation receives the complete definition.
|
||||
3. Test every returned query/simulated action once, batching independent queries. On a concrete schema/preview error, fix only
|
||||
that field and retry `customWidget_previewCreate` with the corrected definition; use `customWidget_previewReviseTemplate`
|
||||
only for JSX errors after a preview exists. Stop only for genuine provider/model, lifecycle-service, or
|
||||
workbench-closure failure.
|
||||
4. If `previewCreate` used `definitionId`, persist with `customWidget_updateFromPreview`; otherwise use
|
||||
`customWidget_createFromPreview`. Follow create `nextAction` once. Configure credentials in Homarr; never repeat plaintext
|
||||
secrets.
|
||||
`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.
|
||||
|
||||
## Delivery
|
||||
Use standard Mantine compound names and deliberate visual hierarchy, spacing, restrained semantic color, and responsive layouts. Avoid excessive nested cards.
|
||||
|
||||
Report actual lifecycle results. If unavailable, add one post-artifact `Unverified:` line naming missing validation, preview,
|
||||
renderer, or persistence. Never claim rendering/persistence from schema checks.
|
||||
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.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Homarr Custom Widget"
|
||||
short_description: "Build and deploy safe Custom JSX widgets"
|
||||
default_prompt: "Use $homarr-custom-widget to author, validate, preview, and configure a safe Custom JSX v2 widget."
|
||||
@@ -1,37 +1,16 @@
|
||||
# Runtime
|
||||
|
||||
Load-query templates read `data.requestId` and `status.requestId`, plus `options.name` and temporary `inputs.name`. Manual `SubFetch` results stay local to that instance and never populate `data`/`status`; render them in its child callback. Status is `{ loading, ok, status, statusText, error }`. Render load queries directly from `data` and `status` with `RefreshButton`; never wrap them in `SubFetch`.
|
||||
Templates read `data.requestId`, `status.requestId`, `options.name`, and temporary `inputs.name`. Status contains `loading`, `ok`, `status`, `statusText`, and `error`.
|
||||
|
||||
`bind` is temporary; manual request values go in `params` and map to `$param`:
|
||||
`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" />
|
||||
<NumberInput bind="page" label="Page" defaultValue={1} resetKey={inputs.search} min={1} />
|
||||
<SubFetch requestId="search" trigger="manual" params={{ query: inputs.search ?? "", page: inputs.page ?? 1 }}>
|
||||
{(result) => <Stack>{(result.results ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
|
||||
<SubFetch requestId="search" params={{ query: inputs.search }}>
|
||||
{(items) => <Stack>{(items ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
|
||||
</SubFetch>
|
||||
```
|
||||
|
||||
Manual queries require `trigger: "manual"` on request and `SubFetch`; otherwise they run automatically. `triggerContent` with `triggerAriaLabel` makes custom content the launcher. `SubFetch` owns loading/error/retry; its child receives success plus `{ ok, status, statusText, loading: false }`. Never author `onClick` or fetch callbacks.
|
||||
`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.
|
||||
|
||||
`SubFetch`, `ActionButton`, and `ToggleSwitch` need literal `requestId`; validation rejects missing/computed IDs.
|
||||
|
||||
Inside a successful manual result, `<RefreshButton requestId="search" label="Run again" />` reruns the same parameters.
|
||||
|
||||
When a manual SubFetch ID, params, or definition changes, Homarr hides the prior result and returns to the trigger; new params fetch only after triggering again.
|
||||
|
||||
Exact paths: load request ID `q` with raw preview body `B` -> `data.q === B`. `q=events` body`{"events":[...]}` -> `data.events.events`; do not flatten repeated keys. Manual `SubFetch` receives `B` as result -> `result.events`. Before persistence inspect core paths against preview; revise JSX and retest if mismatched
|
||||
|
||||
Format timestamps with safe static helpers; never use `new Date`. Never invent a formatter component. Use `Date.toLocaleString(value, "en-US", documentedTimezone)` and label the documented timezone; if no timezone is documented, preserve the source value or omit any timezone label; use UTC only when the response contract says UTC. Also available: `Date.toISOString`, `Date.toLocaleDateString`, and `Date.toLocaleTimeString`.
|
||||
|
||||
For compact numeric enums, index a literal label array with a fallback:
|
||||
|
||||
```jsx
|
||||
<Text>{["Unknown", "Pending", "Ready"][(item.status ?? 1) - 1] ?? "Unknown"}</Text>
|
||||
```
|
||||
|
||||
Request-bound controls use literal `bind` plus a default (`defaultChecked` for Switch/Checkbox); pass `inputs.<name>` through manual `SubFetch params` to matching `$param`. Options are installation config via `options.name`, never `inputs`; dependent pagination uses `defaultValue={1}`/`resetKey={inputs.query}`. Remove dead controls.
|
||||
|
||||
Callback parameters must not shadow the reserved roots `data`, `status`, `options`, or `inputs`. Use registered component names returned by discovery; `Icon` is an accepted alias for canonical `TablerIcon`. Never invent components such as `<IconFoo />`.
|
||||
|
||||
Use expression callbacks for supported collections and trusted slots. No callback blocks, IIFEs, authored recursion, or raw events. Regex is limited to safe string operations.
|
||||
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.
|
||||
@@ -13,41 +13,10 @@ interface HomarrCustomWidgetV2 {
|
||||
}
|
||||
```
|
||||
|
||||
Key `default` is the required source ID, not a source property. Fields: `name?`, `baseUrl`, `networkScope`, `auth?`; localhost/loopback URLs require `networkScope: "loopback"`; never widen explicit scope:
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"sources": {
|
||||
"default": {
|
||||
"name": "Service",
|
||||
"baseUrl": "http://service.local:5055/api/v1",
|
||||
"networkScope": "private",
|
||||
"auth": { "type": "apiKeyHeader", "name": "X-Api-Key" }
|
||||
}
|
||||
},
|
||||
"requests": {
|
||||
"summary": { "path": "/summary" },
|
||||
"search": { "trigger": "manual", "path": "/search", "query": { "q": { "$param": "query" } } },
|
||||
"create": {
|
||||
"kind": "action",
|
||||
"method": "POST",
|
||||
"path": "/items",
|
||||
"body": { "id": { "$param": "id" } },
|
||||
"confirmation": "Create this item?",
|
||||
"invalidates": ["search"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
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.
|
||||
|
||||
Saved sources use `{"type":"integration","integrationKind":"sonarr","integrationId":"saved-id"}`. Discover and bind a full-access entry before preview. Omit URL/auth; paths append to its URL, non-GET requests are actions, and exports omit `integrationId`.
|
||||
|
||||
Auth is `none`, `bearer`, `basic`, `apiKeyHeader`, or `apiKeyQuery`. Requests default to source `default`, query/GET/load, inherited auth, and view permission. Use `load` for initial/current display and `manual` only for explicit interactions or invocation params. Actions are manual/modify; DELETE requires full permission and confirmation.
|
||||
|
||||
JSON responses become their decoded value. Responses with `application/x-ndjson` become an array with one decoded object per non-empty line.
|
||||
|
||||
Use real public API URLs and clear self-hosted placeholders. Homarr collects URL, scope, and credentials outside the manifest.
|
||||
|
||||
Paths use `{option:name}`/`{param:name}`; query/body objects use `{"$option":"name"}`/`{"$param":"name"}`. `$param` is manual-only; `$option` may drive loads. Constants stay primitive.
|
||||
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`.
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
name: mcp-integration
|
||||
description: Expose Homarr tRPC procedures safely through MCP. Use when adding or changing tRPC procedures under packages/api, adding MCP metadata, registering an eager MCP router, shaping tool inputs and descriptions, or reviewing MCP authorization, sensitive data, and destructive actions.
|
||||
---
|
||||
|
||||
# MCP Integration
|
||||
|
||||
Expose a procedure only when an AI client should call it. Preserve the procedure's existing authorization and add MCP-specific safeguards for sensitive or destructive behavior.
|
||||
|
||||
## Assess exposure
|
||||
|
||||
Prefer:
|
||||
|
||||
- Data queries: list, search, get, health, stats, and summaries.
|
||||
- User-requested actions with explicit permission checks and bounded inputs.
|
||||
- Serializable results that do not reveal credentials or internal-only state.
|
||||
|
||||
Skip subscriptions, session/onboarding internals, file streams, blobs, credential values, and procedures whose authorization or audit behavior is unclear. Ask for a security review when a mutation is destructive, tenant boundaries are ambiguous, or returned data is sensitive.
|
||||
|
||||
## Add metadata
|
||||
|
||||
Place `.meta()` before `.input()` and `.query()` or `.mutation()`:
|
||||
|
||||
```typescript
|
||||
getAll: protectedProcedure
|
||||
.meta({
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description: "List resources the current user can access, including stable IDs used by resource_get",
|
||||
},
|
||||
})
|
||||
.input(z.object({ limit: z.number().int().min(1).max(100).default(50) }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
// Preserve normal procedure authorization.
|
||||
});
|
||||
```
|
||||
|
||||
No-input procedures can omit `.input()`; the MCP extractor accepts an empty object for them.
|
||||
|
||||
For mutations, state the effect and required permission in the description:
|
||||
|
||||
```typescript
|
||||
remove: permissionRequiredProcedure
|
||||
.meta({
|
||||
mcp: {
|
||||
enabled: true,
|
||||
description: "Delete one resource by ID. Requires full access; get the ID from resource_all",
|
||||
},
|
||||
})
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// Perform the permission-scoped action.
|
||||
});
|
||||
```
|
||||
|
||||
## Write discoverable descriptions
|
||||
|
||||
Include every item that applies:
|
||||
|
||||
- What the tool returns or changes.
|
||||
- Supported integrations or services.
|
||||
- Required IDs and the tool that returns them.
|
||||
- Permission requirements and the meaning of permission fields.
|
||||
- Important bounds, confirmation semantics, or irreversible effects.
|
||||
|
||||
Use precise domain language. A description such as `Get calendar events` is insufficient because it omits supported services, required IDs, and result scope.
|
||||
|
||||
## Shape inputs for clients
|
||||
|
||||
- Use a top-level `z.object({...})` for parameterized tools.
|
||||
- Bound strings, arrays, numeric ranges, pagination, and payload sizes.
|
||||
- Use defaults where a safe, unsurprising value exists.
|
||||
- Prefer explicit fields over a top-level union or discriminated union.
|
||||
- Keep identifiers stable and describe how to obtain them.
|
||||
- Keep secrets out of inputs unless the procedure is explicitly a credential-configuration flow with server-owned encryption and non-return guarantees.
|
||||
|
||||
## Register the eager router
|
||||
|
||||
The application router in `packages/api/src/root.ts` is lazy. MCP tool extraction is synchronous, so add an eager import and registration in `packages/api/src/mcp.ts`:
|
||||
|
||||
```typescript
|
||||
import { resourceRouter } from "./router/resource";
|
||||
|
||||
export const mcpRouter = createTRPCRouter({
|
||||
resource: resourceRouter,
|
||||
});
|
||||
```
|
||||
|
||||
Register the smallest router that owns the enabled procedures. If a parent router uses `lazy()`, import the needed subrouter directly. Metadata on a procedure is not enough; a procedure absent from `mcpRouter` is not exposed.
|
||||
|
||||
## Validate the tool surface
|
||||
|
||||
1. Confirm the procedure still enforces its ordinary auth and permission boundary.
|
||||
2. Confirm `packages/api/src/mcp.ts` eagerly includes the procedure.
|
||||
3. Confirm the generated tool name, description, and JSON schema are unambiguous.
|
||||
4. When validating tool-list or extraction changes, use the focused API MCP spec: `bun run test packages/api/src/test/mcp.spec.ts`.
|
||||
5. When validating protocol or transport changes, use the focused route spec: `bun run test apps/nextjs/src/app/api/mcp/[transport]/route.spec.ts`.
|
||||
6. If manually probing `/api/mcp/<transport>`, use a scoped API key and avoid printing credentials or secret-bearing results.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Homarr MCP Integration"
|
||||
short_description: "Expose tRPC procedures safely through MCP"
|
||||
default_prompt: "Use $mcp-integration to expose this Homarr tRPC procedure as a safe, discoverable MCP tool."
|
||||
@@ -1 +0,0 @@
|
||||
../.agents/skills
|
||||
@@ -0,0 +1,204 @@
|
||||
---
|
||||
description: Comprehensive Homarr codebase architecture and conventions reference
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Homarr Codebase Context
|
||||
|
||||
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Monorepo**: pnpm workspaces + Turborepo
|
||||
- **Framework**: Next.js (App Router, `output: "standalone"`) — T3 Stack
|
||||
- **Language**: TypeScript throughout
|
||||
- **ORM**: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
|
||||
- **API**: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via `trpc-to-openapi`
|
||||
- **Auth**: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
|
||||
- **UI**: Mantine v9 (not Tailwind) + Tabler icons
|
||||
- **State**: Jotai (atoms), TanStack Query (server state via tRPC)
|
||||
- **Realtime**: WebSocket server (`ws`) for tRPC subscriptions, backed by Redis pub/sub
|
||||
- **Cron**: `node-cron` in a standalone Fastify service (`apps/tasks`)
|
||||
- **i18n**: next-intl with `[locale]` dynamic segment (prefix mode: `"never"`, locale from cookie)
|
||||
- **Testing**: Vitest + jsdom, Playwright for E2E
|
||||
- **Lint/Format**: oxlint + oxfmt (not ESLint/Prettier)
|
||||
- **Docs**: Docusaurus 3 in `apps/docs/` (`@homarr/docs`)
|
||||
- **Package manager**: pnpm 10.34.1, Node >= 24.16.0
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
homarr/
|
||||
├── apps/
|
||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
|
||||
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
├── packages/
|
||||
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
|
||||
│ ├── auth/ # NextAuth config, providers, session, API keys
|
||||
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
|
||||
│ ├── core/ # Env validation, DB/Redis driver factories, logging
|
||||
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
|
||||
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
|
||||
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
|
||||
│ ├── redis/ # Redis pub/sub channels, caching abstractions
|
||||
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
|
||||
│ ├── ui/ # Shared Mantine components, theme, hooks
|
||||
│ ├── validation/ # Shared zod schemas for API/forms
|
||||
│ ├── common/ # Shared utilities, IDs, errors
|
||||
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
|
||||
│ ├── cron-jobs-core/ # Cron scheduling primitives
|
||||
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
|
||||
│ ├── cron-job-status/ # Cron status via Redis
|
||||
│ ├── boards/ # Board context, edit mode, cache updater
|
||||
│ ├── modals/ # Modal primitives on Mantine
|
||||
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
|
||||
│ ├── form/ # useZodForm (Mantine + zod resolver)
|
||||
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
|
||||
│ ├── spotlight/ # Command palette / search with multiple modes
|
||||
│ ├── request-handler/ # Server request handlers (feeds, integrations)
|
||||
│ ├── notifications/ # Mantine notifications wrapper
|
||||
│ ├── docker/ # Dockerode-based Docker access
|
||||
│ ├── icons/ # Icon DB/repo integration
|
||||
│ ├── image-proxy/ # Image proxy + caching
|
||||
│ ├── ping/ # Reachability / ping utilities
|
||||
│ ├── analytics/ # Server-side analytics (Umami)
|
||||
│ ├── server-settings/ # Server setting keys/types
|
||||
│ ├── settings/ # User-facing settings UI context
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
│ └── github/ # CI setup action
|
||||
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
|
||||
├── e2e/ # E2E test specs
|
||||
└── Dockerfile # Multi-stage production build
|
||||
```
|
||||
|
||||
## 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 |
|
||||
|
||||
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
|
||||
|
||||
## Package Dependency Layers
|
||||
|
||||
1. **Foundation**: `core` (env, DB drivers, Redis client, logging) → no `@homarr` deps
|
||||
2. **Domain**: `definitions` → `common` → `core`
|
||||
3. **Data**: `db` → `core`, `definitions`, `common`
|
||||
4. **Auth**: `auth` → `db`, `definitions`, `core`, `validation`
|
||||
5. **Cross-cutting**: `translation`, `validation`, `redis`, `server-settings`
|
||||
6. **Feature backends**: `integrations`, `request-handler`, `cron-jobs`, `docker`, `ping`
|
||||
7. **API surface**: `api` → pulls in auth, db, redis, integrations, etc.
|
||||
8. **UI stack**: `ui`, `modals`, `form`, `notifications`, `boards`, `spotlight`
|
||||
9. **Composed features**: `widgets`, `modals-collection`, `forms-collection`
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### tRPC
|
||||
|
||||
- **Server caller** (RSC): `import { api } from "@homarr/api/server"` → cached caller with auth context
|
||||
- **Client hooks**: `import { clientApi } from "@homarr/api/client"` → React Query hooks
|
||||
- **Router structure**: `packages/api/src/root.ts` defines top-level namespaces (`user`, `board`, `widget`, `integration`, `docker`, `kubernetes`, etc.)
|
||||
- **Procedures**: `publicProcedure`, `protectedProcedure`, `permissionRequiredProcedure`, `onboardingProcedure`
|
||||
- **WebSocket subscriptions**: Client uses `wsLink` when `type === "subscription"`, connects to port 3001
|
||||
|
||||
### 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`
|
||||
|
||||
Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-postgres`). Migrations are per-engine in `packages/db/migrations/{sqlite,mysql,postgresql}/`.
|
||||
|
||||
### 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`
|
||||
- Fetches data via tRPC client hooks (backed by cron job caches)
|
||||
|
||||
### Integration System
|
||||
|
||||
Integrations are defined in `packages/definitions/src/integration.ts` (`integrationDefs`) and instantiated via `createIntegrationAsync` in `packages/integrations/src/base/creator.ts`. Each extends abstract `Integration` class with `testConnectionAsync` and typed methods.
|
||||
|
||||
### 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
|
||||
- Job management (start/stop/trigger) via `cronJobApi` HTTP client to port 3002
|
||||
|
||||
### Modals
|
||||
|
||||
Created with `createModal(component).withOptions({ defaultTitle })` from `@homarr/modals`. Opened via `useModalAction(SomeModal)`. Feature modals live in `packages/modals-collection/`.
|
||||
|
||||
### Forms
|
||||
|
||||
`useZodForm` from `@homarr/form` wraps Mantine `useForm` with zod validation. Shared form UIs in `packages/forms-collection/`.
|
||||
|
||||
### Routing (App Router)
|
||||
|
||||
- `apps/nextjs/src/app/[locale]/` — all UI routes under locale segment
|
||||
- Route groups: `(home)`, `(content)`, `(board)` — don't affect URLs
|
||||
- Key routes: `boards/[name]`, `manage/...` (admin), `auth/login`, `init` (onboarding), `widgets/[kind]`
|
||||
- Locale driven by cookie (no URL prefix)
|
||||
|
||||
### Auth Providers
|
||||
|
||||
Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `oidc`. Database sessions with httpOnly cookies. API key auth for programmatic access.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key env vars (from `.env.example`):
|
||||
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
|
||||
- `DB_URL`: Database connection string
|
||||
- `AUTH_SECRET`: NextAuth secret
|
||||
- `SECRET_ENCRYPTION_KEY`: AES-256-CBC for integration secrets
|
||||
- `AUTH_PROVIDERS`: Comma-separated (`credentials`, `ldap`, `oidc`)
|
||||
- `CRON_JOB_API_KEY`: Shared key between Next.js and tasks service
|
||||
- `REDIS_*`: Redis connection (host, port, password)
|
||||
- `ENABLE_KUBERNETES`: Optional Kubernetes integration
|
||||
|
||||
## Development Commands
|
||||
|
||||
- `pnpm dev` — runs all three services in parallel via Turborepo
|
||||
- `pnpm docker:dev` — starts Redis + MySQL + PostgreSQL containers
|
||||
- `pnpm db:push` — push schema to SQLite (dev)
|
||||
- `pnpm db:studio` — Drizzle Studio
|
||||
- `pnpm test` — Vitest unit tests
|
||||
- `pnpm test:e2e` — Playwright E2E
|
||||
- `pnpm lint` / `pnpm format` — oxlint + oxfmt
|
||||
- `pnpm dev:docs` — Docusaurus docs site (port 3003)
|
||||
- `pnpm typecheck` — tsc --noEmit across all packages
|
||||
|
||||
## Conventions
|
||||
|
||||
- Workspace packages use `@homarr/` scope
|
||||
- All versions managed via pnpm `catalog:` in workspace
|
||||
- Path alias `~/*` → `./src/*` in Next.js app
|
||||
- Mantine for all UI (no Tailwind) — primary color is red, `autoContrast: true`
|
||||
- Icons from `@tabler/icons-react`
|
||||
- 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,612 @@
|
||||
# Migrating Tables from mantine-react-table to mantine-datatable
|
||||
|
||||
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
|
||||
|
||||
## Migration Candidates
|
||||
|
||||
Current `mantine-react-table` consumers that could be migrated:
|
||||
|
||||
**Widgets (in `packages/widgets/`):**
|
||||
- `docker/component.tsx` — Docker container table
|
||||
- `media-server/component.tsx` — Media server sessions
|
||||
|
||||
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
|
||||
- `tools/docker/docker-table.tsx`
|
||||
- `tools/tasks/_components/tasks-table.tsx`
|
||||
- `tools/api/components/api-keys.tsx`
|
||||
- `users/_components/user-list.tsx`
|
||||
- `users/invites/_components/invite-list.tsx`
|
||||
- `tools/kubernetes/` — 8 Kubernetes resource tables
|
||||
|
||||
**Modals:**
|
||||
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
|
||||
|
||||
**Shared hook to remove once all migrated:**
|
||||
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Migration: mantine-react-table → mantine-datatable
|
||||
|
||||
### Import changes
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
|
||||
// AFTER
|
||||
import { DataTable, useDataTableColumns } from "mantine-datatable";
|
||||
import type { DataTableColumn, DataTableSortStatus } from "mantine-datatable";
|
||||
```
|
||||
|
||||
### Column definition shape
|
||||
|
||||
```typescript
|
||||
// BEFORE (MRT)
|
||||
const columns: MRT_ColumnDef<MyRow>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Name",
|
||||
size: 200,
|
||||
enableSorting: true,
|
||||
Cell: ({ row }) => <Text>{row.original.name}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
// AFTER (mantine-datatable)
|
||||
const columns: DataTableColumn<MyRow>[] = [
|
||||
{
|
||||
accessor: "name", // not accessorKey
|
||||
title: "Name", // not header
|
||||
width: 200, // not size
|
||||
sortable: true, // not enableSorting
|
||||
render: (record) => <Text>{record.name}</Text>, // not Cell
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
Key differences:
|
||||
- `accessorKey` → `accessor`
|
||||
- `header` → `title`
|
||||
- `size` → `width`
|
||||
- `Cell: ({ row }) =>` → `render: (record) =>`
|
||||
- `enableSorting` → `sortable`
|
||||
- `enableColumnActions` → does not exist (no column action menus)
|
||||
- `mantineTableBodyCellProps` → `cellsStyle` in `defaultColumnProps`
|
||||
|
||||
### Table component
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
const table = useTranslatedMantineReactTable({ columns, data, ... });
|
||||
return <MantineReactTable table={table} />;
|
||||
|
||||
// AFTER
|
||||
return (
|
||||
<DataTable
|
||||
records={data}
|
||||
columns={columns}
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
...
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Sorting is manual
|
||||
|
||||
mantine-datatable does NOT sort for you. You must sort the data yourself:
|
||||
|
||||
```typescript
|
||||
const [sortStatus, setSortStatus] = useState<DataTableSortStatus<MyRow>>({
|
||||
columnAccessor: "name",
|
||||
direction: "asc",
|
||||
});
|
||||
|
||||
const sortedData = useMemo(() => {
|
||||
const mult = sortStatus.direction === "desc" ? -1 : 1;
|
||||
return [...data].toSorted((a, b) => {
|
||||
const aVal = a[sortStatus.columnAccessor];
|
||||
const bVal = b[sortStatus.columnAccessor];
|
||||
if (typeof aVal === "string") return aVal.localeCompare(bVal as string) * mult;
|
||||
return ((aVal as number) - (bVal as number)) * mult;
|
||||
});
|
||||
}, [data, sortStatus]);
|
||||
|
||||
// Pass sortedData to records, not data
|
||||
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Making Tables Transparent (Widget Context)
|
||||
|
||||
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
|
||||
|
||||
Create a CSS file alongside the component:
|
||||
|
||||
```css
|
||||
/* styles.css */
|
||||
.my-table,
|
||||
.my-table .mantine-datatable-table,
|
||||
.my-table .mantine-datatable-table thead,
|
||||
.my-table .mantine-datatable-table tbody,
|
||||
.my-table .mantine-datatable-table tfoot,
|
||||
.my-table th,
|
||||
.my-table td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.my-table .mantine-datatable-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Hover: translucent instead of opaque */
|
||||
.my-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||
}
|
||||
|
||||
/* Headers: frosted glass effect */
|
||||
.my-table th {
|
||||
white-space: nowrap;
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Cell overflow */
|
||||
.my-table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Hide sort icon until hover */
|
||||
.my-table .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.my-table th:hover .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Row expansion area */
|
||||
.my-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
|
||||
}
|
||||
```
|
||||
|
||||
Import it: `import "./styles.css";` and apply via `className="my-table"`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsive Column Visibility
|
||||
|
||||
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
|
||||
|
||||
```typescript
|
||||
interface SizeConfig {
|
||||
fontSize: "xs" | "sm";
|
||||
iconSize: number;
|
||||
cellPadding: number;
|
||||
showSpeedColumns: boolean;
|
||||
showTimeColumn: boolean;
|
||||
showStateColumn: boolean;
|
||||
}
|
||||
|
||||
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
|
||||
{ maxWidth: 300, config: { fontSize: "xs", iconSize: 12, cellPadding: 2, showSpeedColumns: false, showTimeColumn: false, showStateColumn: false } },
|
||||
{ maxWidth: 500, config: { fontSize: "xs", iconSize: 14, cellPadding: 4, showSpeedColumns: false, showTimeColumn: true, showStateColumn: false } },
|
||||
];
|
||||
|
||||
const DEFAULT_SIZE_CONFIG: SizeConfig = { fontSize: "sm", iconSize: 14, cellPadding: 4, showSpeedColumns: true, showTimeColumn: true, showStateColumn: true };
|
||||
|
||||
function getSizeConfig(width: number): SizeConfig {
|
||||
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
|
||||
if (width < maxWidth) return config;
|
||||
}
|
||||
return DEFAULT_SIZE_CONFIG;
|
||||
}
|
||||
|
||||
// Usage: memoize it
|
||||
const size = useMemo(() => getSizeConfig(width), [width]);
|
||||
```
|
||||
|
||||
Then use a visibility check lookup table for columns:
|
||||
|
||||
```typescript
|
||||
const columnVisibilityChecks: Partial<Record<string, (ctx: ColumnContext) => boolean>> = {
|
||||
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
|
||||
downSpeed: (ctx) => ctx.size.showSpeedColumns,
|
||||
time: (ctx) => ctx.size.showTimeColumn,
|
||||
state: (ctx) => ctx.size.showStateColumn,
|
||||
};
|
||||
|
||||
function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
|
||||
if (!ctx.optionsColumnSet.has(accessor)) return false;
|
||||
const check = columnVisibilityChecks[accessor];
|
||||
if (check && !check(ctx)) return false;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Column Persistence (Drag/Resize → Server)
|
||||
|
||||
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
|
||||
|
||||
### Hidden widget options
|
||||
|
||||
In `index.ts`, add hidden text options:
|
||||
|
||||
```typescript
|
||||
columnOrder: factory.text({ defaultValue: "" }),
|
||||
columnWidths: factory.text({ defaultValue: "" }),
|
||||
```
|
||||
|
||||
With visibility override:
|
||||
|
||||
```typescript
|
||||
{
|
||||
columnOrder: { shouldHide: () => true },
|
||||
columnWidths: { shouldHide: () => true },
|
||||
}
|
||||
```
|
||||
|
||||
### Translation keys required
|
||||
|
||||
Even hidden options need translation keys or the translation test fails:
|
||||
|
||||
```json
|
||||
"widget.mywidget.option.columnOrder.label": "Column order",
|
||||
"widget.mywidget.option.columnWidths.label": "Column widths"
|
||||
```
|
||||
|
||||
### Dynamic storeKey
|
||||
|
||||
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
|
||||
|
||||
```typescript
|
||||
const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted().join(",")}`;
|
||||
```
|
||||
|
||||
### Hydration from server → localStorage
|
||||
|
||||
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
|
||||
|
||||
```typescript
|
||||
const { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } =
|
||||
useDataTableColumns<MyRow>({ key: storeKey, columns });
|
||||
|
||||
const lastStoreKey = useRef(storeKey);
|
||||
const hydrated = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastStoreKey.current !== storeKey) {
|
||||
lastStoreKey.current = storeKey;
|
||||
hydrated.current = false;
|
||||
}
|
||||
if (hydrated.current) return;
|
||||
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
|
||||
if (Object.keys(savedWidths).length > 0) {
|
||||
setMultipleColumnWidths(
|
||||
Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w })),
|
||||
);
|
||||
}
|
||||
requestAnimationFrame(() => { hydrated.current = true; });
|
||||
}, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]);
|
||||
```
|
||||
|
||||
### Persist localStorage → server
|
||||
|
||||
Watch for changes and write back:
|
||||
|
||||
```typescript
|
||||
const prevOrder = useRef(columnsOrder);
|
||||
const prevWidths = useRef(columnsWidth);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated.current) return;
|
||||
const orderChanged = JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
|
||||
const widthsChanged = JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
|
||||
prevOrder.current = columnsOrder;
|
||||
prevWidths.current = columnsWidth;
|
||||
|
||||
if (!orderChanged && !widthsChanged) return;
|
||||
|
||||
if (orderChanged) persistOption({ columnOrder: JSON.stringify(columnsOrder) });
|
||||
if (widthsChanged) {
|
||||
const widthMap: Record<string, number> = {};
|
||||
for (const entry of columnsWidth) {
|
||||
const key = Object.keys(entry)[0];
|
||||
if (!key) continue;
|
||||
const w = entry[key as keyof typeof entry];
|
||||
if (typeof w === "number") widthMap[key] = w;
|
||||
else if (typeof w === "string" && w.endsWith("px")) widthMap[key] = parseInt(w, 10);
|
||||
}
|
||||
persistOption({ columnWidths: JSON.stringify(widthMap) });
|
||||
}
|
||||
}, [columnsOrder, columnsWidth, persistOption]);
|
||||
```
|
||||
|
||||
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
|
||||
|
||||
---
|
||||
|
||||
## 5. Context Menu (Right-Click)
|
||||
|
||||
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
|
||||
|
||||
```typescript
|
||||
interface ContextMenuState {
|
||||
x: number;
|
||||
y: number;
|
||||
item: MyRow;
|
||||
}
|
||||
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
({ record, event }: { record: MyRow; event: React.MouseEvent }) => {
|
||||
event.preventDefault();
|
||||
setContextMenu({ x: event.clientX, y: event.clientY, item: record });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// On DataTable:
|
||||
<DataTable onRowContextMenu={handleContextMenu} />
|
||||
|
||||
// Render:
|
||||
{contextMenu && <RowContextMenu state={contextMenu} onClose={closeContextMenu} />}
|
||||
```
|
||||
|
||||
The menu component uses `Portal` for correct z-index:
|
||||
|
||||
```tsx
|
||||
function RowContextMenu({ state, onClose }: { state: ContextMenuState; onClose: () => void }) {
|
||||
return (
|
||||
<Portal>
|
||||
<Menu opened onClose={onClose} closeOnItemClick={false} position="right-start" offset={0}>
|
||||
<Menu.Target>
|
||||
<Box style={{ position: "fixed", left: state.x, top: state.y, width: 0, height: 0 }} />
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={() => { /* action */ onClose(); }}>Action</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Row Expansion
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
rowExpansion={{
|
||||
trigger: "click",
|
||||
allowMultiple: true,
|
||||
collapseProps: {
|
||||
transitionDuration: 200,
|
||||
animateOpacity: true,
|
||||
transitionTimingFunction: "ease-out",
|
||||
},
|
||||
content: ({ record, collapse }) => <ExpandedRow item={record} collapse={collapse} />,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
|
||||
|
||||
---
|
||||
|
||||
## 7. Common Pitfalls & Fixes
|
||||
|
||||
### Status indicator dots
|
||||
|
||||
Don't use `<Badge variant="dot" />` without children — it renders a pill with empty space. Use a plain `Box`:
|
||||
|
||||
```tsx
|
||||
<Box
|
||||
w={8}
|
||||
h={8}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
backgroundColor: `var(--mantine-color-${color}-filled)`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Tooltip overflow on long text columns
|
||||
|
||||
When a column has `ellipsis: true`, the tooltip target element can still extend beyond the cell bounds if inner elements don't clip. Fix:
|
||||
|
||||
```tsx
|
||||
<Tooltip position="bottom-start" ...>
|
||||
<Group style={{ overflow: "hidden" }}>
|
||||
...
|
||||
</Group>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
Use `position="bottom-start"` instead of `position="right"` so the tooltip never escapes the widget horizontally.
|
||||
|
||||
### Dark mode tooltips
|
||||
|
||||
Mantine tooltips default to the theme's tooltip color which may be white in light mode and stay white in dark mode. Always set `color="dark"` on informational tooltips inside widgets.
|
||||
|
||||
### Division by zero
|
||||
|
||||
Guard all ratio calculations:
|
||||
|
||||
```typescript
|
||||
if (item.size > 0) ratio = item.sent / item.size;
|
||||
if (totalDown > 0) ratio = totalUp / totalDown;
|
||||
```
|
||||
|
||||
### Composite idAccessor
|
||||
|
||||
When rows can come from multiple integrations, IDs may collide. Use a composite key:
|
||||
|
||||
```typescript
|
||||
idAccessor={(record) => `${record.integration.id}:${record.id}`}
|
||||
```
|
||||
|
||||
### JSON option parsing
|
||||
|
||||
Always wrap in try/catch:
|
||||
|
||||
```typescript
|
||||
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
|
||||
if (!value) return fallback;
|
||||
try { return JSON.parse(value) as T; }
|
||||
catch { return fallback; }
|
||||
}
|
||||
```
|
||||
|
||||
### Clipboard API
|
||||
|
||||
`navigator.clipboard.writeText()` can throw in non-HTTPS contexts. Always catch:
|
||||
|
||||
```typescript
|
||||
void navigator.clipboard.writeText(text).catch(() => {});
|
||||
```
|
||||
|
||||
### Sync sortStatus with widget options
|
||||
|
||||
If the widget has configurable default sort, sync it:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
setSortStatus({ columnAccessor: options.defaultSort, direction: defaultSortDirection });
|
||||
}, [options.defaultSort, defaultSortDirection]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Accessibility
|
||||
|
||||
- All `ActionIcon` components need `aria-label`
|
||||
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
|
||||
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
|
||||
|
||||
---
|
||||
|
||||
## 9. Error Handling
|
||||
|
||||
- Every mutation needs an `onError` callback with `showErrorNotification`
|
||||
- The `saveItemOptions` mutation (column persistence) also needs `onError`
|
||||
- Check `isError` on the main query and show a fallback UI
|
||||
- Use a shared `mutationOptions` object to avoid repeating `onSettled`/`onError`:
|
||||
|
||||
```typescript
|
||||
const mutationOptions = {
|
||||
onSettled: () => void utils.myQuery.invalidate(),
|
||||
onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }),
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. i18n
|
||||
|
||||
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
|
||||
- Column titles: `t("items.{accessor}.columnTitle")`
|
||||
- Detail labels: `t("items.{accessor}.detailsTitle")`
|
||||
- States: `t("states.{state}")`
|
||||
- Actions: `t("actions.{action}")`
|
||||
- Errors: `t("errors.{errorKey}")`
|
||||
- Hidden options still need `.label` translation keys
|
||||
|
||||
---
|
||||
|
||||
## 11. Edit Mode
|
||||
|
||||
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
|
||||
|
||||
```typescript
|
||||
let tablePointerEvents: React.CSSProperties["pointerEvents"];
|
||||
if (isEditMode) tablePointerEvents = "none";
|
||||
|
||||
let rowContextMenuHandler = handleContextMenu;
|
||||
if (isEditMode) rowContextMenuHandler = undefined;
|
||||
|
||||
<DataTable style={{ pointerEvents: tablePointerEvents }} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. DataTable Props Reference (Commonly Used)
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
records={sortedData}
|
||||
columns={effectiveColumns}
|
||||
storeColumnsKey={storeKey}
|
||||
withTableBorder={false}
|
||||
borderRadius={0}
|
||||
highlightOnHover
|
||||
striped="odd"
|
||||
stripedColor={{ dark: "dark.7", light: "gray.0" }}
|
||||
highlightOnHoverColor={{ dark: "dark.5", light: "gray.1" }}
|
||||
verticalAlign="center"
|
||||
fetching={isFetching && data.length === 0}
|
||||
loaderBackgroundBlur={2}
|
||||
fz={size.fontSize}
|
||||
textSelectionDisabled
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
noRecordsText={t("errors.noItems")}
|
||||
idAccessor={(record) => record.uniqueKey}
|
||||
height="100%"
|
||||
className="my-table"
|
||||
defaultColumnProps={{
|
||||
noWrap: true,
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
cellsStyle: () => ({ padding: `${size.cellPadding}px 8px` }),
|
||||
}}
|
||||
scrollAreaProps={{ type: "auto", scrollbarSize: 6 }}
|
||||
rowBackgroundColor={(record) => conditionalBgMap[record.state]}
|
||||
onRowContextMenu={handleContextMenu}
|
||||
rowExpansion={{ ... }}
|
||||
onScroll={() => { if (contextMenu) closeContextMenu(); }}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Layout Stability
|
||||
|
||||
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
|
||||
- Give non-name columns explicit `width` values so only the name column flexes
|
||||
- Use `ellipsis: true` on the name column — it handles `text-overflow: ellipsis` internally
|
||||
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
|
||||
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
|
||||
|
||||
---
|
||||
|
||||
## 14. Checklist for Each Migration
|
||||
|
||||
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
|
||||
2. Convert column definitions (`accessorKey` → `accessor`, `header` → `title`, `Cell` → `render`, etc.)
|
||||
3. Implement manual sorting with `useState` + `useMemo`
|
||||
4. Add CSS file for transparent backgrounds (if widget context)
|
||||
5. Add responsive column visibility (if widget context)
|
||||
6. Add hidden options + hydration logic (if column persistence needed)
|
||||
7. Add translation keys (including hidden option labels)
|
||||
8. Add context menu if row actions exist
|
||||
9. Add row expansion if detail view exists
|
||||
10. Add error handling on all mutations
|
||||
11. Add accessibility attributes
|
||||
12. Test at multiple widget widths
|
||||
13. Update docs in `apps/docs/docs/widgets/` if applicable
|
||||
14. Run `pnpm turbo typecheck`
|
||||
15. Run translation spec — catches missing keys
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
description: How to expose tRPC procedures as MCP tools for AI clients
|
||||
globs: packages/api/src/router/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
## Adding MCP to a procedure
|
||||
|
||||
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
|
||||
|
||||
```typescript
|
||||
// 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(50) }))
|
||||
.query(async ({ ctx, input }) => { ... })
|
||||
|
||||
// Write tool (mutation)
|
||||
myAction: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Delete an item by ID. Requires interact permission on the integration" } })
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => { ... })
|
||||
|
||||
// No-input procedure (z.void) — works fine, no .input() needed
|
||||
getAll: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "List all resources" } })
|
||||
.query(async ({ ctx }) => { ... })
|
||||
```
|
||||
|
||||
The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()` in the chain.
|
||||
|
||||
## Writing good descriptions
|
||||
|
||||
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:**
|
||||
```
|
||||
"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:**
|
||||
```
|
||||
"Get calendar events"
|
||||
```
|
||||
|
||||
**For tools that return permission fields**, explain what they mean:
|
||||
```
|
||||
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
|
||||
```
|
||||
|
||||
## Registering in the MCP router
|
||||
|
||||
After adding `.meta()` to procedures, you must register the router in `packages/api/src/mcp.ts`. This file uses **eager imports** (no `lazy()`) because MCP tool extraction requires synchronous access to all procedure definitions.
|
||||
|
||||
```typescript
|
||||
// packages/api/src/mcp.ts
|
||||
import { myNewRouter } from "./router/my-new-feature";
|
||||
|
||||
export const mcpRouter = createTRPCRouter({
|
||||
// ... existing routers
|
||||
myFeature: myNewRouter,
|
||||
});
|
||||
```
|
||||
|
||||
**If your router is nested** (e.g., widget sub-routers), import and register the sub-router directly — do not import a parent that uses `lazy()`.
|
||||
|
||||
## 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
|
||||
- Procedures that return non-serializable data (streams, blobs)
|
||||
|
||||
## Input schema rules
|
||||
|
||||
- Use `z.object({...})` for inputs — this maps cleanly to MCP tool parameters
|
||||
- `z.void()` (no `.input()`) works — the MCP handler patches these to accept empty objects
|
||||
- Avoid `z.union()` or `z.discriminatedUnion()` at the top level — AI clients struggle with these
|
||||
- Use `.default()` on optional fields so the AI doesn't need to guess values
|
||||
|
||||
## Checklist for new features
|
||||
|
||||
1. Add `.meta({ mcp: { enabled: true, description: "..." } })` to relevant procedures
|
||||
2. Import and register the router in `packages/api/src/mcp.ts` (eager, no lazy)
|
||||
3. Write descriptions that explain what the tool does, what it needs, and what it returns
|
||||
4. If the tool requires an ID from another tool, say which one in the description
|
||||
5. Test with `curl` against `/api/mcp/mcp` to verify the tool appears in `tools/list`
|
||||
@@ -5,18 +5,9 @@ node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
**/.output
|
||||
.turbo
|
||||
**/.turbo
|
||||
**/next-env.d.ts
|
||||
packages/db/migrations/*/migrate.cjs
|
||||
packages/cli/cli.cjs
|
||||
apps/tasks/tasks.cjs
|
||||
apps/tasks/tasks.css
|
||||
apps/websocket/wssServer.cjs
|
||||
apps/websocket/wssServer.css
|
||||
.bun-cache
|
||||
**/.bun-cache
|
||||
.pnpm-store
|
||||
**/.pnpm-store
|
||||
coverage
|
||||
**/coverage
|
||||
**/.docusaurus
|
||||
@@ -32,8 +23,3 @@ apps/nextjs/.next
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
benchmark-results
|
||||
scripts/benchmarks/results
|
||||
**/__pycache__
|
||||
**/*.tsbuildinfo
|
||||
@@ -1,25 +1,34 @@
|
||||
# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo.
|
||||
# Keep this file up-to-date when you add new variables to `.env`.
|
||||
# Keep this file up-to-date when you add new variables to \`.env\`.
|
||||
|
||||
# This file will be committed to version control, so make sure not to have any secrets in it.
|
||||
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.
|
||||
|
||||
# Generate two different 64-character hex values with `openssl rand -hex 32` before starting Homarr.
|
||||
# Never reuse these values or commit the populated `.env` file.
|
||||
AUTH_SECRET=
|
||||
# The below secret is not used anywhere but required for Auth.js (Would encrypt JWTs and Mail hashes, both not used)
|
||||
AUTH_SECRET="supersecret"
|
||||
|
||||
# The below secret is used to encrypt integration secrets in the database.
|
||||
# Homarr will reject an empty or invalid value and print a randomly generated candidate.
|
||||
SECRET_ENCRYPTION_KEY=
|
||||
# It should be a 32-byte string, generated by running `openssl rand -hex 32` on Unix
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
DB_DRIVER='better-sqlite3'
|
||||
# Set an absolute writable SQLite path so migrations and the app use the same file.
|
||||
# Their working directories differ; do not use a relative path here.
|
||||
# Container images use /appdata/db/db.sqlite by default instead.
|
||||
DB_URL=
|
||||
DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
|
||||
# Those are the two ways to use the mysql2 driver:
|
||||
# 1. Using the URL format:
|
||||
# DB_DRIVER='mysql2'
|
||||
# DB_URL='mysql://user:password@host:port/database'
|
||||
# 2. Using the connection options format:
|
||||
# DB_DRIVER='mysql2'
|
||||
# DB_HOST='localhost'
|
||||
# DB_PORT='3306'
|
||||
# DB_USER='username'
|
||||
# DB_PASSWORD='password'
|
||||
# DB_NAME='name-of-database'
|
||||
|
||||
# The following is an example on how to use the node-postgres driver:
|
||||
# DB_DRIVER='node-postgres'
|
||||
@@ -54,8 +63,6 @@ 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.
|
||||
# Public Homarr origin used by MCP OAuth metadata and redirects behind proxies.
|
||||
# BASE_URL=https://homarr.example.com
|
||||
HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# PocketBase API used by Homarr and by the standalone documentation site.
|
||||
# Defaults to HOMARR_WEBSITE_URL.
|
||||
@@ -79,6 +86,6 @@ HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# 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; Assistant conversation history remains writable for canned chats.
|
||||
# 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
|
||||
@@ -25,18 +25,6 @@ body:
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other information related to the bug here, screenshots if applicable.
|
||||
- type: input
|
||||
id: exactBuild
|
||||
attributes:
|
||||
label: Exact build or image
|
||||
description: For preview or development builds, include the commit SHA or image digest. A moving tag such as v2 is not enough to identify the build.
|
||||
placeholder: "Commit SHA, image digest, or release version"
|
||||
- type: input
|
||||
id: lastWorkingVersion
|
||||
attributes:
|
||||
label: Last working version
|
||||
description: If this worked before, include the last version or build where you tried the same action. Leave blank if unknown.
|
||||
placeholder: "Version or commit SHA"
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
@@ -45,14 +33,6 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 2.2.0
|
||||
- 2.1.2
|
||||
- 2.0.0
|
||||
- 1.77.0
|
||||
- 1.76.2
|
||||
- 1.76.1
|
||||
- 1.76.0
|
||||
- 1.75.0
|
||||
- 1.74.0
|
||||
- 1.73.0
|
||||
- 1.72.0
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
name: 🐞 Custom Widget bug
|
||||
description: Report a problem with a Custom JSX widget, its requests, preview, or editor
|
||||
title: "bug: custom widget "
|
||||
labels: ["needs triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Share a small reproduction so we can distinguish widget code, upstream API, and Homarr problems.
|
||||
Remove API keys, passwords, tokens, cookies, authorization headers, private URLs, and personal data from everything you share, including exports and screenshots. Replace private hosts with `https://example.invalid` while preserving relevant paths and response structure.
|
||||
- type: textarea
|
||||
id: behavior
|
||||
attributes:
|
||||
label: Actual and expected behavior
|
||||
description: Describe what happens and what you expected. Include the exact error text and a sanitized screenshot if useful.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Include the affected request or control, relevant widget options, and whether this occurs in Test, preview, or on the dashboard. For layout issues, include the widget size and desktop or mobile viewport.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: definition
|
||||
attributes:
|
||||
label: Sanitized widget definition
|
||||
description: Paste or attach the exported JSON with sources, requests, options, and JSX template. Prefer the smallest definition that reproduces the problem. If export fails, include the relevant JSX and request configuration. Check for private URLs and sensitive data before sharing.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: Include the exact Homarr release, commit SHA, or image digest; installation method; browser/version; and desktop or mobile OS. For request issues, include the source type (HTTP or integration), integration kind, and upstream service version. Do not include server URLs or credentials.
|
||||
placeholder: |
|
||||
Homarr version / commit / image digest:
|
||||
Installation method:
|
||||
Browser and OS:
|
||||
Source type, integration kind, and service version:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: diagnostics
|
||||
attributes:
|
||||
label: Sanitized API response and logs
|
||||
description: For request or data issues, include the request ID, method, relative API path, HTTP status, and a small response sample preserving field names and types. Add relevant Homarr or browser console errors. Remove sensitive values and private URLs; leave blank for purely visual issues.
|
||||
@@ -3,20 +3,18 @@
|
||||
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
|
||||
|
||||
```bash
|
||||
bun run ci:act:fast
|
||||
bun run ci:act:docker
|
||||
bun run ci:act:all
|
||||
bun run ci:act:cleanup
|
||||
pnpm ci:act:fast
|
||||
pnpm ci:act:docker
|
||||
pnpm ci:act:all
|
||||
pnpm ci:act:cleanup
|
||||
```
|
||||
|
||||
`fast` runs the database checks and quality gate and requires an `origin/dev` tracking ref. `docker` and `all` use the manual validation event to run those checks, build the container once, and run the converter boot/restart smoke test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
|
||||
|
||||
For rootless Docker, set `ACT_TESTCONTAINERS_HOST` to a host address reachable from the runner when loopback callbacks are unavailable.
|
||||
`fast` runs the quality gate. `docker` and `all` run the gate followed by the single container build, E2E suite, and memory regression test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
|
||||
|
||||
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
|
||||
|
||||
```bash
|
||||
ACT_SECRET_FILE=.github/act/.secrets bun run ci:act:all
|
||||
ACT_SECRET_FILE=.github/act/.secrets pnpm ci:act:all
|
||||
```
|
||||
|
||||
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
|
||||
|
||||
@@ -19,6 +19,11 @@ inputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prebuilt debian dependencies
|
||||
uses: homarr-labs/homarr/.github/actions/prebuilt-debian@dev
|
||||
id: prebuilt-debian
|
||||
with:
|
||||
architecture: ${{ inputs.architecture }}
|
||||
- name: Start docker container for ${{ inputs.architecture }}
|
||||
run: |
|
||||
docker run --name homarr \
|
||||
@@ -38,6 +43,8 @@ runs:
|
||||
shell: bash
|
||||
- name: Extract source from ${{ inputs.architecture }} container (debian)
|
||||
run: |
|
||||
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/build && \
|
||||
docker cp ${{ steps.prebuilt-debian.outputs.path }}/. homarr:/app/node_modules/better-sqlite3/build/Release && \
|
||||
docker exec homarr tar -czf extraction-debian.tar.gz -C /app . && \
|
||||
docker cp homarr:/app/extraction-debian.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz
|
||||
shell: bash
|
||||
|
||||
@@ -11,6 +11,9 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<h3>Homarr</h3>
|
||||
</div>
|
||||
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following requirements:**
|
||||
**Thank you for your contribution. Please ensure that your pull request meets the following pull request:**
|
||||
|
||||
- [ ] Relevant checks pass without warnings or errors
|
||||
- [ ] Pull request targets the intended base branch (`dev` by default; `release/v2` for v2 release work)
|
||||
- [ ] Builds without warnings or errors (`pnpm build`, autofix with `pnpm format:fix`)
|
||||
- [ ] Pull request targets `dev` branch
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] No shorthand variable names are used (for example, `x`, `y`, `i`, or abbreviations)
|
||||
- [ ] Documentation under `apps/docs` is updated only when users need new or corrected information to use the app
|
||||
- [ ] When using AI, no temporary files are checked in and the code style follows the rest of the project
|
||||
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
|
||||
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
|
||||
- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project
|
||||
|
||||
**When contributing a new integration or widget:**
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
matchPackageNames: ["^@homarr/"],
|
||||
enabled: false,
|
||||
},
|
||||
// Reenable once https://github.com/privatenumber/tsx/issues/737 is fixed
|
||||
{
|
||||
matchPackageNames: ["tsx"],
|
||||
enabled: false,
|
||||
},
|
||||
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
|
||||
{
|
||||
matchPackageNames: ["undici"],
|
||||
@@ -17,10 +22,11 @@
|
||||
automerge: true,
|
||||
},
|
||||
{
|
||||
// disallow automerge for trustedDependencies (native builds need manual verification)
|
||||
// disallow automerge for allowBuilds dependencies (native builds need manual verification)
|
||||
matchPackageNames: [
|
||||
"@tree-sitter-grammars/tree-sitter-yaml",
|
||||
"bcrypt",
|
||||
"better-sqlite3",
|
||||
"cpu-features",
|
||||
"esbuild",
|
||||
"sharp",
|
||||
@@ -31,6 +37,12 @@
|
||||
automerge: false,
|
||||
},
|
||||
],
|
||||
// Updates lockfile for prebuilt-debian after if package.json is updated, otherwise only root lockfile is updated
|
||||
postUpgradeTasks: {
|
||||
commands: ["cd deployments/prebuilt-debian && pnpm install --ignore-workspace --lockfile-only"],
|
||||
fileFilters: ["deployments/prebuilt-debian/pnpm-lock.yaml"],
|
||||
executionMode: "update",
|
||||
},
|
||||
updateInternalDeps: true,
|
||||
rangeStrategy: "bump",
|
||||
automerge: false,
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Formatting
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: ["**"]
|
||||
branches: ["*"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -32,4 +32,4 @@ jobs:
|
||||
run: |
|
||||
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
|
||||
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
|
||||
| xargs -0 -r bun x oxfmt --check --no-error-on-unmatched-pattern --
|
||||
| xargs -0 -r pnpm exec oxfmt --check --
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve a pull request and enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
|
||||
@@ -10,8 +10,6 @@ on:
|
||||
push:
|
||||
branches: [main, dev, beta, next, v2, release/v2]
|
||||
merge_group:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
operation:
|
||||
@@ -49,17 +47,11 @@ env:
|
||||
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
|
||||
|
||||
jobs:
|
||||
database-validation:
|
||||
name: Database validation
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
uses: ./.github/workflows/database.yml
|
||||
|
||||
fast-gate:
|
||||
name: Fast gate
|
||||
needs: database-validation
|
||||
if: ${{ !cancelled() && github.event_name != 'pull_request_target' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v2.')) && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup') }}
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
@@ -67,12 +59,8 @@ jobs:
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || 'HEAD~1' }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
TURBO_PLATFORM: linux/amd64
|
||||
steps:
|
||||
- name: Require database validation
|
||||
env:
|
||||
DATABASE_RESULT: ${{ needs.database-validation.result }}
|
||||
run: test "$DATABASE_RESULT" = success
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
@@ -91,42 +79,41 @@ jobs:
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "all"
|
||||
turbo-cache: "true"
|
||||
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Lint, typecheck, and build workspaces
|
||||
run: |
|
||||
scope=(--affected)
|
||||
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
|
||||
scope=()
|
||||
fi
|
||||
bun run turbo run lint typecheck build "${scope[@]}" --continue=dependencies-successful --concurrency=2
|
||||
- name: Lint, typecheck, and build affected workspaces
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=2
|
||||
|
||||
- name: Validate workspace configuration
|
||||
run: bun run lint:ws
|
||||
run: pnpm lint:ws
|
||||
|
||||
- name: Validate Custom Widget architecture
|
||||
run: bun run check:custom-widgets
|
||||
run: pnpm check:custom-widgets
|
||||
|
||||
- name: Validate checked-in OpenAPI schema
|
||||
run: bun run check:openapi
|
||||
run: pnpm check:openapi
|
||||
|
||||
- name: Validate Custom Widget bundle budget
|
||||
run: |
|
||||
bun run turbo run build --filter=@homarr/nextjs
|
||||
bun run check:custom-widget-bundle
|
||||
pnpm --filter nextjs build
|
||||
pnpm check:custom-widget-bundle
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
container:
|
||||
name: Container and migration smoke
|
||||
# Legacy converter verification is opt-in, not a release publication gate.
|
||||
name: Container and E2E
|
||||
needs: [fast-gate, preview-amd64]
|
||||
if: |
|
||||
!cancelled() && needs.fast-gate.result == 'success' &&
|
||||
github.event_name == 'workflow_dispatch' && inputs.operation == 'validate'
|
||||
(
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/release/v2') ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
@@ -142,6 +129,10 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Reuse amd64 preview image
|
||||
if: needs.preview-amd64.outputs.digest != '' && !env.ACT
|
||||
env:
|
||||
@@ -149,12 +140,9 @@ jobs:
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
docker pull "${PREVIEW_IMAGE}@${DIGEST}"
|
||||
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-ci
|
||||
docker tag "${PREVIEW_IMAGE}@${DIGEST}" homarr-e2e
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build container once
|
||||
if: needs.preview-amd64.outputs.digest == '' && !env.ACT
|
||||
uses: docker/build-push-action@v7
|
||||
@@ -187,8 +175,14 @@ jobs:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
run: |
|
||||
bash tooling/github/docker-build.sh --platform linux/amd64 \
|
||||
--tag homarr-ci --tag homarr-e2e .
|
||||
secret_args=()
|
||||
for secret in TURBO_API TURBO_TEAM TURBO_TOKEN TURBO_REMOTE_CACHE_SIGNATURE_KEY; do
|
||||
if [ -n "${!secret:-}" ]; then
|
||||
secret_args+=(--secret "id=${secret},env=${secret}")
|
||||
fi
|
||||
done
|
||||
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \
|
||||
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
if: "!env.ACT"
|
||||
@@ -196,24 +190,21 @@ jobs:
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('bun.lock') }}
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
|
||||
run: bun x playwright install --with-deps chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: bun x playwright install-deps chromium
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Install standalone converter test dependencies
|
||||
run: bun install --cwd tools/mysql-to-sqlite --frozen-lockfile
|
||||
|
||||
- name: Verify converted database boots and survives restart
|
||||
- name: Run E2E tests
|
||||
env:
|
||||
NODE_ENV: development
|
||||
CI: "true"
|
||||
run: bun x vitest run --project e2e e2e/mysql-conversion.spec.ts --maxWorkers 1
|
||||
run: pnpm exec vitest e2e --retry 1 --maxWorkers 4
|
||||
|
||||
- name: Upload E2E artifacts
|
||||
if: failure() && !env.ACT
|
||||
@@ -329,10 +320,10 @@ jobs:
|
||||
|
||||
publish-preview:
|
||||
name: Publish multi-platform preview image
|
||||
needs: [fast-gate, preview-amd64, preview-arm64]
|
||||
needs: [container, preview-amd64, preview-arm64]
|
||||
if: |
|
||||
!cancelled() &&
|
||||
needs.fast-gate.result == 'success' &&
|
||||
needs.container.result == 'success' &&
|
||||
needs.preview-amd64.result == 'success' &&
|
||||
needs.preview-arm64.result == 'success' &&
|
||||
(
|
||||
@@ -445,7 +436,7 @@ jobs:
|
||||
const targetTags = target.metadata?.container?.tags ?? [];
|
||||
const extraTags = targetTags.filter((targetTag) => targetTag !== tag);
|
||||
if (extraTags.length > 0) {
|
||||
core.warning(
|
||||
core.setFailed(
|
||||
`Package version ${target.id} also carries tag(s) ${extraTags.map((targetTag) => `"${targetTag}"`).join(", ")}; refusing cleanup.`,
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.CROWDIN_CLIENT_ID }}
|
||||
client-id: ${{ secrets.CROWDIN_APP_ID }}
|
||||
permission-contents: write # required to commit to crowdin branch
|
||||
permission-pull-requests: write # required to create pull request
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
name: Database migrations
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NODE_ENV: development
|
||||
CI: "true"
|
||||
SKIP_ENV_VALIDATION: "true"
|
||||
TURBO_TELEMETRY_DISABLED: "1"
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
migrations: ${{ steps.select.outputs.migrations }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Select database checks
|
||||
id: select
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
|
||||
shell: bash
|
||||
run: |
|
||||
migrations=false
|
||||
if [[ "$EVENT_NAME" != pull_request && "$EVENT_NAME" != merge_group && "$EVENT_NAME" != push ]] ||
|
||||
[[ -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]] ||
|
||||
! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
|
||||
migrations=true
|
||||
else
|
||||
changed_files=$(mktemp)
|
||||
trap 'rm -f "$changed_files"' EXIT
|
||||
comparison="$BASE_SHA...HEAD"
|
||||
if [[ "$EVENT_NAME" = push ]]; then comparison="$BASE_SHA..HEAD"; fi
|
||||
git diff --name-only -z "$comparison" > "$changed_files"
|
||||
while IFS= read -r -d '' file; do
|
||||
case "$file" in
|
||||
packages/db/*|packages/core/src/infrastructure/db/*|packages/core/src/infrastructure/certificates/*|packages/auth/*|packages/api/src/router/onboard*|packages/api/src/router/test/onboard*|packages/onboarding/*|apps/nextjs/src/app/api/backup/*|apps/nextjs/src/app/*/init/*|Dockerfile|scripts/run.sh|scripts/entrypoint.sh|deployments/*|package.json|packages/core/package.json|packages/common/package.json|packages/definitions/package.json|packages/server-settings/package.json|packages/validation/package.json|mise.toml|bun.lock|bunfig.toml|patches/*|vitest*|tooling/github/*|.github/workflows/database.yml|.github/workflows/ci.yml)
|
||||
migrations=true
|
||||
;;
|
||||
esac
|
||||
done < "$changed_files"
|
||||
fi
|
||||
echo "migrations=$migrations" >> "$GITHUB_OUTPUT"
|
||||
|
||||
journals:
|
||||
name: Migration journals
|
||||
needs: changes
|
||||
if: needs.changes.outputs.migrations == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./tooling/github/setup
|
||||
- run: bun x vitest run --project db-node packages/db/test/migration-journal.spec.ts
|
||||
|
||||
migrations:
|
||||
name: SQLite and PostgreSQL migrations
|
||||
needs: changes
|
||||
if: needs.changes.outputs.migrations == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./tooling/github/setup
|
||||
- name: Reject retired database configuration
|
||||
run: bun x vitest run --project dom packages/core/src/infrastructure/db/env.spec.ts
|
||||
- run: bun x vitest run --project db-node --project integration packages/db/test/sqlite-migration.spec.ts packages/db/test/postgresql-migration.spec.ts packages/db/proxy-reader.integration.spec.ts --maxWorkers 1
|
||||
|
||||
database-gate:
|
||||
name: Database gate
|
||||
needs: [changes, journals, migrations]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Require selected database checks
|
||||
env:
|
||||
CHANGES: ${{ needs.changes.result }}
|
||||
JOURNALS: ${{ needs.journals.result }}
|
||||
MIGRATIONS: ${{ needs.migrations.result }}
|
||||
RUN_MIGRATIONS: ${{ needs.changes.outputs.migrations }}
|
||||
run: |
|
||||
test "$CHANGES" = success
|
||||
test "$RUN_MIGRATIONS" = true || test "$RUN_MIGRATIONS" = false
|
||||
if [ "$RUN_MIGRATIONS" = true ]; then
|
||||
test "$JOURNALS" = success
|
||||
test "$MIGRATIONS" = success
|
||||
fi
|
||||
@@ -16,10 +16,10 @@ permissions:
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ inputs.send-notifications }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -37,22 +37,11 @@ jobs:
|
||||
branch: next
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
run: |
|
||||
pr_url="$(gh pr list --base beta --head next --state open --json url --jq '.[0].url // empty')"
|
||||
if [[ -z "$pr_url" ]]; then
|
||||
pr_url="$(gh pr create \
|
||||
--title "chore(release): release ${{ steps.semver.outputs.next }}-beta" \
|
||||
--body "Detected change to be ${{ steps.semver.outputs.bump }}<br/>Bump version from ${{ steps.semver.outputs.current }} to ${{ steps.semver.outputs.next }}-beta" \
|
||||
--base beta \
|
||||
--head next \
|
||||
--label automerge)"
|
||||
fi
|
||||
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
|
||||
run: 'gh pr create --title "chore(release): release ${{ steps.semver.outputs.next }}-beta" --body "Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}-beta``" --base beta --head next --label automerge'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Discord notification
|
||||
if: ${{ inputs.send-notifications }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -63,19 +52,19 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
|
||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||
- name: Obtain token
|
||||
id: obtainMergeToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # write to beta branch (due to merge)
|
||||
permission-pull-requests: write # merge pull request
|
||||
- id: automerge
|
||||
@@ -93,21 +82,21 @@ jobs:
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && inputs.send-notifications }}
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
|
||||
- name: Major Bump Discord notification
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && inputs.send-notifications }}
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}-beta"
|
||||
- name: Discord Fail Notification
|
||||
if: ${{ failure() && inputs.send-notifications }}
|
||||
if: failure() && github.event.inputs.send-notifications
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: Send notifications when redeploying an existing release
|
||||
description: Send notifications
|
||||
|
||||
permissions:
|
||||
contents: write # Required to update package.json version
|
||||
@@ -38,11 +38,10 @@ jobs:
|
||||
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' || github.ref_name == 'next' }}
|
||||
outputs:
|
||||
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
|
||||
git_ref: ${{ steps.read-git-ref.outputs.ref || steps.read-semver.outputs.version || github.ref }}
|
||||
git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }}
|
||||
skipped: ${{ env.SKIP_RELEASE }}
|
||||
upstream_sync: ${{ steps.update-upstream.outcome }}
|
||||
steps:
|
||||
- run: echo "Manual dispatch redeploys the latest existing release without creating a new one"
|
||||
- run: echo "Skipping release for workflow_dispatch event"
|
||||
if: env.SKIP_RELEASE == 'true'
|
||||
# The below generated version fallback represents a normalized branch name, for example "feature/branch-name" -> "feature-branch-name"
|
||||
- run: echo "version="$(echo ${{github.ref_name}} | sed 's/[^a-zA-Z0-9\-]/-/g') >> "$GITHUB_OUTPUT"
|
||||
@@ -55,41 +54,26 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: pnpm/action-setup@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
- name: Verify major release intent
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: |
|
||||
latest_tag="$(git describe --tags --abbrev=0)"
|
||||
latest_major="${latest_tag#v}"
|
||||
latest_major="${latest_major%%.*}"
|
||||
package_version="$(jq --raw-output '.version' package.json)"
|
||||
package_major="${package_version%%.*}"
|
||||
|
||||
if (( package_major <= latest_major )); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git log --format='%s%n%b' "${latest_tag}..HEAD" \
|
||||
| grep --extended-regexp --quiet '(^[[:alnum:]-]+(\([^)]*\))?!:)|(^BREAKING[ -]CHANGE:)'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::error::package.json declares ${package_version}, but commits since ${latest_tag} contain no breaking-change signal. Use a conventional ! commit or BREAKING CHANGE footer so Semantic Release creates the intended major version."
|
||||
exit 1
|
||||
- name: Install dependencies
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
pnpm install
|
||||
- name: Run Semantic Release
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
env:
|
||||
@@ -99,7 +83,7 @@ jobs:
|
||||
GIT_COMMITTER_NAME: "Releases Homarr"
|
||||
GIT_COMMITTER_EMAIL: "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
run: |
|
||||
bun run release
|
||||
pnpm release
|
||||
- name: Read semver output
|
||||
# We read the last tag either from the created release or from the current branch, this is to rerun the deployment job for the currently released version when it failed
|
||||
if: env.SKIP_RELEASE == 'false' || github.ref_name == 'main' || github.ref_name == 'beta'
|
||||
@@ -113,26 +97,20 @@ jobs:
|
||||
run: |
|
||||
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
- name: Update dev / next branch
|
||||
id: update-upstream
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
# Finish publishing the already-created release, then fail in the publish job if synchronization did not work.
|
||||
continue-on-error: true
|
||||
continue-on-error: true # Prevent pipeline from failing when merge fails
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
UPSTREAM_BRANCH: ${{ github.ref_name == 'main' && 'dev' || 'next' }}
|
||||
RELEASE_REF: ${{ steps.read-git-ref.outputs.ref }}
|
||||
run: |
|
||||
git config user.name "Releases Homarr"
|
||||
git config user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
||||
git fetch origin "$UPSTREAM_BRANCH"
|
||||
|
||||
# Dependency installation can modify generated files in the release checkout.
|
||||
sync_dir="$(mktemp -d "${RUNNER_TEMP}/homarr-release-sync.XXXXXX")"
|
||||
trap 'git worktree remove --force "$sync_dir"' EXIT
|
||||
git worktree add --detach "$sync_dir" FETCH_HEAD
|
||||
git -C "$sync_dir" merge --no-edit "$RELEASE_REF"
|
||||
git -C "$sync_dir" push origin "HEAD:${UPSTREAM_BRANCH}"
|
||||
git remote set-url origin https://x-access-token:${{ steps.obtainToken.outputs.token }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin ${{ env.UPSTREAM_BRANCH }}
|
||||
git checkout ${{ env.UPSTREAM_BRANCH }}
|
||||
git pull origin ${{ env.UPSTREAM_BRANCH }}
|
||||
git rebase ${{ github.ref_name }}
|
||||
git push origin ${{ env.UPSTREAM_BRANCH }}
|
||||
build-amd64:
|
||||
name: Build docker image for amd64
|
||||
needs: release
|
||||
@@ -227,14 +205,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
persist-credentials: false
|
||||
- name: Extract amd64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: ./.github/actions/extract-build-artifact
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }}"
|
||||
architecture: amd64
|
||||
@@ -248,14 +221,9 @@ jobs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
persist-credentials: false
|
||||
- name: Extract arm64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: ./.github/actions/extract-build-artifact
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}"
|
||||
architecture: arm64
|
||||
@@ -278,7 +246,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to publish the release
|
||||
|
||||
- name: Log in to the Container registry
|
||||
@@ -315,30 +283,9 @@ jobs:
|
||||
run: |
|
||||
gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false
|
||||
|
||||
- name: Verify dev / next synchronization
|
||||
if: needs.release.outputs.skipped == 'false' && needs.release.outputs.upstream_sync != 'success'
|
||||
run: |
|
||||
echo "::error::The released commit was not synchronized back to the upstream development branch."
|
||||
exit 1
|
||||
|
||||
- name: Discord notification
|
||||
if: github.event_name != 'workflow_dispatch' || inputs['send-notifications']
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
|
||||
|
||||
notify-failure:
|
||||
name: Notify deployment failure
|
||||
if: ${{ always() && (failure() || cancelled()) && (github.event_name != 'workflow_dispatch' || inputs['send-notifications']) }}
|
||||
needs: [release, build-amd64, build-arm64, extract-asset-amd64, extract-asset-arm64, publish]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord failure notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The deployment for branch **${{ github.ref_name }}** failed or was cancelled: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
@@ -5,13 +5,7 @@ on:
|
||||
branches: ["dev"]
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "packages/**"
|
||||
- "tooling/**"
|
||||
- "tools/motion-reel/three/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
- "packages/definitions/src/docs/**"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -39,24 +33,16 @@ jobs:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "docs"
|
||||
turbo-cache: "true"
|
||||
- name: Build docs
|
||||
run: bun run turbo run build --filter=@homarr/docs
|
||||
run: pnpm turbo build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Validate exported links and search
|
||||
run: |
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: "apps/docs/out"
|
||||
path: "apps/docs/build"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -19,10 +19,10 @@ permissions:
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ github.event_name == 'schedule' || inputs.send-notifications }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -38,30 +38,13 @@ jobs:
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
branch: dev
|
||||
noNewCommitBehavior: silent
|
||||
noVersionBumpBehavior: silent
|
||||
- name: No release needed
|
||||
if: ${{ steps.semver.outputs.bump == '' || steps.semver.outputs.bump == 'none' }}
|
||||
run: echo "No release-worthy commits were found since ${{ steps.semver.outputs.current }}." >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
if: ${{ steps.semver.outputs.bump != '' && steps.semver.outputs.bump != 'none' }}
|
||||
run: |
|
||||
pr_url="$(gh pr list --base main --head dev --state open --json url --jq '.[0].url // empty')"
|
||||
if [[ -z "$pr_url" ]]; then
|
||||
pr_url="$(gh pr create \
|
||||
--title "chore(release): automatic release ${{ steps.semver.outputs.next }}" \
|
||||
--body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" \
|
||||
--base main \
|
||||
--head dev \
|
||||
--label automerge)"
|
||||
fi
|
||||
echo "url=$pr_url" >> "$GITHUB_OUTPUT"
|
||||
echo "pr_number=${pr_url##*/}" >> "$GITHUB_OUTPUT"
|
||||
run: 'gh pr create --title "chore(release): automatic release ${{ steps.semver.outputs.next }}" --body "**This is an automatic release**.<br/>Manual action may be required for major bumps.<br/>Detected change to be ``${{ steps.semver.outputs.bump }}``<br/>Bump version from ``${{ steps.semver.outputs.current }}`` to ``${{ steps.semver.outputs.next }}``" --base main --head dev --label automerge'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Discord notification
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
@@ -69,29 +52,26 @@ jobs:
|
||||
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }} (new behaviour: ${{ steps.semver.outputs.bump }})"
|
||||
- name: Obtain token
|
||||
id: obtainApprovalToken
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_APPROVE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review "${{ steps.create-pull-request.outputs.url }}" --approve --body "Automatically approved by GitHub Action"
|
||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||
- name: Obtain token
|
||||
id: obtainMergeToken
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' }}
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # write to main branch (due to merge)
|
||||
permission-pull-requests: write # merge pull request
|
||||
- id: automerge
|
||||
if: ${{ steps.create-pull-request.outcome == 'success' && steps.semver.outputs.bump != 'major' }}
|
||||
if: ${{ steps.semver.outputs.bump != 'major' }}
|
||||
name: automerge
|
||||
uses: "pascalgn/automerge-action@v0.16.4"
|
||||
env:
|
||||
@@ -105,21 +85,21 @@ jobs:
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Merged PR ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
|
||||
- name: Major Bump Discord notification
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The release PR must be manually merged because the next version is a major version: ${{ steps.create-pull-request.outputs.url }} for release ${{ steps.semver.outputs.next }}"
|
||||
- name: Discord Fail Notification
|
||||
if: ${{ failure() && (github.event_name == 'schedule' || inputs.send-notifications) }}
|
||||
if: failure() && github.event.inputs.send-notifications
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
name: "[Docs] Algolia Recrawl"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["[Deployment] Documentation"]
|
||||
types: [completed]
|
||||
branches: [dev]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
recrawl:
|
||||
if: >-
|
||||
${{
|
||||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'pull_request')
|
||||
|| github.event_name == 'workflow_dispatch'
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Check Algolia crawler secrets
|
||||
run: |
|
||||
missing=0
|
||||
for secret in ALGOLIA_CRAWLER_ID ALGOLIA_CRAWLER_USER_ID ALGOLIA_CRAWLER_API_KEY; do
|
||||
if [ -z "${!secret}" ]; then
|
||||
echo "::warning::Missing repository secret: $secret"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -eq 1 ]; then
|
||||
echo "Add crawler secrets to enable automatic recrawls."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
ALGOLIA_CRAWLER_ID: ${{ secrets.ALGOLIA_CRAWLER_ID }}
|
||||
ALGOLIA_CRAWLER_USER_ID: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
|
||||
ALGOLIA_CRAWLER_API_KEY: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
|
||||
|
||||
- name: Wait for GitHub Pages to serve the new deployment
|
||||
run: sleep 60
|
||||
|
||||
- name: Trigger Algolia DocSearch recrawl
|
||||
uses: cssnr/algolia-crawler-action@v2
|
||||
with:
|
||||
crawler_id: ${{ secrets.ALGOLIA_CRAWLER_ID }}
|
||||
crawler_user_id: ${{ secrets.ALGOLIA_CRAWLER_USER_ID }}
|
||||
crawler_api_key: ${{ secrets.ALGOLIA_CRAWLER_API_KEY }}
|
||||
|
||||
- name: Verify search index
|
||||
working-directory: apps/docs
|
||||
run: pnpm verify:search
|
||||
@@ -28,12 +28,12 @@ jobs:
|
||||
|
||||
- name: Dump contributors
|
||||
working-directory: apps/docs
|
||||
run: bun run api-dump-contributors
|
||||
run: pnpm api-dump-contributors
|
||||
|
||||
- name: Commit updated contributors
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/public"
|
||||
add: "apps/docs/static"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update contributors"
|
||||
push: true
|
||||
@@ -26,13 +26,41 @@ jobs:
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate and validate OpenAPI spec
|
||||
run: bun run scripts:update-openapi
|
||||
- name: Start dev server
|
||||
run: pnpm --filter @homarr/nextjs dev &
|
||||
env:
|
||||
TURBO_TELEMETRY_DISABLED: 1
|
||||
DISABLE_TELEMETRY: 1
|
||||
|
||||
- name: Wait for server and fetch spec
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS http://localhost:3000/api/openapi -o /tmp/openapi.json 2>/dev/null; then
|
||||
echo "Server is up, spec fetched"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for server... ($i/30)"
|
||||
sleep 3
|
||||
done
|
||||
|
||||
if [ ! -f /tmp/openapi.json ]; then
|
||||
echo "::error::Server failed to start within timeout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -e '.openapi and .paths' /tmp/openapi.json > /dev/null || {
|
||||
echo "::error::Fetched file is not a valid OpenAPI spec"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cp /tmp/openapi.json apps/docs/static/api/open-api-schema.json
|
||||
echo "OpenAPI spec updated ($(jq '.paths | length' /tmp/openapi.json) paths)"
|
||||
|
||||
- name: Commit updated spec
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/public/api/"
|
||||
add: "apps/docs/static/api/"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update OpenAPI spec"
|
||||
push: true
|
||||
@@ -5,7 +5,7 @@ permissions:
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
branches: ["*"]
|
||||
paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"]
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,22 +5,15 @@ permissions:
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
branches: ["*"]
|
||||
paths: [".github/renovate.json5"]
|
||||
|
||||
jobs:
|
||||
renovate-validate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
env:
|
||||
# The optional RE2 addon uses V8 APIs; validation can use JavaScript regexes.
|
||||
RENOVATE_X_IGNORE_RE2: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
- run: |
|
||||
bun x --package renovate \
|
||||
npx --yes --package renovate -- \
|
||||
renovate-config-validator --strict .github/renovate.json5
|
||||
@@ -5,6 +5,39 @@ on:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
trigger-docs-release:
|
||||
name: Trigger Documentation Release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||
owner: homarr-labs
|
||||
repositories: |
|
||||
documentation
|
||||
permission-contents: write # required to dispatch repository workflow
|
||||
- name: Trigger documentation release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
SOURCE_TAG: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/homarr-labs/documentation/dispatches \
|
||||
-d @- <<EOF
|
||||
{
|
||||
"event_type": "trigger-release",
|
||||
"client_payload": {
|
||||
"tag": "${SOURCE_TAG}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
update-bug-report-template:
|
||||
name: Update Bug Report Template
|
||||
runs-on: ubuntu-latest
|
||||
@@ -15,7 +48,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.RENOVATE_MERGE_CLIENT_ID }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
@@ -25,7 +58,7 @@ jobs:
|
||||
ref: dev
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: bun run scripts:update-bug-report-template
|
||||
- run: pnpm run scripts:update-bug-report-template
|
||||
env:
|
||||
NEXT_VERSION: ${{ github.event.release.tag_name }}
|
||||
|
||||
|
||||
@@ -15,13 +15,16 @@ jobs:
|
||||
update-contributors:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [22]
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
|
||||
@@ -29,12 +32,16 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Run update script
|
||||
run: bun ./scripts/update-contributors.mjs
|
||||
run: node ./scripts/update-contributors.mjs
|
||||
|
||||
- name: Commit changes
|
||||
env:
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_CLIENT_ID }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: bun run scripts:update-readme-integrations
|
||||
- run: pnpm run scripts:update-readme-integrations
|
||||
- name: Commit changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
name: Validate documentation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "apps/nextjs/src/app/api/**"
|
||||
- "packages/definitions/src/**"
|
||||
- "packages/widgets/src/**"
|
||||
- "packages/custom-widgets/src/**"
|
||||
- "packages/ui/src/**"
|
||||
- "packages/workshop/src/**"
|
||||
- "packages/api/src/**"
|
||||
- "packages/auth/src/**"
|
||||
- "packages/integrations/src/**"
|
||||
- "tools/motion-reel/three/**"
|
||||
- "tooling/**"
|
||||
- "bun.lock"
|
||||
- "package.json"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
- ".github/workflows/validate-docs.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: validate-docs-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Build and validate docs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
with:
|
||||
next-cache: "docs"
|
||||
turbo-cache: "true"
|
||||
- name: Validate the published API schema against the application
|
||||
run: bun run check:openapi
|
||||
- name: Validate browser access and API schema rendering
|
||||
run: bun x vitest run 'apps/nextjs/src/app/api/[...trpc]/route.spec.ts' apps/docs/src/lib/openapi.spec.ts
|
||||
- name: Build docs
|
||||
run: bun run turbo run build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Validate exported links, search, and metadata
|
||||
run: |
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
@@ -2,123 +2,30 @@ name: Workshop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/workshop.yml"
|
||||
- "tooling/github/**"
|
||||
- "apps/workshop/**"
|
||||
- "apps/docs/**"
|
||||
- "packages/common/**"
|
||||
- "packages/core/**"
|
||||
- "packages/custom-widgets/**"
|
||||
- "packages/definitions/**"
|
||||
- "packages/db/**"
|
||||
- "packages/server-settings/**"
|
||||
- "packages/settings/**"
|
||||
- "packages/translation/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/validation/**"
|
||||
- "packages/workshop/**"
|
||||
- "tooling/typescript/**"
|
||||
- "patches/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
push:
|
||||
branches: [dev, v2, release/v2, main]
|
||||
paths:
|
||||
- ".github/workflows/workshop.yml"
|
||||
- "tooling/github/**"
|
||||
- "apps/workshop/**"
|
||||
- "apps/docs/**"
|
||||
- "packages/common/**"
|
||||
- "packages/core/**"
|
||||
- "packages/custom-widgets/**"
|
||||
- "packages/definitions/**"
|
||||
- "packages/db/**"
|
||||
- "packages/server-settings/**"
|
||||
- "packages/settings/**"
|
||||
- "packages/translation/**"
|
||||
- "packages/ui/**"
|
||||
- "packages/validation/**"
|
||||
- "packages/workshop/**"
|
||||
- "tooling/typescript/**"
|
||||
- "patches/**"
|
||||
- "package.json"
|
||||
- "bun.lock"
|
||||
- "bunfig.toml"
|
||||
- "turbo.json"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ (github.ref_name == 'v2' || github.ref_name == 'release/v2') && 'v2-preview' || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
production-smoke:
|
||||
if: github.event_name == 'pull_request'
|
||||
name: Production image smoke (${{ matrix.arch }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: ubuntu-24.04
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build the production image
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
context: .
|
||||
file: apps/workshop/Dockerfile
|
||||
target: production
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
load: true
|
||||
tags: homarr-workshop:smoke
|
||||
cache-from: type=gha,scope=workshop-linux-${{ matrix.arch }}
|
||||
cache-to: type=gha,scope=workshop-linux-${{ matrix.arch }},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 }}
|
||||
- name: Exercise the production image
|
||||
if: "!env.ACT"
|
||||
env:
|
||||
WORKSHOP_IMAGE_TEST_IMAGE: homarr-workshop:smoke
|
||||
run: sh apps/workshop/test-image.sh
|
||||
- name: Build and exercise the production image with Act
|
||||
if: env.ACT
|
||||
env:
|
||||
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 }}
|
||||
run: sh apps/workshop/test-image.sh
|
||||
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
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-amd64:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
@@ -132,7 +39,6 @@ jobs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
@@ -149,11 +55,6 @@ jobs:
|
||||
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=workshop-linux-amd64
|
||||
cache-to: type=gha,scope=workshop-linux-amd64,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-arm64:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
@@ -167,7 +68,6 @@ jobs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
@@ -184,11 +84,6 @@ jobs:
|
||||
outputs: type=image,"name=ghcr.io/homarr-labs/workshop",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=workshop-linux-arm64
|
||||
cache-to: type=gha,scope=workshop-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:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'release/v2' || github.ref_name == 'main')
|
||||
@@ -207,19 +102,6 @@ jobs:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify branch head before publication
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const branchName = context.ref.replace("refs/heads/", "");
|
||||
const { data: branch } = await github.rest.repos.getBranch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branch: branchName,
|
||||
});
|
||||
if (branch.commit.sha !== context.sha) {
|
||||
core.setFailed(`Branch ${branchName} moved from ${context.sha} to ${branch.commit.sha}.`);
|
||||
}
|
||||
- name: Publish multi-platform image
|
||||
env:
|
||||
AMD64_DIGEST: ${{ needs.publish-amd64.outputs.digest }}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.bun-cache/
|
||||
.pnpm-store/
|
||||
.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
@@ -36,7 +36,7 @@ tools/homarr-dev/homarr-dev
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
bun-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
@@ -76,12 +76,15 @@ 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
|
||||
apps/docs/.docusaurus
|
||||
apps/docs/build
|
||||
.bun-cache
|
||||
.pnpm-store
|
||||
benchmark-results
|
||||
|
||||
# local performance investigation (never ship benchmark data or restored backups)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"mantine": {
|
||||
"command": "bun",
|
||||
"args": ["x", "@mantine/mcp-server@9.6.0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[tools]
|
||||
node = "24.18.0"
|
||||
@@ -0,0 +1,2 @@
|
||||
# pnpm v11: non-auth settings moved to pnpm-workspace.yaml
|
||||
# Keep this file for any future registry/auth configuration
|
||||
@@ -8,15 +8,10 @@
|
||||
"**/lang/**",
|
||||
"static-data/**",
|
||||
"CHANGELOG.md",
|
||||
"apps/docs/.next/**",
|
||||
"apps/docs/out/**",
|
||||
"apps/docs/blog/2022/**",
|
||||
"apps/docs/blog/2023/**",
|
||||
"apps/docs/blog/2024/**",
|
||||
"apps/docs/blog/2025/**",
|
||||
"apps/docs/blog/authors.yml",
|
||||
"apps/docs/public/api/open-api-schema.json",
|
||||
"apps/docs/.docusaurus/**",
|
||||
"apps/docs/build/**",
|
||||
"apps/docs/blog/**",
|
||||
"packages/custom-widgets/src/core/component-catalog.generated.json",
|
||||
"apps/docs/public/custom-widgets/component-catalog-v1.json"
|
||||
"apps/docs/static/custom-widgets/component-catalog-v1.json"
|
||||
]
|
||||
}
|
||||
@@ -26,11 +26,16 @@
|
||||
"changelogFile": "CHANGELOG.md"
|
||||
}
|
||||
],
|
||||
"./scripts/release-version.mjs",
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
"npmPublish": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": ["package.json", "bun.lock", "CHANGELOG.md"],
|
||||
"assets": ["package.json", "CHANGELOG.md"],
|
||||
"message": "chore(release): ${nextRelease.version} [skip ci]"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="db:migration:mysql:generate" type="js.build_tools.npm">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="db:migration:mysql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:migration:postgresql:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:migration:sqlite:generate" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:push" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="db:studio" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="dev" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="docker:dev" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="format" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="format:fix" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="test" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="test:ui" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script value="typecheck" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<package-manager value="bun" />
|
||||
<package-manager value="pnpm" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "dev",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run dev",
|
||||
"command": "pnpm dev",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"name": "docker dev",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run docker:dev",
|
||||
"command": "pnpm docker:dev",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
"name": "lint fix",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run lint:fix",
|
||||
"command": "pnpm lint:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -29,7 +29,7 @@
|
||||
"name": "format fix",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run format:fix",
|
||||
"command": "pnpm format:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"name": "db push",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:push",
|
||||
"command": "pnpm db:push",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"name": "db studio",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:studio",
|
||||
"command": "pnpm db:studio",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -53,7 +53,7 @@
|
||||
"name": "db migration run",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run db:migration:sqlite:run",
|
||||
"command": "pnpm db:migration:run",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -61,7 +61,7 @@
|
||||
"name": "test",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run test",
|
||||
"command": "pnpm test",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
},
|
||||
@@ -69,7 +69,7 @@
|
||||
"name": "test ui",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "bun run test:ui",
|
||||
"command": "pnpm test:ui",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
}
|
||||
|
||||
@@ -6,24 +6,23 @@
|
||||
homarr/
|
||||
├── apps/
|
||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||
│ ├── docs/ # Next.js + Fumadocs documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron-job initialization and scheduling runtime
|
||||
│ ├── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
│ └── workshop/ # Go/PocketBase Workshop service
|
||||
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
|
||||
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
|
||||
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
|
||||
├── packages/
|
||||
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
|
||||
│ ├── auth/ # NextAuth config, providers, session, API keys
|
||||
│ ├── db/ # SQLite and PostgreSQL schemas, migrations, queries
|
||||
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
|
||||
│ ├── core/ # Env validation, DB/Redis driver factories, logging
|
||||
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
|
||||
│ ├── widgets/ # Dashboard widget definitions and components
|
||||
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
|
||||
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
|
||||
│ ├── redis/ # Redis pub/sub channels, caching abstractions
|
||||
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
|
||||
│ ├── ui/ # Shared Mantine components, theme, hooks
|
||||
│ ├── validation/ # Shared zod schemas for API/forms
|
||||
│ ├── common/ # Shared utilities, IDs, errors
|
||||
│ ├── cron-jobs/ # Cron job implementations
|
||||
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
|
||||
│ ├── cron-jobs-core/ # Cron scheduling primitives
|
||||
│ ├── cron-job-status/ # Cron status via Redis
|
||||
│ ├── boards/ # Board context, edit mode, cache updater
|
||||
@@ -41,9 +40,6 @@ homarr/
|
||||
│ ├── analytics/ # Server-side analytics (Umami)
|
||||
│ ├── server-settings/ # Server setting keys/types
|
||||
│ ├── settings/ # User-facing settings UI context
|
||||
│ ├── custom-widgets/ # Custom JSX v2 schema, validation, and runtime
|
||||
│ ├── onboarding/ # Onboarding studio and setup flow
|
||||
│ ├── workshop/ # Homarr-side Workshop client and contracts
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
@@ -55,17 +51,15 @@ homarr/
|
||||
└── Dockerfile # Multi-stage production build
|
||||
```
|
||||
|
||||
## Documentation
|
||||
## Documentation Sync
|
||||
|
||||
Add documentation only when users need the information to set up, use, or troubleshoot Homarr and cannot reasonably discover it from the UI or generated API schema. If a user does not need to know it, do not document it. A code, UI, or API change alone is never a reason to add docs.
|
||||
The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`).
|
||||
|
||||
Keep warranted documentation concise. Omit UI walkthroughs, visible control descriptions, internal implementation details, and anything already clear from the interface or schema.
|
||||
|
||||
Only after this user-need test passes, use these locations:
|
||||
When modifying user-facing code, you MUST also update the corresponding documentation:
|
||||
|
||||
- New integration → `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
|
||||
- New widget → `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
|
||||
- Changed API → `apps/docs/docs/management/api/index.mdx`
|
||||
- Changed API → `apps/docs/docs/management/api.mdx`
|
||||
- New/changed env vars → `apps/docs/docs/advanced/`
|
||||
- New CLI commands → `apps/docs/docs/advanced/command-line/`
|
||||
- Auth changes → `apps/docs/docs/advanced/` SSO pages
|
||||
@@ -73,52 +67,24 @@ Only after this user-need test passes, use these locations:
|
||||
|
||||
## Monorepo Commands
|
||||
|
||||
- `bun run dev` — Next.js app only
|
||||
- `bun run dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `bun run db:seed` — seed default database data explicitly
|
||||
- `bun run docker:dev:up` — start the Redis development service in the background
|
||||
- `bun run dev:docs` — Fumadocs site only
|
||||
- `bun run turbo build` — build all packages
|
||||
- `bun run turbo build --filter=@homarr/docs` — build docs only
|
||||
- `bun run turbo typecheck` — typecheck all packages
|
||||
- `bun run lint` / `bun run format` — oxlint / oxfmt
|
||||
- `pnpm dev` — Next.js app only
|
||||
- `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `pnpm db:seed` — seed default database data explicitly
|
||||
- `pnpm docker:dev:up` — start the Redis development service in the background
|
||||
- `pnpm dev:docs` — Docusaurus docs site only
|
||||
- `pnpm turbo build` — build all packages
|
||||
- `pnpm turbo build --filter=@homarr/docs` — build docs only
|
||||
- `pnpm turbo typecheck` — typecheck all packages
|
||||
- `pnpm lint` / `pnpm format` — oxlint / oxfmt
|
||||
|
||||
## Code Style
|
||||
|
||||
- Lint: oxlint (not ESLint)
|
||||
- Format: oxfmt (not Prettier)
|
||||
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
||||
- Mantine: use the `mantine` MCP server in `.mcp.json` for current v9 APIs before writing component code. Prefer built-in primitives (`Combobox`/`useCombobox`, the polymorphic `component` prop, `@mantine/hooks`) and check `packages/ui/` for existing conventions first.
|
||||
- Icons: @tabler/icons-react
|
||||
- Docs app can import from `@homarr/definitions` for shared types
|
||||
- Run `bun run dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `bun run dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `bun run dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
|
||||
## Testing
|
||||
|
||||
Add tests only when requested, and only when the test is extremely relevant and will plausibly prevent a future regression in user-visible behavior or a security boundary. Do not add tests that restate implementation details or duplicate stronger coverage. For focused changes, run only the relevant existing or newly requested checks when validation is needed. Do not run broad test, Docker, or E2E suites by default.
|
||||
|
||||
## MCP servers
|
||||
|
||||
`.mcp.json` declares project-scoped MCP servers. Keep only keyless definitions there; credentials belong in local dotfiles. See [Mantine LLM tooling](https://mantine.dev/guides/llms/) for client-specific setup.
|
||||
|
||||
## Agent Skills
|
||||
|
||||
Portable skills live in `.agents/skills/`. Read the relevant `SKILL.md` before working in that domain; detailed references are loaded only when needed. Claude-compatible discovery is provided through `.claude/skills`.
|
||||
|
||||
- `codebase-context` — architecture, package boundaries, and shared utilities
|
||||
- `documentation-sync` — user-critical documentation only
|
||||
- `mcp-integration` — safe tRPC-to-MCP exposure
|
||||
- `homarr-custom-widget` — safe Custom JSX v2 authoring
|
||||
|
||||
<!-- BEGIN:turborepo-agent-rules -->
|
||||
|
||||
# This is NOT the Turborepo you know
|
||||
|
||||
Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`.
|
||||
|
||||
Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
|
||||
|
||||
This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. When agent guidance is enabled, keep the block committed to avoid an uncommitted change on the next agent invocation. This repository disables agent guidance in `turbo.json`.
|
||||
<!-- END:turborepo-agent-rules -->
|
||||
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `pnpm dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `pnpm dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
- Run Go checks from `tools/homarr-dev` with `go test ./...` and `go vet ./...`.
|
||||
@@ -1,27 +1,3 @@
|
||||
## [2.3.0](https://github.com/homarr-labs/homarr/compare/v2.2.0...v2.3.0) (2026-10-07)
|
||||
|
||||
## [2.2.0](https://github.com/homarr-labs/homarr/compare/v2.1.2...v2.2.0) (2026-10-05)
|
||||
|
||||
## [2.1.2](https://github.com/homarr-labs/homarr/compare/v2.1.1...v2.1.2) (2026-10-04)
|
||||
|
||||
## [2.1.1](https://github.com/homarr-labs/homarr/compare/v2.1.0...v2.1.1) (2026-10-03)
|
||||
|
||||
## [2.1.0](https://github.com/homarr-labs/homarr/compare/v2.0.0...v2.1.0) (2026-10-03)
|
||||
|
||||
## [2.0.0](https://github.com/homarr-labs/homarr/compare/v1.77.2...v2.0.0) (2026-10-02)
|
||||
|
||||
## [1.77.1](https://github.com/homarr-labs/homarr/compare/v1.77.0...v1.77.1) (2026-09-11)
|
||||
|
||||
## [1.77.0](https://github.com/homarr-labs/homarr/compare/v1.76.2...v1.77.0) (2026-09-06)
|
||||
|
||||
## [1.76.2](https://github.com/homarr-labs/homarr/compare/v1.76.1...v1.76.2) (2026-08-27)
|
||||
|
||||
## [1.76.1](https://github.com/homarr-labs/homarr/compare/v1.76.0...v1.76.1) (2026-08-26)
|
||||
|
||||
## [1.76.0](https://github.com/homarr-labs/homarr/compare/v1.75.0...v1.76.0) (2026-08-21)
|
||||
|
||||
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
|
||||
|
||||
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
|
||||
|
||||
## [1.73.0](https://github.com/homarr-labs/homarr/compare/v1.72.0...v1.73.0) (2026-07-31)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
# ── CODEOWNERS ───────────────────────────────────────
|
||||
* @homarr-labs/maintainers
|
||||
|
||||
# Specific features / integrations
|
||||
# These owners come from the PR that introduced each integration implementation.
|
||||
# Specific rules override the global fallback because CODEOWNERS uses the last
|
||||
# matching pattern; do not include @homarr-labs/maintainers here unless they
|
||||
# should also be requested for that integration.
|
||||
|
||||
# AdGuard Home - introduced in https://github.com/homarr-labs/homarr/pull/929 by @hillaliy
|
||||
apps/docs/docs/integrations/adguard-home @hillaliy
|
||||
packages/integrations/src/adguard-home @hillaliy
|
||||
|
||||
# Anchor - introduced in https://github.com/homarr-labs/homarr/pull/5040 by @ohmzi
|
||||
apps/docs/docs/integrations/anchor @ohmzi
|
||||
packages/integrations/src/anchor @ohmzi
|
||||
|
||||
# ArchiveTeam Warrior - introduced in https://github.com/homarr-labs/homarr/pull/5784 by @darbyjack
|
||||
apps/docs/docs/integrations/archive-team-warrior @darbyjack
|
||||
packages/integrations/src/archive-team-warrior @darbyjack
|
||||
|
||||
# Bazarr - introduced in https://github.com/homarr-labs/homarr/pull/6156 by @Ayush7614
|
||||
apps/docs/docs/integrations/bazarr @Ayush7614
|
||||
apps/docs/docs/widgets/bazarr @Ayush7614
|
||||
packages/api/src/router/widgets/bazarr.ts @Ayush7614
|
||||
packages/integrations/src/bazarr @Ayush7614
|
||||
packages/request-handler/src/bazarr.ts @Ayush7614
|
||||
packages/widgets/src/bazarr @Ayush7614
|
||||
|
||||
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/audiobookshelf @ajnart
|
||||
packages/integrations/src/audiobookshelf @ajnart
|
||||
|
||||
# Beszel - introduced in https://github.com/homarr-labs/homarr/pull/5900 by @ajnart
|
||||
apps/docs/docs/integrations/beszel @ajnart
|
||||
packages/integrations/src/beszel @ajnart
|
||||
|
||||
# Codeberg - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/codeberg @Aandree5
|
||||
packages/integrations/src/codeberg @Aandree5
|
||||
|
||||
# Coolify - introduced in https://github.com/homarr-labs/homarr/pull/4789 by @Rhiz3K
|
||||
apps/docs/docs/integrations/coolify @Rhiz3K
|
||||
packages/integrations/src/coolify @Rhiz3K
|
||||
|
||||
# Dash. - introduced in https://github.com/homarr-labs/homarr/pull/1541 by @manuel-rw
|
||||
apps/docs/docs/integrations/dash-dot @manuel-rw
|
||||
packages/integrations/src/dashdot @manuel-rw
|
||||
|
||||
# Docker Hub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/docker-hub @Aandree5
|
||||
packages/integrations/src/docker-hub @Aandree5
|
||||
|
||||
# Emby - introduced in https://github.com/homarr-labs/homarr/pull/2466 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/emby @Meierschlumpf
|
||||
packages/integrations/src/emby @Meierschlumpf
|
||||
|
||||
# GitHub - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/github @Aandree5
|
||||
packages/integrations/src/github @Aandree5
|
||||
|
||||
# GitHub Container Registry - introduced in https://github.com/homarr-labs/homarr/pull/3727 by @Aandree5
|
||||
apps/docs/docs/integrations/github-containerregistry @Aandree5
|
||||
packages/integrations/src/github-container-registry @Aandree5
|
||||
|
||||
# GitLab - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/gitlab @Aandree5
|
||||
packages/integrations/src/gitlab @Aandree5
|
||||
|
||||
# Glances - introduced in https://github.com/homarr-labs/homarr/pull/5102 by @manuel-rw
|
||||
apps/docs/docs/integrations/glances @manuel-rw
|
||||
packages/integrations/src/glances @manuel-rw
|
||||
|
||||
# Gluetun - introduced in https://github.com/homarr-labs/homarr/pull/5812 by @Lok3sh-kumar
|
||||
apps/docs/docs/integrations/gluetun @Lok3sh-kumar
|
||||
packages/integrations/src/gluetun @Lok3sh-kumar
|
||||
|
||||
# Gotify - introduced in https://github.com/homarr-labs/homarr/pull/5847 by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/gotify @Nerdy-Technician
|
||||
packages/integrations/src/gotify @Nerdy-Technician
|
||||
|
||||
# Home Assistant - introduced in https://github.com/homarr-labs/homarr/pull/578 by @manuel-rw
|
||||
apps/docs/docs/integrations/home-assistant @manuel-rw
|
||||
packages/integrations/src/homeassistant @manuel-rw
|
||||
|
||||
# ICal - introduced in https://github.com/homarr-labs/homarr/pull/3980 by @NicolasNewman
|
||||
apps/docs/docs/integrations/ical @NicolasNewman
|
||||
packages/integrations/src/ical @NicolasNewman
|
||||
|
||||
# Immich - introduced in https://github.com/homarr-labs/homarr/pull/5165 by @manuel-rw
|
||||
apps/docs/docs/integrations/immich @manuel-rw
|
||||
packages/integrations/src/immich @manuel-rw
|
||||
|
||||
# Jellyfin - introduced in https://github.com/homarr-labs/homarr/pull/672 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyfin @manuel-rw
|
||||
packages/integrations/src/jellyfin @manuel-rw
|
||||
|
||||
# Jellyseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/jellyseerr @manuel-rw
|
||||
packages/integrations/src/jellyseerr @manuel-rw
|
||||
|
||||
# LinuxServer.io - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/linux-server-io @Aandree5
|
||||
packages/integrations/src/linuxserverio @Aandree5
|
||||
|
||||
# Navidrome - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/navidrome @ajnart
|
||||
packages/integrations/src/navidrome @ajnart
|
||||
|
||||
# Nextcloud - introduced in https://github.com/homarr-labs/homarr/pull/2501 by @manuel-rw
|
||||
apps/docs/docs/integrations/nextcloud @manuel-rw
|
||||
packages/integrations/src/nextcloud @manuel-rw
|
||||
|
||||
# npm - introduced in https://github.com/homarr-labs/homarr/pull/3253 by @Aandree5
|
||||
apps/docs/docs/integrations/npm @Aandree5
|
||||
packages/integrations/src/npm @Aandree5
|
||||
|
||||
# ntfy - introduced in https://github.com/homarr-labs/homarr/pull/2900 by @itzTheMeow
|
||||
apps/docs/docs/integrations/ntfy @ajnart @manuel-rw
|
||||
packages/integrations/src/ntfy @ajnart @manuel-rw
|
||||
|
||||
# OpenMediaVault - introduced in https://github.com/homarr-labs/homarr/pull/1142 by @hillaliy
|
||||
apps/docs/docs/integrations/open-media-vault @hillaliy
|
||||
packages/integrations/src/openmediavault @hillaliy
|
||||
|
||||
# OPNsense - introduced in https://github.com/homarr-labs/homarr/pull/3424 by @Oupsman
|
||||
apps/docs/docs/integrations/opnsense @Oupsman
|
||||
packages/integrations/src/opnsense @Oupsman
|
||||
|
||||
# Overseerr - introduced in https://github.com/homarr-labs/homarr/pull/774 by @manuel-rw
|
||||
apps/docs/docs/integrations/overseerr @manuel-rw
|
||||
packages/integrations/src/overseerr @manuel-rw
|
||||
|
||||
# Paperless-ngx - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/paperless-ngx @ajnart
|
||||
packages/integrations/src/paperless-ngx @ajnart
|
||||
|
||||
# PatchMon - introduced in https://github.com/homarr-labs/homarr/pull/6119 by @mrjoshuap
|
||||
apps/docs/docs/integrations/patchmon @mrjoshuap
|
||||
packages/integrations/src/patchmon @mrjoshuap
|
||||
packages/widgets/src/patchmon @mrjoshuap
|
||||
packages/api/src/router/widgets/patchmon.ts @mrjoshuap
|
||||
packages/request-handler/src/patchmon.ts @mrjoshuap
|
||||
|
||||
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
|
||||
apps/docs/docs/integrations/peanut @afonsojramos
|
||||
packages/integrations/src/peanut @afonsojramos
|
||||
|
||||
# Pi-hole - introduced in https://github.com/homarr-labs/homarr/pull/521 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/pi-hole @Meierschlumpf
|
||||
packages/integrations/src/pi-hole @Meierschlumpf
|
||||
|
||||
# Plex - introduced in https://github.com/homarr-labs/homarr/pull/1342 by @hillaliy
|
||||
apps/docs/docs/integrations/plex @hillaliy
|
||||
packages/integrations/src/plex @hillaliy
|
||||
|
||||
# Prowlarr - introduced in https://github.com/homarr-labs/homarr/pull/965 by @hillaliy
|
||||
apps/docs/docs/integrations/prowlarr @hillaliy
|
||||
packages/integrations/src/prowlarr @hillaliy
|
||||
|
||||
# Proxmox - introduced in https://github.com/homarr-labs/homarr/pull/1969 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/proxmox @Meierschlumpf
|
||||
packages/integrations/src/proxmox @Meierschlumpf
|
||||
|
||||
# Quay - introduced in https://github.com/homarr-labs/homarr/pull/3607 by @Aandree5
|
||||
apps/docs/docs/integrations/quay @Aandree5
|
||||
packages/integrations/src/quay @Aandree5
|
||||
|
||||
# search.ch - introduced in https://github.com/homarr-labs/homarr/pull/5180 by @Meierschlumpf
|
||||
apps/docs/docs/integrations/search-ch @Meierschlumpf
|
||||
packages/integrations/src/search-ch @Meierschlumpf
|
||||
|
||||
# Speedtest Tracker - introduced in https://github.com/homarr-labs/homarr/pull/5363 by @kylegrieder
|
||||
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
|
||||
packages/integrations/src/speedtest-tracker @kylegrieder
|
||||
|
||||
# Synology - introduced in https://github.com/homarr-labs/homarr/pull/6121 by @mrjoshuap
|
||||
apps/docs/docs/integrations/synology @mrjoshuap
|
||||
packages/integrations/src/synology @mrjoshuap
|
||||
|
||||
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
|
||||
apps/docs/docs/integrations/technitium-dns @lopsch
|
||||
packages/integrations/src/technitium @lopsch
|
||||
|
||||
# Tracearr - introduced in https://github.com/homarr-labs/homarr/pull/5060 by @StrandedTurtle
|
||||
apps/docs/docs/integrations/tracearr @StrandedTurtle
|
||||
packages/integrations/src/tracearr @StrandedTurtle
|
||||
|
||||
# TrueNAS - introduced in https://github.com/homarr-labs/homarr/pull/3745 by @manuel-rw
|
||||
apps/docs/docs/integrations/truenas @manuel-rw @afonsojramos
|
||||
packages/integrations/src/truenas @manuel-rw @afonsojramos
|
||||
|
||||
# Umami - introduced in https://github.com/homarr-labs/homarr/pull/5362 by @febrile42
|
||||
apps/docs/docs/integrations/umami @febrile42
|
||||
packages/integrations/src/umami @febrile42
|
||||
|
||||
# UniFi Controller - introduced in https://github.com/homarr-labs/homarr/pull/2236 by @pitschi
|
||||
apps/docs/docs/integrations/unifi-controller @pitschi
|
||||
packages/integrations/src/unifi-controller @pitschi
|
||||
|
||||
# Unraid - introduced in https://github.com/homarr-labs/homarr/pull/4439 by @manuel-rw
|
||||
apps/docs/docs/integrations/unraid @manuel-rw
|
||||
packages/integrations/src/unraid @manuel-rw
|
||||
|
||||
# Uptime Kuma - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/uptime-kuma @ajnart
|
||||
packages/integrations/src/uptime-kuma @ajnart
|
||||
|
||||
# What's Up Docker (WUD) - introduced in https://github.com/homarr-labs/homarr/pull/6523 by @tiimonn
|
||||
apps/docs/docs/integrations/whats-up-docker @tiimonn
|
||||
apps/docs/docs/widgets/whats-up-docker @tiimonn
|
||||
packages/api/src/router/widgets/wud.ts @tiimonn
|
||||
packages/integrations/src/wud @tiimonn
|
||||
packages/request-handler/src/wud.ts @tiimonn
|
||||
packages/widgets/src/wud @tiimonn
|
||||
|
||||
# Traefik - by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/traefik @Nerdy-Technician
|
||||
apps/docs/docs/widgets/traefik @Nerdy-Technician
|
||||
packages/api/src/router/widgets/traefik.ts @Nerdy-Technician
|
||||
packages/integrations/src/traefik @Nerdy-Technician
|
||||
packages/request-handler/src/traefik.ts @Nerdy-Technician
|
||||
packages/widgets/src/traefik @Nerdy-Technician
|
||||
|
||||
# Exempt Renovate-managed files (no owners)
|
||||
package.json
|
||||
**/package.json
|
||||
package-lock.json
|
||||
**/package-lock.json
|
||||
pnpm-lock.yaml
|
||||
**/pnpm-lock.yaml
|
||||
pnpm-workspace.yaml
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
@@ -1,62 +1,100 @@
|
||||
# syntax=docker/dockerfile:1.25
|
||||
|
||||
FROM oven/bun:1.4.2-alpine AS bun-tool
|
||||
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
ARG TARGETPLATFORM
|
||||
WORKDIR /app
|
||||
COPY --from=bun-tool /usr/local/bin/bun /usr/local/bin/bun
|
||||
# Compiler tools stay in the builder for optional native dependency fallbacks.
|
||||
RUN apk add --no-cache libstdc++ python3 make g++
|
||||
COPY bun.lock bunfig.toml package.json ./
|
||||
# Native dependencies normally use prebuilds, but the build must remain
|
||||
# reproducible when a prebuild download times out and compilation is required.
|
||||
RUN apk add --no-cache libc6-compat curl bash python3 make g++
|
||||
|
||||
RUN corepack enable pnpm
|
||||
COPY .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY patches ./patches
|
||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||
RUN --mount=type=cache,id=homarr-bun-cache,target=/root/.bun/install/cache,sharing=locked \
|
||||
npm_config_nodedir=/usr/local bun install --frozen-lockfile --concurrent-scripts=1
|
||||
# @homarr/definitions generates documentation types during install.
|
||||
COPY --parents ./packages/definitions/src ./
|
||||
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
|
||||
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
|
||||
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
|
||||
# content-addressable store is populated). By temporarily switching to the
|
||||
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
|
||||
# which IS created by pnpm fetch. The original hoisted linker is restored
|
||||
# before the install step so the final node_modules layout stays flat.
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
pnpm config set store-dir /pnpm/store && \
|
||||
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||
pnpm fetch --ignore-scripts && \
|
||||
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||
|
||||
# Install only from the fetched, committed lockfile so local and Docker builds
|
||||
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
||||
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store,sharing=locked \
|
||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
ARG DISABLE_REDIS_LOGS='true'
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
|
||||
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
|
||||
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
|
||||
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
|
||||
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
|
||||
--mount=type=cache,id=homarr-turbo-${TARGETPLATFORM},target=/app/.turbo,sharing=locked \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}/musl/node-24.18.0" \
|
||||
bun run turbo run build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
||||
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
|
||||
FROM alpine:3.24.1 AS runner
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=base /usr/local/bin/node /usr/local/bin/node
|
||||
# envsubst, privilege drop and AUTH_SECRET generation are used by the entrypoint.
|
||||
RUN apk add --no-cache libstdc++ ca-certificates redis nginx bash gettext su-exec openssl && \
|
||||
mkdir -p /appdata /var/cache/nginx /var/log/nginx /var/lib/nginx \
|
||||
/run/nginx /etc/nginx/templates /etc/nginx/ssl/certs && \
|
||||
touch /run/nginx/nginx.pid
|
||||
|
||||
# gettext is required for envsubst, openssl for generating AUTH_SECRET, su-exec for running application as non-root
|
||||
RUN apk add --no-cache redis nginx bash gettext su-exec openssl
|
||||
RUN mkdir /appdata
|
||||
VOLUME /appdata
|
||||
|
||||
# Enable homarr cli
|
||||
COPY --from=builder /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
|
||||
# Bundled CLI/migrations resolve the native binding from the application root.
|
||||
RUN echo $'#!/bin/bash\ncd /app/apps/cli && node ./cli.cjs "$@"' > /usr/bin/homarr
|
||||
RUN chmod +x /usr/bin/homarr
|
||||
|
||||
# Don't run production as root
|
||||
RUN mkdir -p /var/cache/nginx && \
|
||||
mkdir -p /var/log/nginx && \
|
||||
mkdir -p /var/lib/nginx && \
|
||||
touch /run/nginx/nginx.pid && \
|
||||
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs
|
||||
|
||||
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
||||
COPY --from=builder /app/apps/nextjs/package.json .
|
||||
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
|
||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
RUN printf '#!/bin/sh\ncd /app/apps/cli && exec node ./cli.cjs "$@"\n' > /usr/bin/homarr && \
|
||||
chmod +x /usr/bin/homarr
|
||||
|
||||
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
||||
# Ship only Next's traced production dependencies and application assets.
|
||||
COPY --from=builder /app/apps/nextjs/.output/standalone ./
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder /app/apps/nextjs/.next/standalone ./
|
||||
COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
|
||||
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
|
||||
COPY scripts/run.sh ./run.sh
|
||||
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
|
||||
COPY packages/redis/redis.conf /app/redis.conf
|
||||
COPY nginx.conf /etc/nginx/templates/nginx.conf
|
||||
|
||||
|
||||
ENV DB_URL='/appdata/db/db.sqlite'
|
||||
ENV DB_DIALECT='sqlite'
|
||||
ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
EXPOSE 7575
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -0,0 +1,475 @@
|
||||
# Mantine UI - LLM Documentation
|
||||
|
||||
This index lists Mantine documentation pages formatted for LLMs.
|
||||
Each link points to a standalone Markdown file under the /llms path.
|
||||
|
||||
For a single consolidated file with all content, use:
|
||||
|
||||
- https://mantine.dev/llms-full.txt
|
||||
|
||||
## FAQ
|
||||
|
||||
- [Are Mantine components accessible?](https://mantine.dev/llms/q-are-mantine-components-accessible.md): Learn about Mantine components accessibility features
|
||||
- [Browser zooms in when input is focused. What should I do?](https://mantine.dev/llms/q-browser-zooms-on-focus.md): Use meta tag to disable browser scaling or increase input size
|
||||
- [Can I have color schemes other than light and dark?](https://mantine.dev/llms/q-light-dark-is-not-enough.md): Learn about the difference between color scheme and theme
|
||||
- [Can I have different primary color for light and dark color schemes?](https://mantine.dev/llms/q-primary-virtual-color.md): Learn how to use virtual color with primary color in theme object
|
||||
- [Can I remove MultiSelect placeholder when the component has selected values?](https://mantine.dev/llms/q-multiselect-value-placeholder.md): Learn why MultiSelect placeholder is not removed when values are selected and how to remove it with CSS
|
||||
- [Can I use an array of strings as a list in use-form?](https://mantine.dev/llms/q-list-of-strings-in-use-form.md): Learn about use-form lists limitations
|
||||
- [Can I use Mantine components as server components?](https://mantine.dev/llms/q-server-components.md): Learn about use client directive and server components usage
|
||||
- [Can I use Mantine with Astro?](https://mantine.dev/llms/q-can-i-use-mantine-with-astro.md): No, Astro does not support React context
|
||||
- [Can I use Mantine with Create React App (CRA)?](https://mantine.dev/llms/q-can-i-use-mantine-with-cra.md): Learn how to use Mantine without postcss-preset-mantine or how to eject CRA and customize webpack.config.js and use postcss-preset-mantine
|
||||
- [Can I use Mantine with Emotion/styled-components/tailwindcss?](https://mantine.dev/llms/q-third-party-styles.md): Learn about limitations of third-party styles
|
||||
- [Can I use Mantine with Vue/Svelte/Angular/etc.?](https://mantine.dev/llms/q-vue-svelte-angular.md): No, Mantine is a React library and does not support other frameworks/libraries
|
||||
- [Can I use nested inline styles with Mantine components?](https://mantine.dev/llms/q-nested-inline-styles.md): Nested styles are supported only in CSS files
|
||||
- [Can I use PostCSS function in inline styles?](https://mantine.dev/llms/q-postcss-fns-inline.md): Learn where PostCSS functions can be used in Mantine
|
||||
- [Can I use private CSS variables to style components?](https://mantine.dev/llms/q-private-css-variables.md): No, it is not safe and will not work with future versions of Mantine.
|
||||
- [Can I use SegmentedControl with empty value?](https://mantine.dev/llms/q-segmented-control-no-value.md): SegmentedControl cannot be used without a value
|
||||
- [How can I add dynamic CSS styles?](https://mantine.dev/llms/q-dynamic-css-styles.md): Use data attributes, CSS variables or inline styles
|
||||
- [How can I add fuzzy search to Select component?](https://mantine.dev/llms/q-select-fuzzy.md): Learn how to integrate third-party fuzzy search libraries with Mantine Select component
|
||||
- [How can I add hover styles to an element?](https://mantine.dev/llms/q-how-to-add-hover-styles.md): Learn how to add hover classes to an element with CSS modules, &:hover or @mixin hover
|
||||
- [How can I apply styles to all Mantine components?](https://mantine.dev/llms/q-apply-styles-to-all.md): Learn how to use attribute selector to apply styles to all Mantine components
|
||||
- [How can I change body background color?](https://mantine.dev/llms/q-body-background.md): Use CSS to change body background color
|
||||
- [How can I change component color prop value depending on the color scheme?](https://mantine.dev/llms/q-color-scheme-color.md): Learn how to use CSS variables resolver to change color value depending on the color scheme
|
||||
- [How can I change input focus styles?](https://mantine.dev/llms/q-input-focus-styles.md): Learn how to use Styles API with Mantine inputs
|
||||
- [How can I change Tabs border color?](https://mantine.dev/llms/q-tabs-border-color.md): Learn how to use Styles API with Tabs component
|
||||
- [How can I contribute to the library?](https://mantine.dev/llms/q-how-can-i-contribute.md): There are multiple ways to contribute even without writing code
|
||||
- [How can I disable all inputs/input groups inside a form?](https://mantine.dev/llms/q-disable-all-inputs-in-form.md): Learn how to disable all inputs/input groups inside a form with the unstyled Fieldset component
|
||||
- [How can I display different elements in light and dark color schemes?](https://mantine.dev/llms/q-light-dark-elements.md): Learn how to hide/show elements based on color scheme
|
||||
- [How can I focus the first input with error with use-form?](https://mantine.dev/llms/q-focus-first-input-with-error.md): Learn how to handle focus with use-form hook
|
||||
- [How can I get current color scheme value in JavaScript?](https://mantine.dev/llms/q-how-to-get-color-scheme-value-in-js.md): How to use useMantineColorScheme and useComputedColorScheme hooks to get current color scheme value in JavaScript
|
||||
- [How can I lint CSS files?](https://mantine.dev/llms/q-how-to-setup-stylelint.md): Learn how to set up Stylelint for Mantine postcss syntax
|
||||
- [How can I load fonts in Next.js?](https://mantine.dev/llms/q-next-load-fonts.md): A guide to load custom fonts in Next.js with CSS and next/font package
|
||||
- [How can I load fonts in React Router?](https://mantine.dev/llms/q-react-router-load-fonts.md): A guide to load custom fonts in React Router
|
||||
- [How can I load fonts in Vite?](https://mantine.dev/llms/q-vite-load-fonts.md): A guide to load custom fonts in Vite
|
||||
- [How can I lock scroll in my application?](https://mantine.dev/llms/q-how-to-lock-scroll.md): Use react-remove-scroll library to lock scroll in your application
|
||||
- [How can I submit a template to Mantine documentation?](https://mantine.dev/llms/q-submit-template.md): Learn how to create and submit a template to Mantine documentation
|
||||
- [How can I test Modal/Drawer/Popover components?](https://mantine.dev/llms/q-portals-testing.md): Learn how to use react-testing-library to test components that use portals and transitions.
|
||||
- [How can I test Select/MultiSelect components?](https://mantine.dev/llms/q-combobox-testing.md): Learn how to use react-testing-library to test Select and MultiSelect components.
|
||||
- [How can I upload files from Dropzone component?](https://mantine.dev/llms/q-dropzone-upload.md): Learn how to process files dropped into Dropzone component
|
||||
- [How is that thing done on the mantine.dev website?](https://mantine.dev/llms/q-how-that-thing-is-done.md): Learn how various elements are implemented on the mantine.dev website
|
||||
- [How to align input with a button in a flex container?](https://mantine.dev/llms/q-align-input-button.md): Learn how to align Mantine inputs with buttons using Group component
|
||||
- [How to call a function when Modal/Drawer closes and animation completes?](https://mantine.dev/llms/q-how-to-call-function-when-modal-closes.md): How to use transitionProps in Modal/Drawer components
|
||||
- [How to change background color for FiveM?](https://mantine.dev/llms/q-fivem-background-color.md): Unset body background color and color-scheme styles for FiveM
|
||||
- [How to change input placeholder color?](https://mantine.dev/llms/q-inputs-placeholder-color.md): Learn how to change placeholder color with Styles API
|
||||
- [How to integrate custom input with use-form hook?](https://mantine.dev/llms/q-custom-input-use-form.md): Learn how to add use-form support for custom inputs
|
||||
- [How to prevent Modal from closing?](https://mantine.dev/llms/q-how-to-prevent-modal-from-closing.md): Learn how to prevent Modal from closing when the user clicks outside of it or presses the Escape key
|
||||
- [How to scroll to the top of the form if the form is submitted with errors?](https://mantine.dev/llms/q-scroll-to-the-top-of-the-form.md): Learn how to call a function when the form is submitted with errors
|
||||
- [How to update Mantine dependencies?](https://mantine.dev/llms/q-how-to-update-dependencies.md): Learn how to update `@mantine/*`, `@mantinex/*` and `postcss-preset-mantine` dependencies manually or with ncu script
|
||||
- [How to use Dropzone with @mantine/form?](https://mantine.dev/llms/q-how-to-use-dropzone-with-form.md): Learn how to use Dropzone with @mantine/form to handle file selection state
|
||||
- [How to use Mantine template on GitHub?](https://mantine.dev/llms/q-templates-usage.md): Learn how to create a new repository based on Mantine template
|
||||
- [I get hydration warning about data-mantine-color-scheme attribute, what does it mean?](https://mantine.dev/llms/q-color-scheme-hydration-warning.md): Learn how hydration works and how to fix warnings
|
||||
- [Is there a comparison with other libraries?](https://mantine.dev/llms/q-other-libs.md): Comparison with other libraries is not provided, you are trusted to make your own decision based on documentation examples
|
||||
- [Is there a floating action button component?](https://mantine.dev/llms/q-floating-action-button.md): No, but you can build it with Affix and ActionIcon components
|
||||
- [Is there DataGrid component that I can use with Mantine?](https://mantine.dev/llms/q-data-grid-i-need.md): A list of community packages that provide DataGrid component that can be used with Mantine.
|
||||
- [It is not possible to pinch to zoom when a Modal is opened. What should I do?](https://mantine.dev/llms/q-pinch-to-zoom-modal.md): Use removeScrollProps to configure react-remove-scroll options
|
||||
- [MantineProvider was not found in component tree. What should I do?](https://mantine.dev/llms/q-mantine-provider-missing.md): Learn how to resolve MantineProvider issues
|
||||
- [My buttons are transparent and the background is visible only on hover, what is wrong?](https://mantine.dev/llms/q-transparent-buttons.md): You have installed a third-party library that overrides Mantine styles
|
||||
- [My Popover dropdown closes when I click on the dropdown of a nested Popover](https://mantine.dev/llms/q-nested-popover-closes.md): Popover dropdown is closed when it detects click-outside events
|
||||
- [My styles are broken with disabled JavaScript. What should I do?](https://mantine.dev/llms/q-disabled-js.md): Learn how to use Mantine without JavaScript enabled
|
||||
- [My styles are overridden by Mantine components styles, what should I do?](https://mantine.dev/llms/q-styles-order.md): Learn how to use CSS layers to control styles order and prevent Mantine components from overriding your styles
|
||||
- [Native browser validation does not work in some components, what should I do?](https://mantine.dev/llms/q-native-required.md): Learn why native browser validation does not work in some components
|
||||
- [The use-local-storage hook returns the real value only after mounting. Is it a bug?](https://mantine.dev/llms/q-local-storage-effect.md): Learn how Mantine retrieves local storage value
|
||||
- [What is the difference between searchable Select and Autocomplete?](https://mantine.dev/llms/q-select-autocomplete-difference.md): Searchable Select and Autocomplete are similar components, but they serve different purposes.
|
||||
- [Where can I find the roadmap?](https://mantine.dev/llms/q-roadmap.md): The roadmap is private and only available to the team.
|
||||
- [Why are my Carousel slides in vertical orientation?](https://mantine.dev/llms/q-carousel-missing-styles.md): You forgot to import carousel styles
|
||||
- [Why are my notifications displayed at the wrong position?](https://mantine.dev/llms/q-notifications-missing-styles.md): Because you did not import styles
|
||||
- [Why can I not use one polymorphic component in the component prop of another polymorphic component?](https://mantine.dev/llms/q-polymorphic-in-polymorphic.md): Learn how polymorphic components types work
|
||||
- [Why can I not use value/label data structure with Autocomplete/TagsInput?](https://mantine.dev/llms/q-autocomplete-value-label.md): Learn the difference between Autocomplete and Select components
|
||||
- [Why can VSCode not autoimport the Text component?](https://mantine.dev/llms/q-why-vscode-cannot-autoimport-text.md): It is confused by the native Text constructor
|
||||
- [Why do I see color scheme flickering on page load?](https://mantine.dev/llms/q-color-scheme-flickering.md): Color scheme flickering is caused by incorrect usage of ColorSchemeScript
|
||||
- [Why do my dates components look broken?](https://mantine.dev/llms/q-dates-missing-styles.md): Because you did not import styles
|
||||
- [Why does FileButton not work in Menu?](https://mantine.dev/llms/q-file-button-in-menu.md): Learn how to use FileButton in Menu component
|
||||
- [Why is it required to have 10 shades per color?](https://mantine.dev/llms/q-ten-shades-per-color.md): Learn how Mantine uses theme colors
|
||||
- [Why is my screen completely empty after I](https://mantine.dev/llms/q-notifications-empty-screen.md): You have used Notifications component incorrectly
|
||||
|
||||
## Core
|
||||
|
||||
- [Accordion](https://mantine.dev/llms/core-accordion.md): Divide content into collapsible sections
|
||||
- [ActionIcon](https://mantine.dev/llms/core-action-icon.md): Icon button
|
||||
- [Affix](https://mantine.dev/llms/core-affix.md): Renders children inside portal at fixed position
|
||||
- [Alert](https://mantine.dev/llms/core-alert.md): Attract user attention with important static message
|
||||
- [AlphaSlider](https://mantine.dev/llms/core-alpha-slider.md): Slider component for selecting alpha channel in color pickers (0 – 1)
|
||||
- [Anchor](https://mantine.dev/llms/core-anchor.md): Display link with theme styles
|
||||
- [AngleSlider](https://mantine.dev/llms/core-angle-slider.md): Pick angle value between 0 and 360
|
||||
- [AppShell](https://mantine.dev/llms/core-app-shell.md): Responsive shell for your application with header, navbar, aside and footer
|
||||
- [AspectRatio](https://mantine.dev/llms/core-aspect-ratio.md): Maintain responsive consistent width/height ratio
|
||||
- [Autocomplete](https://mantine.dev/llms/core-autocomplete.md): Autocomplete user input with any list of options
|
||||
- [Avatar](https://mantine.dev/llms/core-avatar.md): Display user profile image, initials or fallback icon
|
||||
- [BackgroundImage](https://mantine.dev/llms/core-background-image.md): Displays image as background
|
||||
- [Badge](https://mantine.dev/llms/core-badge.md): Display badge, pill or tag
|
||||
- [Blockquote](https://mantine.dev/llms/core-blockquote.md): Blockquote with optional cite
|
||||
- [Box](https://mantine.dev/llms/core-box.md): Base component for all Mantine components
|
||||
- [Breadcrumbs](https://mantine.dev/llms/core-breadcrumbs.md): Separates list of react nodes with given separator
|
||||
- [Burger](https://mantine.dev/llms/core-burger.md): Open/close navigation button
|
||||
- [Button](https://mantine.dev/llms/core-button.md): Button component to render button or link
|
||||
- [Card](https://mantine.dev/llms/core-card.md): Card with sections
|
||||
- [Cascader](https://mantine.dev/llms/core-cascader.md): Select a path from hierarchical data with cascading columns
|
||||
- [Center](https://mantine.dev/llms/core-center.md): Centers content vertically and horizontally
|
||||
- [Checkbox](https://mantine.dev/llms/core-checkbox.md): Capture boolean input from user
|
||||
- [Chip](https://mantine.dev/llms/core-chip.md): Pick one or multiple values with inline controls
|
||||
- [CloseButton](https://mantine.dev/llms/core-close-button.md): Button with close icon
|
||||
- [Code](https://mantine.dev/llms/core-code.md): Inline and block code
|
||||
- [Collapse](https://mantine.dev/llms/core-collapse.md): Animate presence with slide down/up transition
|
||||
- [ColorInput](https://mantine.dev/llms/core-color-input.md): Capture color from user
|
||||
- [ColorPicker](https://mantine.dev/llms/core-color-picker.md): Pick colors in hex(a), rgb(a), hsl(a) and hsv(a) formats
|
||||
- [ColorSwatch](https://mantine.dev/llms/core-color-swatch.md): Displays color
|
||||
- [Combobox](https://mantine.dev/llms/core-combobox.md): Create custom select, autocomplete or multiselect inputs
|
||||
- [ComboboxPopover](https://mantine.dev/llms/core-combobox-popover.md): Add combobox dropdown to any button element
|
||||
- [Container](https://mantine.dev/llms/core-container.md): Center content with padding and max-width
|
||||
- [CopyButton](https://mantine.dev/llms/core-copy-button.md): Copies given text to clipboard
|
||||
- [CorePackage](https://mantine.dev/llms/core-package.md)
|
||||
- [DataList](https://mantine.dev/llms/core-data-list.md): Display label-value pairs as a description list
|
||||
- [Dialog](https://mantine.dev/llms/core-dialog.md): Display a fixed overlay dialog at any side of the screen
|
||||
- [Divider](https://mantine.dev/llms/core-divider.md): Horizontal line with optional label or vertical divider
|
||||
- [Drawer](https://mantine.dev/llms/core-drawer.md): Display overlay area at any side of the screen
|
||||
- [EmptyState](https://mantine.dev/llms/core-empty-state.md): Display a placeholder for no data, empty results and first-run states
|
||||
- [Fieldset](https://mantine.dev/llms/core-fieldset.md): Group related elements in a form
|
||||
- [FileButton](https://mantine.dev/llms/core-file-button.md): Open file picker with a button click
|
||||
- [FileInput](https://mantine.dev/llms/core-file-input.md): Capture files from user
|
||||
- [Flex](https://mantine.dev/llms/core-flex.md): Compose elements in a flex container
|
||||
- [FloatingIndicator](https://mantine.dev/llms/core-floating-indicator.md): Display a floating indicator over a group of elements
|
||||
- [FloatingWindow](https://mantine.dev/llms/core-floating-window.md): Draggable floating area
|
||||
- [FocusTrap](https://mantine.dev/llms/core-focus-trap.md): Trap focus at child node
|
||||
- [Grid](https://mantine.dev/llms/core-grid.md): Responsive 12 columns grid system
|
||||
- [Group](https://mantine.dev/llms/core-group.md): Compose elements and components in a horizontal flex container
|
||||
- [Highlight](https://mantine.dev/llms/core-highlight.md): Highlight given part of a string with mark
|
||||
- [HoverCard](https://mantine.dev/llms/core-hover-card.md): Display popover section when target element is hovered
|
||||
- [HueSlider](https://mantine.dev/llms/core-hue-slider.md): Slider component for selecting hue channel in color pickers (0 – 360 degrees)
|
||||
- [Image](https://mantine.dev/llms/core-image.md): Image with optional fallback
|
||||
- [Indicator](https://mantine.dev/llms/core-indicator.md): Display element at the corner of another element
|
||||
- [Input](https://mantine.dev/llms/core-input.md): Base component to create custom inputs
|
||||
- [JsonInput](https://mantine.dev/llms/core-json-input.md): Capture json data from user
|
||||
- [Kbd](https://mantine.dev/llms/core-kbd.md): Display keyboard key
|
||||
- [List](https://mantine.dev/llms/core-list.md): Display ordered or unordered list
|
||||
- [Loader](https://mantine.dev/llms/core-loader.md): Indicate loading state
|
||||
- [LoadingOverlay](https://mantine.dev/llms/core-loading-overlay.md): An overlay with centered loader
|
||||
- [Mark](https://mantine.dev/llms/core-mark.md): Highlight part of the text
|
||||
- [Marquee](https://mantine.dev/llms/core-marquee.md): Create continuous scrolling animation for content
|
||||
- [MaskInput](https://mantine.dev/llms/core-mask-input.md): Input with mask pattern for formatted text entry
|
||||
- [Menu](https://mantine.dev/llms/core-menu.md): Combine a list of secondary actions into single interactive area
|
||||
- [Menubar](https://mantine.dev/llms/core-menubar.md): Desktop application style menubar with a row of menu triggers
|
||||
- [Modal](https://mantine.dev/llms/core-modal.md): An accessible overlay dialog
|
||||
- [MultiSelect](https://mantine.dev/llms/core-multi-select.md): Custom searchable multi select
|
||||
- [NativeSelect](https://mantine.dev/llms/core-native-select.md): Native select element based on Input
|
||||
- [NavLink](https://mantine.dev/llms/core-nav-link.md): Navigation link
|
||||
- [Notification](https://mantine.dev/llms/core-notification.md): Show dynamic notifications and alerts to user, part of notifications system
|
||||
- [NumberFormatter](https://mantine.dev/llms/core-number-formatter.md): Format number with thousands/decimal separators and suffix/prefix
|
||||
- [NumberInput](https://mantine.dev/llms/core-number-input.md): Capture number from user
|
||||
- [OverflowList](https://mantine.dev/llms/core-overflow-list.md): Hide items that do not fit in a container and display them as a single collapsed item
|
||||
- [Overlay](https://mantine.dev/llms/core-overlay.md): Overlays parent element with div element with any color and opacity
|
||||
- [Pagination](https://mantine.dev/llms/core-pagination.md): Display active page and navigate between multiple pages
|
||||
- [Paper](https://mantine.dev/llms/core-paper.md): Renders white or dark background depending on color scheme
|
||||
- [PasswordInput](https://mantine.dev/llms/core-password-input.md): Capture password data from user
|
||||
- [Pill](https://mantine.dev/llms/core-pill.md): Removable and non-removable tags
|
||||
- [PillsInput](https://mantine.dev/llms/core-pills-input.md): Base component for custom tags inputs and multi selects
|
||||
- [PinInput](https://mantine.dev/llms/core-pin-input.md): Capture pin code or one time password from the user
|
||||
- [Popover](https://mantine.dev/llms/core-popover.md): Display popover section relative to given target element
|
||||
- [Portal](https://mantine.dev/llms/core-portal.md): Renders component outside of parent element tree
|
||||
- [Progress](https://mantine.dev/llms/core-progress.md): Give user feedback for status of the task
|
||||
- [Radio](https://mantine.dev/llms/core-radio.md): Wrapper for input type radio
|
||||
- [RangeSlider](https://mantine.dev/llms/core-range-slider.md): RangeSlider component
|
||||
- [Rating](https://mantine.dev/llms/core-rating.md): Pick and display rating
|
||||
- [RingProgress](https://mantine.dev/llms/core-ring-progress.md): Give user feedback for status of the task with circle diagram
|
||||
- [RollingNumber](https://mantine.dev/llms/core-rolling-number.md): Animate value changes with rolling digits
|
||||
- [ScrollArea](https://mantine.dev/llms/core-scroll-area.md): Area with custom scrollbars
|
||||
- [Scroller](https://mantine.dev/llms/core-scroller.md): Horizontal scroll container with navigation controls
|
||||
- [SegmentedControl](https://mantine.dev/llms/core-segmented-control.md): A linear set of two or more segments
|
||||
- [Select](https://mantine.dev/llms/core-select.md): Custom searchable select
|
||||
- [SemiCircleProgress](https://mantine.dev/llms/core-semi-circle-progress.md): Represent progress with semi circle diagram
|
||||
- [SimpleGrid](https://mantine.dev/llms/core-simple-grid.md): Responsive grid in which each item takes equal amount of space
|
||||
- [Skeleton](https://mantine.dev/llms/core-skeleton.md): Indicate content loading state
|
||||
- [Slider](https://mantine.dev/llms/core-slider.md): Slider component
|
||||
- [Space](https://mantine.dev/llms/core-space.md): Add horizontal or vertical spacing from theme
|
||||
- [Splitter](https://mantine.dev/llms/core-splitter.md): Resizable split pane layout
|
||||
- [Spoiler](https://mantine.dev/llms/core-spoiler.md): Hide long sections of content under a spoiler
|
||||
- [Stack](https://mantine.dev/llms/core-stack.md): Compose elements and components in a vertical flex container
|
||||
- [Stepper](https://mantine.dev/llms/core-stepper.md): Display content divided into a steps sequence
|
||||
- [Switch](https://mantine.dev/llms/core-switch.md): Capture boolean input from user
|
||||
- [Table](https://mantine.dev/llms/core-table.md): Render table with theme styles
|
||||
- [TableOfContents](https://mantine.dev/llms/core-table-of-contents.md): Renders a list of headings on the page and tracks current heading visible in the viewport
|
||||
- [Tabs](https://mantine.dev/llms/core-tabs.md): Switch between different views
|
||||
- [TagsInput](https://mantine.dev/llms/core-tags-input.md): Capture a list of values from user with free input and suggestions
|
||||
- [Text](https://mantine.dev/llms/core-text.md): Display text
|
||||
- [Textarea](https://mantine.dev/llms/core-textarea.md): Autosize or regular textarea
|
||||
- [TextInput](https://mantine.dev/llms/core-text-input.md): Capture string input from user
|
||||
- [ThemeIcon](https://mantine.dev/llms/core-theme-icon.md): Render icon inside element with theme colors
|
||||
- [Timeline](https://mantine.dev/llms/core-timeline.md): Display list of events in chronological order
|
||||
- [Title](https://mantine.dev/llms/core-title.md): h1-h6 heading
|
||||
- [Tooltip](https://mantine.dev/llms/core-tooltip.md): Renders tooltip at given element on mouse over or other event
|
||||
- [Transition](https://mantine.dev/llms/core-transition.md): Animate presence of component with pre-made animations
|
||||
- [Tree](https://mantine.dev/llms/core-tree.md): Display a Tree structure
|
||||
- [TreeSelect](https://mantine.dev/llms/core-tree-select.md): Select with hierarchical tree data
|
||||
- [Typography](https://mantine.dev/llms/core-typography.md): Styles provider for html content
|
||||
- [UnstyledButton](https://mantine.dev/llms/core-unstyled-button.md): Unstyled polymorphic button
|
||||
- [VisuallyHidden](https://mantine.dev/llms/core-visually-hidden.md): Hide element visually but keep it accessible for screen readers
|
||||
|
||||
## Hooks
|
||||
|
||||
- [HooksPackage](https://mantine.dev/llms/hooks-package.md)
|
||||
- [use-element-size](https://mantine.dev/llms/hooks-use-element-size.md): Returns element width and height and observes changes with ResizeObserver
|
||||
- [useClickOutside](https://mantine.dev/llms/hooks-use-click-outside.md)
|
||||
- [useClipboard](https://mantine.dev/llms/hooks-use-clipboard.md)
|
||||
- [useCollapse](https://mantine.dev/llms/hooks-use-collapse.md)
|
||||
- [useColorScheme](https://mantine.dev/llms/hooks-use-color-scheme.md)
|
||||
- [useCounter](https://mantine.dev/llms/hooks-use-counter.md)
|
||||
- [useDebouncedCallback](https://mantine.dev/llms/hooks-use-debounced-callback.md)
|
||||
- [useDebouncedState](https://mantine.dev/llms/hooks-use-debounced-state.md)
|
||||
- [useDebouncedValue](https://mantine.dev/llms/hooks-use-debounced-value.md)
|
||||
- [useDidUpdate](https://mantine.dev/llms/hooks-use-did-update.md)
|
||||
- [useDisclosure](https://mantine.dev/llms/hooks-use-disclosure.md)
|
||||
- [useDocumentTitle](https://mantine.dev/llms/hooks-use-document-title.md)
|
||||
- [useDocumentVisibility](https://mantine.dev/llms/hooks-use-document-visibility.md)
|
||||
- [useDrag](https://mantine.dev/llms/hooks-use-drag.md)
|
||||
- [useEventListener](https://mantine.dev/llms/hooks-use-event-listener.md)
|
||||
- [useEyeDropper](https://mantine.dev/llms/hooks-use-eye-dropper.md)
|
||||
- [useFavicon](https://mantine.dev/llms/hooks-use-favicon.md)
|
||||
- [useFetch](https://mantine.dev/llms/hooks-use-fetch.md)
|
||||
- [useFileDialog](https://mantine.dev/llms/hooks-use-file-dialog.md)
|
||||
- [useFloatingWindow](https://mantine.dev/llms/hooks-use-floating-window.md)
|
||||
- [useFocusReturn](https://mantine.dev/llms/hooks-use-focus-return.md)
|
||||
- [useFocusTrap](https://mantine.dev/llms/hooks-use-focus-trap.md)
|
||||
- [useFocusWithin](https://mantine.dev/llms/hooks-use-focus-within.md)
|
||||
- [useForceUpdate](https://mantine.dev/llms/hooks-use-force-update.md)
|
||||
- [useFullscreen](https://mantine.dev/llms/hooks-use-fullscreen.md)
|
||||
- [useHash](https://mantine.dev/llms/hooks-use-hash.md)
|
||||
- [useHeadroom](https://mantine.dev/llms/hooks-use-headroom.md)
|
||||
- [useHotkeys](https://mantine.dev/llms/hooks-use-hotkeys.md)
|
||||
- [useHover](https://mantine.dev/llms/hooks-use-hover.md)
|
||||
- [useId](https://mantine.dev/llms/hooks-use-id.md)
|
||||
- [useIdle](https://mantine.dev/llms/hooks-use-idle.md)
|
||||
- [useInputState](https://mantine.dev/llms/hooks-use-input-state.md)
|
||||
- [useIntersection](https://mantine.dev/llms/hooks-use-intersection.md)
|
||||
- [useInterval](https://mantine.dev/llms/hooks-use-interval.md)
|
||||
- [useInViewport](https://mantine.dev/llms/hooks-use-in-viewport.md)
|
||||
- [useIsFirstRender](https://mantine.dev/llms/hooks-use-is-first-render.md)
|
||||
- [useIsomorphicEffect](https://mantine.dev/llms/hooks-use-isomorphic-effect.md)
|
||||
- [useListState](https://mantine.dev/llms/hooks-use-list-state.md)
|
||||
- [useLocalStorage](https://mantine.dev/llms/hooks-use-local-storage.md)
|
||||
- [useLogger](https://mantine.dev/llms/hooks-use-logger.md)
|
||||
- [useLongPress](https://mantine.dev/llms/hooks-use-long-press.md)
|
||||
- [useMap](https://mantine.dev/llms/hooks-use-map.md)
|
||||
- [useMask](https://mantine.dev/llms/hooks-use-mask.md)
|
||||
- [useMediaQuery](https://mantine.dev/llms/hooks-use-media-query.md)
|
||||
- [useMergedRef](https://mantine.dev/llms/hooks-use-merged-ref.md)
|
||||
- [useMounted](https://mantine.dev/llms/hooks-use-mounted.md)
|
||||
- [useMouse](https://mantine.dev/llms/hooks-use-mouse.md)
|
||||
- [useMove](https://mantine.dev/llms/hooks-use-move.md)
|
||||
- [useMutationObserver](https://mantine.dev/llms/hooks-use-mutation-observer.md)
|
||||
- [useNetwork](https://mantine.dev/llms/hooks-use-network.md)
|
||||
- [useOrientation](https://mantine.dev/llms/hooks-use-orientation.md)
|
||||
- [useOs](https://mantine.dev/llms/hooks-use-os.md)
|
||||
- [usePageLeave](https://mantine.dev/llms/hooks-use-page-leave.md)
|
||||
- [usePagination](https://mantine.dev/llms/hooks-use-pagination.md)
|
||||
- [usePrevious](https://mantine.dev/llms/hooks-use-previous.md)
|
||||
- [useQueue](https://mantine.dev/llms/hooks-use-queue.md)
|
||||
- [useRadialMove](https://mantine.dev/llms/hooks-use-radial-move.md)
|
||||
- [useReducedMotion](https://mantine.dev/llms/hooks-use-reduced-motion.md)
|
||||
- [useResizeObserver](https://mantine.dev/llms/hooks-use-resize-observer.md)
|
||||
- [useRovingIndex](https://mantine.dev/llms/hooks-use-roving-index.md)
|
||||
- [useScrollDirection](https://mantine.dev/llms/hooks-use-scroll-direction.md)
|
||||
- [useScroller](https://mantine.dev/llms/hooks-use-scroller.md)
|
||||
- [useScrollIntoView](https://mantine.dev/llms/hooks-use-scroll-into-view.md)
|
||||
- [useScrollSpy](https://mantine.dev/llms/hooks-use-scroll-spy.md)
|
||||
- [useSelection](https://mantine.dev/llms/hooks-use-selection.md)
|
||||
- [useSet](https://mantine.dev/llms/hooks-use-set.md)
|
||||
- [useSetState](https://mantine.dev/llms/hooks-use-set-state.md)
|
||||
- [useShallowEffect](https://mantine.dev/llms/hooks-use-shallow-effect.md)
|
||||
- [useSplitter](https://mantine.dev/llms/hooks-use-splitter.md)
|
||||
- [useStateHistory](https://mantine.dev/llms/hooks-use-state-history.md)
|
||||
- [useTextSelection](https://mantine.dev/llms/hooks-use-text-selection.md)
|
||||
- [useThrottledCallback](https://mantine.dev/llms/hooks-use-throttled-callback.md)
|
||||
- [useThrottledState](https://mantine.dev/llms/hooks-use-throttled-state.md)
|
||||
- [useThrottledValue](https://mantine.dev/llms/hooks-use-throttled-value.md)
|
||||
- [useTimeout](https://mantine.dev/llms/hooks-use-timeout.md)
|
||||
- [useToggle](https://mantine.dev/llms/hooks-use-toggle.md)
|
||||
- [useUncontrolled](https://mantine.dev/llms/hooks-use-uncontrolled.md)
|
||||
- [useValidatedState](https://mantine.dev/llms/hooks-use-validated-state.md)
|
||||
- [useViewportSize](https://mantine.dev/llms/hooks-use-viewport-size.md)
|
||||
- [useWindowEvent](https://mantine.dev/llms/hooks-use-window-event.md)
|
||||
- [useWindowScroll](https://mantine.dev/llms/hooks-use-window-scroll.md)
|
||||
|
||||
## Form
|
||||
|
||||
- [Form context](https://mantine.dev/llms/form-create-form-context.md): Add context support to use-form with createFormContext
|
||||
- [formActions](https://mantine.dev/llms/form-actions.md)
|
||||
- [formAllInputs](https://mantine.dev/llms/form-all-inputs.md)
|
||||
- [formErrors](https://mantine.dev/llms/form-errors.md)
|
||||
- [formGetInputProps](https://mantine.dev/llms/form-get-input-props.md)
|
||||
- [formNested](https://mantine.dev/llms/form-nested.md)
|
||||
- [FormPackage](https://mantine.dev/llms/form-package.md)
|
||||
- [formRecipes](https://mantine.dev/llms/form-recipes.md)
|
||||
- [formSchemaValidation](https://mantine.dev/llms/form-schema-validation.md)
|
||||
- [formStatus](https://mantine.dev/llms/form-status.md)
|
||||
- [formUncontrolled](https://mantine.dev/llms/form-uncontrolled.md)
|
||||
- [formValidation](https://mantine.dev/llms/form-validation.md)
|
||||
- [formValidators](https://mantine.dev/llms/form-validators.md)
|
||||
- [formValues](https://mantine.dev/llms/form-values.md)
|
||||
- [use-field](https://mantine.dev/llms/form-use-field.md): use-field hook – manage single field state
|
||||
- [use-form](https://mantine.dev/llms/form-use-form.md): Manage form state
|
||||
|
||||
## Dates
|
||||
|
||||
- [Calendar](https://mantine.dev/llms/dates-calendar.md): Base component for custom date pickers
|
||||
- [DateInput](https://mantine.dev/llms/dates-date-input.md): Free form date input
|
||||
- [DatePicker](https://mantine.dev/llms/dates-date-picker.md): Inline date, multiple dates and dates range picker
|
||||
- [DatePickerInput](https://mantine.dev/llms/dates-date-picker-input.md): Date, multiple dates and dates range picker input
|
||||
- [DateTimePicker](https://mantine.dev/llms/dates-date-time-picker.md): Capture datetime from the user
|
||||
- [GettingStartedDates](https://mantine.dev/llms/dates-getting-started.md)
|
||||
- [InlineDateTimePicker](https://mantine.dev/llms/dates-inline-date-time-picker.md): Inline date and time picker with range support
|
||||
- [MiniCalendar](https://mantine.dev/llms/dates-mini-calendar.md): Compact calendar to display a small number of days in a row
|
||||
- [MonthPicker](https://mantine.dev/llms/dates-month-picker.md): Inline month, multiple months and months range picker
|
||||
- [MonthPickerInput](https://mantine.dev/llms/dates-month-picker-input.md): Month, multiple months and months range picker input
|
||||
- [TimeGrid](https://mantine.dev/llms/dates-time-grid.md): Captures time value from the user with a predefined set of options
|
||||
- [TimeInput](https://mantine.dev/llms/dates-time-input.md): Capture time from the user
|
||||
- [TimePicker](https://mantine.dev/llms/dates-time-picker.md): Captures time value from the user
|
||||
- [TimeValue](https://mantine.dev/llms/dates-time-value.md): Display a formatted time value
|
||||
- [YearPicker](https://mantine.dev/llms/dates-year-picker.md): Inline year, multiple years and years range picker
|
||||
- [YearPickerInput](https://mantine.dev/llms/dates-year-picker-input.md): Inline year, multiple years and years range picker
|
||||
|
||||
## Charts
|
||||
|
||||
- [AreaChart](https://mantine.dev/llms/charts-area-chart.md): Area chart component with stacked, percent and split variants
|
||||
- [BarChart](https://mantine.dev/llms/charts-bar-chart.md): Bar chart component with stacked and percent variants
|
||||
- [BarsList](https://mantine.dev/llms/charts-bars-list.md): Display a list of bars with names and values
|
||||
- [BubbleChart](https://mantine.dev/llms/charts-bubble-chart.md): Bubble chart component
|
||||
- [BulletChart](https://mantine.dev/llms/charts-bullet-chart.md): Compact KPI chart with value, target, and qualitative ranges
|
||||
- [CompositeChart](https://mantine.dev/llms/charts-composite-chart.md): Composed chart with support for Area, Bar and Line charts
|
||||
- [DonutChart](https://mantine.dev/llms/charts-donut-chart.md): Donut chart component
|
||||
- [FunnelChart](https://mantine.dev/llms/charts-funnel-chart.md): Funnel chart component
|
||||
- [GettingStartedCharts](https://mantine.dev/llms/charts-getting-started.md)
|
||||
- [Heatmap](https://mantine.dev/llms/charts-heatmap.md): Heatmap chart component
|
||||
- [LineChart](https://mantine.dev/llms/charts-line-chart.md): Line chart component
|
||||
- [PieChart](https://mantine.dev/llms/charts-pie-chart.md): Pie chart component
|
||||
- [RadarChart](https://mantine.dev/llms/charts-radar-chart.md): Radar chart component
|
||||
- [RadialBarChart](https://mantine.dev/llms/charts-radial-bar-chart.md): Radial bar chart component
|
||||
- [SankeyChart](https://mantine.dev/llms/charts-sankey-chart.md): Sankey diagram component
|
||||
- [ScatterChart](https://mantine.dev/llms/charts-scatter-chart.md): Scatter chart component
|
||||
- [Sparkline](https://mantine.dev/llms/charts-sparkline.md): Simplified area chart to show trends
|
||||
- [SunburstChart](https://mantine.dev/llms/charts-sunburst-chart.md): Sunburst chart component for hierarchical data
|
||||
- [Treemap](https://mantine.dev/llms/charts-treemap.md): Treemap chart component
|
||||
|
||||
## Schedule
|
||||
|
||||
- [AgendaView](https://mantine.dev/llms/schedule-agenda-view.md): Vertical list view of events grouped by date
|
||||
- [DayView](https://mantine.dev/llms/schedule-day-view.md): Standalone schedule day view component
|
||||
- [GettingStartedSchedule](https://mantine.dev/llms/schedule-getting-started.md)
|
||||
- [MobileMonthView](https://mantine.dev/llms/schedule-mobile-month-view.md): Mobile-optimized schedule month view component
|
||||
- [MonthView](https://mantine.dev/llms/schedule-month-view.md): Standalone schedule month view component
|
||||
- [RecurringEventsSchedule](https://mantine.dev/llms/schedule-recurring-events.md)
|
||||
- [ResourcesDayView](https://mantine.dev/llms/schedule-resources-day-view.md): Day view with resource columns for scheduling across resources
|
||||
- [ResourcesMonthView](https://mantine.dev/llms/schedule-resources-month-view.md): Month view with resource lanes for scheduling across resources
|
||||
- [ResourcesSchedule](https://mantine.dev/llms/schedule-resources-schedule.md): Schedule wrapper component that combines resource day, week and month views
|
||||
- [ResourcesWeekView](https://mantine.dev/llms/schedule-resources-week-view.md): Week view with resource rows for scheduling across resources
|
||||
- [Schedule](https://mantine.dev/llms/schedule-schedule.md): Schedule component with day, week, month and year views
|
||||
- [ScheduleEventsData](https://mantine.dev/llms/schedule-events-data.md)
|
||||
- [WeekView](https://mantine.dev/llms/schedule-week-view.md): Standalone schedule week view component
|
||||
- [YearView](https://mantine.dev/llms/schedule-year-view.md): Standalone schedule year view component
|
||||
|
||||
## Guides
|
||||
|
||||
- [ControlledVsUncontrolled](https://mantine.dev/llms/guides-controlled-vs-uncontrolled.md)
|
||||
- [CustomComponents](https://mantine.dev/llms/guides-custom-components.md)
|
||||
- [EightToNine](https://mantine.dev/llms/guides-8x-to-9x.md)
|
||||
- [FunctionsReference](https://mantine.dev/llms/guides-functions-reference.md)
|
||||
- [Gatsby](https://mantine.dev/llms/guides-gatsby.md)
|
||||
- [Icons](https://mantine.dev/llms/guides-icons.md)
|
||||
- [JavaScript](https://mantine.dev/llms/guides-javascript.md)
|
||||
- [Jest](https://mantine.dev/llms/guides-jest.md)
|
||||
- [LLMDocumentation](https://mantine.dev/llms/guides-llms.md)
|
||||
- [NextJs](https://mantine.dev/llms/guides-next.md)
|
||||
- [Polymorphic](https://mantine.dev/llms/guides-polymorphic.md)
|
||||
- [ReactRouter](https://mantine.dev/llms/guides-react-router.md)
|
||||
- [Redwood](https://mantine.dev/llms/guides-redwood.md)
|
||||
- [SevenToEight](https://mantine.dev/llms/guides-7x-to-8x.md)
|
||||
- [SixToSeven](https://mantine.dev/llms/guides-6x-to-7x.md)
|
||||
- [Storybook](https://mantine.dev/llms/guides-storybook.md)
|
||||
- [Tiptap3Migration](https://mantine.dev/llms/guides-tiptap-3-migration.md)
|
||||
- [TypeScript](https://mantine.dev/llms/guides-typescript.md)
|
||||
- [Vite](https://mantine.dev/llms/guides-vite.md)
|
||||
- [Vitest](https://mantine.dev/llms/guides-vitest.md)
|
||||
|
||||
## Theming
|
||||
|
||||
- [Colors](https://mantine.dev/llms/theming-colors.md)
|
||||
- [ColorSchemes](https://mantine.dev/llms/theming-color-schemes.md)
|
||||
- [DefaultProps](https://mantine.dev/llms/theming-default-props.md)
|
||||
- [MantineProvider](https://mantine.dev/llms/theming-mantine-provider.md)
|
||||
- [ThemeObject](https://mantine.dev/llms/theming-theme-object.md)
|
||||
- [Typography](https://mantine.dev/llms/theming-typography.md): Styles provider for html content
|
||||
|
||||
## Styles
|
||||
|
||||
- [ColorFunctions](https://mantine.dev/llms/styles-color-functions.md)
|
||||
- [CSSFilesList](https://mantine.dev/llms/styles-css-files-list.md)
|
||||
- [CSSModules](https://mantine.dev/llms/styles-css-modules.md)
|
||||
- [CssVariables](https://mantine.dev/llms/styles-css-variables.md)
|
||||
- [CSSVariablesList](https://mantine.dev/llms/styles-css-variables-list.md)
|
||||
- [DataAttributes](https://mantine.dev/llms/styles-data-attributes.md)
|
||||
- [Emotion](https://mantine.dev/llms/styles-emotion.md)
|
||||
- [GlobalStyles](https://mantine.dev/llms/styles-global-styles.md)
|
||||
- [MantineStyles](https://mantine.dev/llms/styles-mantine-styles.md)
|
||||
- [PostCSSPreset](https://mantine.dev/llms/styles-postcss-preset.md)
|
||||
- [Rem](https://mantine.dev/llms/styles-rem.md)
|
||||
- [ResponsiveStyles](https://mantine.dev/llms/styles-responsive.md)
|
||||
- [Rtl](https://mantine.dev/llms/styles-rtl.md)
|
||||
- [StyleProp](https://mantine.dev/llms/styles-style.md)
|
||||
- [StyleProps](https://mantine.dev/llms/styles-style-props.md)
|
||||
- [StylesApi](https://mantine.dev/llms/styles-styles-api.md)
|
||||
- [StylesOverview](https://mantine.dev/llms/styles-styles-overview.md)
|
||||
- [StylesPerformance](https://mantine.dev/llms/styles-styles-performance.md)
|
||||
- [UnstyledComponents](https://mantine.dev/llms/styles-unstyled.md)
|
||||
- [UsageWithSass](https://mantine.dev/llms/styles-sass.md)
|
||||
- [VanillaExtract](https://mantine.dev/llms/styles-vanilla-extract.md)
|
||||
- [VariantsAndSizes](https://mantine.dev/llms/styles-variants-sizes.md)
|
||||
|
||||
## X
|
||||
|
||||
- [Carousel](https://mantine.dev/llms/x-carousel.md): Embla based carousel component
|
||||
- [CodeHighlight](https://mantine.dev/llms/x-code-highlight.md): Highlight code with shiki or highlight.js
|
||||
- [Dropzone](https://mantine.dev/llms/x-dropzone.md): Capture files from user with drag and drop
|
||||
- [ExtensionsPackage](https://mantine.dev/llms/x-extensions.md)
|
||||
- [Modals manager](https://mantine.dev/llms/x-modals.md): Centralized modals manager with option to handle state of multi-step modals
|
||||
- [NavigationProgress](https://mantine.dev/llms/x-nprogress.md): Navigation progress bar
|
||||
- [Notifications system](https://mantine.dev/llms/x-notifications.md): Mantine notifications system
|
||||
- [Rich text editor](https://mantine.dev/llms/x-tiptap.md): Tiptap based rich text editor
|
||||
- [Spotlight](https://mantine.dev/llms/x-spotlight.md): Command center for your application
|
||||
|
||||
## Other
|
||||
|
||||
- [About](https://mantine.dev/llms/about.md)
|
||||
- [AllReleases](https://mantine.dev/llms/changelog-all-releases.md)
|
||||
- [BrowserSupport](https://mantine.dev/llms/browser-support.md)
|
||||
- [Changelog700](https://mantine.dev/llms/changelog-7-0-0.md)
|
||||
- [Changelog710](https://mantine.dev/llms/changelog-7-1-0.md)
|
||||
- [Changelog7100](https://mantine.dev/llms/changelog-7-10-0.md)
|
||||
- [Changelog7110](https://mantine.dev/llms/changelog-7-11-0.md)
|
||||
- [Changelog7120](https://mantine.dev/llms/changelog-7-12-0.md)
|
||||
- [Changelog7130](https://mantine.dev/llms/changelog-7-13-0.md)
|
||||
- [Changelog7140](https://mantine.dev/llms/changelog-7-14-0.md)
|
||||
- [Changelog7150](https://mantine.dev/llms/changelog-7-15-0.md)
|
||||
- [Changelog7160](https://mantine.dev/llms/changelog-7-16-0.md)
|
||||
- [Changelog7170](https://mantine.dev/llms/changelog-7-17-0.md)
|
||||
- [Changelog720](https://mantine.dev/llms/changelog-7-2-0.md)
|
||||
- [Changelog730](https://mantine.dev/llms/changelog-7-3-0.md)
|
||||
- [Changelog740](https://mantine.dev/llms/changelog-7-4-0.md)
|
||||
- [Changelog750](https://mantine.dev/llms/changelog-7-5-0.md)
|
||||
- [Changelog760](https://mantine.dev/llms/changelog-7-6-0.md)
|
||||
- [Changelog770](https://mantine.dev/llms/changelog-7-7-0.md)
|
||||
- [Changelog780](https://mantine.dev/llms/changelog-7-8-0.md)
|
||||
- [Changelog790](https://mantine.dev/llms/changelog-7-9-0.md)
|
||||
- [Changelog800](https://mantine.dev/llms/changelog-8-0-0.md)
|
||||
- [Changelog810](https://mantine.dev/llms/changelog-8-1-0.md)
|
||||
- [Changelog820](https://mantine.dev/llms/changelog-8-2-0.md)
|
||||
- [Changelog830](https://mantine.dev/llms/changelog-8-3-0.md)
|
||||
- [Changelog900](https://mantine.dev/llms/changelog-9-0-0.md)
|
||||
- [Changelog910](https://mantine.dev/llms/changelog-9-1-0.md)
|
||||
- [Changelog920](https://mantine.dev/llms/changelog-9-2-0.md)
|
||||
- [Changelog930](https://mantine.dev/llms/changelog-9-3-0.md)
|
||||
- [Changelog940](https://mantine.dev/llms/changelog-9-4-0.md)
|
||||
- [Changelog950](https://mantine.dev/llms/changelog-9-5-0.md)
|
||||
- [Contribute](https://mantine.dev/llms/contribute.md)
|
||||
- [GettingStarted](https://mantine.dev/llms/getting-started.md)
|
||||
- [NotFound](https://mantine.dev/llms/404.md)
|
||||
- [OxcConfig](https://mantine.dev/llms/oxc-config-mantine.md)
|
||||
- [PreviousChangelogs](https://mantine.dev/llms/changelog-previous-versions.md)
|
||||
- [Support](https://mantine.dev/llms/support.md)
|
||||
@@ -1,33 +0,0 @@
|
||||
# Product
|
||||
|
||||
## Register
|
||||
|
||||
brand
|
||||
|
||||
## Users
|
||||
|
||||
Self-hosted Homarr users who need to evaluate the project, install it, connect services, find extensions, and reference its APIs. They move between the homepage, documentation, Workshop, API reference, and project updates as one continuous experience.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
The site explains what Homarr does and gives users reliable paths to install, configure, extend, and operate it. Success means users can identify where they are, move between major surfaces without relearning the navigation, and read technical content comfortably.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Practical, technical, welcoming.
|
||||
|
||||
## Anti-references
|
||||
|
||||
Avoid disconnected microsites, marketing-heavy copy, ornamental dashboards, hidden navigation, and dense long-form pages without visible structure.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- Keep one recognizable Homarr identity across every surface.
|
||||
- Let navigation topology match the task while keeping labels and behavior consistent.
|
||||
- Prioritize readable line lengths, strong heading hierarchy, and visible document structure.
|
||||
- Make primary paths obvious without adding decorative UI.
|
||||
- Preserve useful functionality on mobile, keyboard navigation, and reduced-motion settings.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Target WCAG 2.2 AA. Maintain keyboard access, visible focus states, sufficient contrast, semantic landmarks, touch-friendly controls, responsive layouts, and reduced-motion behavior.
|
||||
@@ -5,7 +5,7 @@
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.bun-cache/
|
||||
.pnpm-store/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -13,7 +13,6 @@
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/.source/
|
||||
|
||||
# production
|
||||
/build
|
||||
@@ -56,4 +55,4 @@ static/data/configs
|
||||
|
||||
# CI Stuff
|
||||
screenshots
|
||||
test-results
|
||||
test-results
|
||||
@@ -1,18 +0,0 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
|
||||
|
||||
This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
|
||||
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
|
||||
## Fumadocs workflow
|
||||
|
||||
- Start with `README.md`, `src/lib/source.ts`, `source.config.ts`, and the matching route under `src/app/`.
|
||||
- Read current [Fumadocs documentation](https://fumadocs.dev/docs) with Context7 before changing framework APIs. Check the installed version and types; latest examples may use a different search adapter.
|
||||
- Preserve static export: search uses `staticGET` with the matching static client. Do not introduce a request-time search server into this app.
|
||||
- Keep integration/widget metadata in the existing typed definitions. Check both rendered HTML and processed Markdown when changing MDX components; JSX alone does not guarantee readable search or AI output.
|
||||
- Run the README validation commands after content or navigation changes. Search results and table-of-contents links must resolve to rendered anchors.
|
||||
- Kapa is an independent hosted assistant. Keep its keyboard shortcut disabled so it does not override docs search; follow the README for its build configuration and crawler setup.
|
||||
@@ -1,37 +0,0 @@
|
||||
# Homarr Documentation
|
||||
|
||||
## Register
|
||||
|
||||
product
|
||||
|
||||
## Users
|
||||
|
||||
People who run Homarr, administrators configuring self-hosted infrastructure, and contributors extending Homarr. They arrive with a concrete task such as installing Homarr, connecting a service, configuring a widget, managing users, or building a custom widget, and need an answer they can scan and apply quickly.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
The documentation is the complete, authoritative operating guide for Homarr. It keeps setup steps, configuration contracts, screenshots, generated integration and widget metadata, API references, and Workshop tooling next to the application code. Success means a reader can find the right page quickly, understand the next action, copy reliable instructions, and verify the result without searching elsewhere.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Focused, dependable, and approachable. The writing is direct and practical. The interface should feel calm and familiar during long technical sessions while retaining Homarr's red identity.
|
||||
|
||||
## Anti-references
|
||||
|
||||
- Generic marketing layouts that replace Homarr's product identity with unrelated slogans or decorative conversion copy.
|
||||
- Decorative dashboard aesthetics that compete with reading, including glass effects, large radii, card grids, and gratuitous motion.
|
||||
- Generic generated-docs styling that obscures Homarr's identity or makes navigation feel detached from the main product.
|
||||
- Dense reference portals that expose structure without giving readers a clear path through a task.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. Put the reader's task first: search, navigation, page outline, and the answer must remain easy to reach.
|
||||
2. Preserve one source of truth: typed Homarr metadata and existing custom components stay connected to the monorepo.
|
||||
3. Prefer familiar documentation patterns: the interface should disappear while a reader scans, copies, and follows instructions.
|
||||
4. Make complete content feel manageable through strong hierarchy, progressive disclosure, and precise cross-links.
|
||||
5. Treat machine readers as users through canonical Markdown, structured metadata, and standard LLM discovery endpoints.
|
||||
6. Keep the root landing page recognizably Homarr: preserve its self-hosted dashboard promise and real product evidence while documentation routes remain task-first.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Meet WCAG 2.2 AA for color contrast, keyboard operation, focus visibility, landmarks, headings, and interactive controls. Respect reduced-motion and system color-scheme preferences. Diagrams, screenshots, icons, and emoji must have meaningful text alternatives when they carry information.
|
||||
@@ -1,168 +1,111 @@
|
||||
# Homarr documentation (`@homarr/docs`)
|
||||
# Homarr Documentation (`@homarr/docs`)
|
||||
|
||||
The source for [homarr.dev](https://homarr.dev), built with Next.js and Fumadocs UI inside the Homarr monorepo.
|
||||
The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo.
|
||||
|
||||
## Develop
|
||||
## Why it's in the monorepo
|
||||
|
||||
From the repository root:
|
||||
Having documentation alongside the application code enables:
|
||||
|
||||
- **Two-way linking** — the docs app imports from `@homarr/definitions` for type-safe integration/widget metadata, and the main app references doc paths via generated sitemap types
|
||||
- **Atomic PRs** — code changes and their documentation updates ship in the same pull request
|
||||
- **Shared tooling** — same Node version, pnpm catalog, oxlint/oxfmt, CI pipeline
|
||||
- **Build-time validation** — Docusaurus strict mode catches broken links and references at build time
|
||||
|
||||
## Development
|
||||
|
||||
From the **monorepo root**:
|
||||
|
||||
```bash
|
||||
bun run dev:docs
|
||||
# Start the docs dev server (port 3003)
|
||||
pnpm dev:docs
|
||||
|
||||
# Or from within this directory
|
||||
pnpm dev
|
||||
|
||||
# Point the docs at a local PocketBase instance
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 pnpm dev:docs
|
||||
```
|
||||
|
||||
The site runs on `http://127.0.0.1:3003`. To use a local Workshop API:
|
||||
The standalone docs use `WORKSHOP_API_URL` for PocketBase and `HOMARR_WEBSITE_URL` for the public site origin. The old
|
||||
`WORKSHOP_URL` name is a deprecated one-release alias.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
WORKSHOP_API_URL=http://127.0.0.1:8090 bun run dev:docs
|
||||
# Build docs only
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
|
||||
# Build everything (docs + main app + tasks + websocket)
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Validate
|
||||
## Lint & Format
|
||||
|
||||
Uses oxlint and oxfmt (same as the rest of the monorepo):
|
||||
|
||||
```bash
|
||||
bun run --filter @homarr/docs typecheck
|
||||
bun run --filter @homarr/docs build
|
||||
bun run --filter @homarr/docs validate:links
|
||||
bun run --filter @homarr/docs verify:search
|
||||
bun run --filter @homarr/docs verify:seo
|
||||
pnpm lint # oxlint
|
||||
pnpm format # oxfmt (check)
|
||||
pnpm format:fix # oxfmt (fix)
|
||||
```
|
||||
|
||||
The static export is written to `out/`. The build validates docs pages and typed metadata for integrations and widgets
|
||||
that declare a documentation slug. Set the slug to `null` when a page would add no useful information for advanced users.
|
||||
CI enforces coverage alongside schema drift and export validation. The link check validates internal Markdown and MDX
|
||||
links and rendered anchors against the export.
|
||||
The search check exercises the exported index with title, heading, and body queries and verifies result destinations.
|
||||
The SEO check validates canonical URLs, descriptions, social metadata, heading structure, and sitemap coverage in the
|
||||
exported HTML. These tags are generated at build time; set `HOMARR_WEBSITE_URL` before building for a different origin.
|
||||
|
||||
## Preview the static export with PocketBase
|
||||
|
||||
After building the docs, run from the repository root:
|
||||
## Typecheck
|
||||
|
||||
```bash
|
||||
sh apps/workshop/preview-docs.sh
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
Open `http://127.0.0.1:8093/docs/`. This starts an isolated PocketBase container with temporary local data and serves
|
||||
the export directly, including search, Markdown, and the custom 404 page. Workshop browser requests use the configured
|
||||
backend; a trailing `/api/` is removed because the PocketBase SDK appends its API paths. The preview does not copy or
|
||||
migrate another database. Set `WORKSHOP_API_URL` to a remote PocketBase origin when testing against one;
|
||||
`WORKSHOP_REMOTE_API_URL` can override the server-side metadata source independently. Direct item links then use that
|
||||
source for titles and social metadata. Missing remote items return 404; an unavailable backend returns 503.
|
||||
## Content Structure
|
||||
|
||||
Verify public item pages without changing the remote database:
|
||||
```
|
||||
docs/
|
||||
├── getting-started/ # Installation guides, glossary, prerequisites
|
||||
├── management/ # Boards, apps, users, integrations, settings, tasks, API
|
||||
├── integrations/ # 56 integration guides (one folder each)
|
||||
├── widgets/ # 43 widget guides (one folder each)
|
||||
├── advanced/ # SSO, env vars, proxy, styling, CLI, development
|
||||
└── community/ # FAQ, donate, license, translations, get-in-touch
|
||||
```
|
||||
|
||||
Each integration/widget doc follows a consistent pattern:
|
||||
|
||||
- `index.ts` — typed metadata (`IntegrationDefinition` / `WidgetDefinition`)
|
||||
- `index.mdx` — content using shared React components (`IntegrationHeader`, `WidgetHeader`, etc.)
|
||||
|
||||
## Importing from homarr packages
|
||||
|
||||
The docs app has `@homarr/definitions` as a workspace dependency. You can import integration kinds, widget kinds, and other definitions directly:
|
||||
|
||||
```typescript
|
||||
import { IntegrationKind } from "@homarr/definitions";
|
||||
```
|
||||
|
||||
This enables type-safe references to homarr domain concepts within documentation code.
|
||||
|
||||
## Search (Algolia DocSearch)
|
||||
|
||||
Search uses the `Docusaurus` Algolia index (crawler `cd77a285-2756-4557-bf21-ee703748df15`). A separate `markdown` index is built for LLM markdown indexing.
|
||||
|
||||
Crawler reference config: [`docsearch.config.js`](docsearch.config.js).
|
||||
|
||||
**Verify search health:**
|
||||
|
||||
```bash
|
||||
REMOTE_WORKSHOP_URL=https://workshop.example.com
|
||||
WORKSHOP_TEST_URL=http://127.0.0.1:8093 WORKSHOP_REMOTE_API_URL="$REMOTE_WORKSHOP_URL" \
|
||||
bun apps/workshop/tests/remote-workshop.integration.mjs
|
||||
pnpm verify:search
|
||||
```
|
||||
|
||||
Stop it with `docker stop homarr-docs-static-preview`. Rebuild and restart the preview after changing source files;
|
||||
startup recreates the runtime configuration. `DOCS_PREVIEW_PORT`, `DOCS_PREVIEW_HOST`, and `DOCS_PREVIEW_NAME` override
|
||||
the defaults. Set the host to this machine's Tailscale address to access it from your other devices.
|
||||
## Tech Stack
|
||||
|
||||
## Write content
|
||||
| Layer | Technology |
|
||||
| -------------- | ------------------------------------------- |
|
||||
| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) |
|
||||
| Styling | Tailwind CSS 4 + Docusaurus Infima |
|
||||
| Icons | `@tabler/icons-react` |
|
||||
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
|
||||
| Search | Algolia DocSearch |
|
||||
| Analytics | PostHog |
|
||||
| Charts | @nivo/line |
|
||||
|
||||
Documentation lives in `docs/`; project posts live in `blog/`. Navigation is controlled by the nearest `meta.json`.
|
||||
Integration and widget folders pair typed `index.ts` metadata with `index.mdx` content.
|
||||
## Contributing
|
||||
|
||||
Use standard Markdown and MDX. Shared components live in `src/components/`. Mermaid fences, tabs, callouts, zoomable
|
||||
images, and syntax-highlighted code are supported by the Fumadocs pipeline.
|
||||
|
||||
Update docs in the same pull request only when a smart, advanced user cannot infer the changed behavior from the UI or
|
||||
generated API schema. Routine or self-explanatory changes need no docs update. See the
|
||||
[documentation-sync skill](../../.agents/skills/documentation-sync/SKILL.md) for target paths.
|
||||
|
||||
## Published formats
|
||||
|
||||
- `/docs` — searchable documentation with typed navigation
|
||||
- `/api-reference` — interactive reference generated from the OpenAPI schema
|
||||
- `/llms.txt` — compact documentation and blog index for agents
|
||||
- `/llms-full.txt` — complete processed Markdown corpus
|
||||
- `/llms.mdx/docs/.../content.md` — raw Markdown for each docs page
|
||||
- `/llms.mdx/blog/.../content.md` — raw Markdown for each blog post
|
||||
- `/llms.mdx/api-reference/.../content.md` — operation parameters, bodies, responses, and authentication
|
||||
- `/blog/rss.xml` — RSS feed for project posts
|
||||
- `/search?q=...` — opens documentation search with the supplied query, including existing Homarr search-engine links
|
||||
|
||||
Every docs page and blog post includes copy-Markdown and view-source actions. Search is generated at build time and
|
||||
runs locally in the browser; it does not depend on an external crawler. Markdown exports and search resolve
|
||||
integration credentials and widget defaults from the same typed metadata used by the pages.
|
||||
The search dialog and its client-side engine load when search is first opened. Fumadocs caches the downloaded index
|
||||
for subsequent queries. The docs index, individual Markdown pages, and full corpus share the `llms()` renderer.
|
||||
API operations are indexed alongside docs and exported as Markdown from the same OpenAPI schema. The reference uses
|
||||
Fumadocs with Scalar: select an instance URL and API key, then explicitly send a request. Authentication is not
|
||||
persisted in local storage. Browser access requires the documented REST CORS configuration.
|
||||
|
||||
The Custom JSX guide includes a lazily loaded, editable example using the application renderer and real component
|
||||
registry. Its bindings work locally; configured widget queries and actions do not run.
|
||||
|
||||
The getting-started overview and complete Custom JSX component catalog are also included, with property types,
|
||||
binding contracts, and blocked capabilities taken from the published catalog JSON.
|
||||
|
||||
Published Docusaurus `/docs/category/...` addresses remain available as static compatibility pages. They forward to
|
||||
the corresponding overview or developer setup guide, preserving query strings and anchors, and provide a direct
|
||||
link when JavaScript is disabled. Their canonical metadata points to the destination; compatibility pages are
|
||||
excluded from search and the sitemap.
|
||||
|
||||
## Fumadocs upgrade notes
|
||||
|
||||
The September 2026 update moves Core/UI from 16.15.4 to 16.15.12, MDX from 15.4.0 to 15.4.3, and OpenAPI from
|
||||
11.3.5 to 12.0.1. Review the upstream [Core](https://github.com/fuma-nama/fumadocs/blob/dev/packages/core/CHANGELOG.md),
|
||||
[UI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/CHANGELOG.md),
|
||||
[MDX](https://github.com/fuma-nama/fumadocs/blob/dev/packages/mdx/CHANGELOG.md), and
|
||||
[OpenAPI](https://github.com/fuma-nama/fumadocs/blob/dev/packages/openapi/CHANGELOG.md) changelogs when upgrading.
|
||||
|
||||
- Core/UI and MDX now declare side effects for better tree shaking. UI also fixes tabs reverting to the URL hash.
|
||||
- Core's `llms({ renderPage })` API provides the shared `index()`, `page()`, and `full()` exports.
|
||||
- OpenAPI 12 replaces several custom renderer options and hooks. This site uses `createOpenAPIPage()` and
|
||||
`getOpenAPIPageProps()` without those removed overrides; the built-in renderer retains all request languages.
|
||||
The Scalar adapter receives an equivalent nonrecursive union for unconstrained JSON values to avoid recursive
|
||||
schema expansion during rendering. The generated public OpenAPI document remains unchanged.
|
||||
- MDX 15.4.3 fixes duplicate compilation output when its experimental build cache is enabled. We leave that cache
|
||||
disabled: its current key checks source content without accounting for all compiler configuration changes.
|
||||
- The existing macro collections, processed-Markdown component adapters, and static search remain supported.
|
||||
Typed version roots and server-side MCP tools are available upstream but are not needed by this static site.
|
||||
|
||||
## Kapa AI
|
||||
|
||||
The compact Homarr mascot in the bottom-right opens **Ask AI**. It slowly spins when idle and gives a subtle claw movement and wink on hover. Animation respects reduced motion.
|
||||
|
||||
The launcher uses Homarr's existing public Kapa Website ID. The root layout loads the widget once across
|
||||
client-side navigation and follows the docs light or dark theme. **Search** and `Ctrl+K` / `Cmd+K` always use Fumadocs search, including when Kapa is unavailable.
|
||||
|
||||
Set `KAPA_WEBSITE_ID` at build time to use a different Kapa Website Widget integration. An explicitly empty value
|
||||
disables the widget, for example `KAPA_WEBSITE_ID= bun run --filter @homarr/docs build`. This is a public integration ID
|
||||
included in exported pages, not a Kapa API key. Rebuild the static export after changing it.
|
||||
|
||||
In the Kapa dashboard, confirm the integration is live and enable the production and preview domains. Restoring the
|
||||
script does not verify that the historical integration is still active. Follow the
|
||||
[Kapa widget setup](https://docs.kapa.ai/integrations/website-widget/quickstart) for domain restrictions and CSP settings.
|
||||
|
||||
After changing the documentation structure, review the Website Crawl source in Kapa: use the deployed `/docs` URL as
|
||||
the start URL and preview `main` as the content selector. Replace any old Docusaurus-specific selector and check that
|
||||
titles, headings, code samples, and integration details appear in the extracted Markdown. Docs are exported as HTML;
|
||||
the site also publishes `/sitemap.xml` and permits crawling in `/robots.txt`. The `/llms.txt` exports do not configure
|
||||
Kapa ingestion automatically. See [Kapa source setup](https://docs.kapa.ai/getting-started/index-your-first-source).
|
||||
|
||||
Before publishing, check that Ask AI opens after navigating between pages, ask a documentation question, and verify
|
||||
its citations point to the current docs. In a separate check, block `widget.kapa.ai` and confirm search still works.
|
||||
|
||||
## Release verification
|
||||
|
||||
The production image compiles the docs itself; it does not depend on a host `out/` directory.
|
||||
Build-time `HOMARR_WEBSITE_URL` controls canonical metadata. Runtime URL overrides configure Workshop connections
|
||||
but do not rewrite already-exported canonical URLs.
|
||||
|
||||
PostHog records SPA pageviews, installation clicks, searches with no results (including the search query), and Workshop
|
||||
content downloads and submission starts/completions through `hog.homarr.dev`. Downloads include JSON files and
|
||||
successful JSON/CSS clipboard copies. All events carry `site` and `source_path`. Autocapture, page-leave events, and
|
||||
session replay are disabled; tracked URL query strings and fragments are removed. Localhost and `?analytics_test`
|
||||
traffic carries `verification=true`; exclude it from production reports.
|
||||
|
||||
Carbon loads one visible placement after the desktop TOC, below API examples, or below content on other layouts.
|
||||
The homepage is excluded at every viewport size. Navigation reloads the ad script; resizing changes placement only when crossing its breakpoint. Ad blockers
|
||||
or no-fill responses must not prevent content, navigation, or search from working.
|
||||
|
||||
To preview Carbon locally, start docs with `NEXT_PUBLIC_ENABLE_CARBON_ADS=true bun run dev:docs`. Production builds
|
||||
enable ads automatically. When the TOC collapses into a toggle, the ad appears below the content before “Edit this page”.
|
||||
When making changes to the homarr codebase that affect user-facing behavior, **always update the corresponding documentation in this app**. See `.cursor/rules/documentation-sync.mdc` for the complete mapping of code changes to doc locations.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
slug: documentation-migration
|
||||
title: Migration of Documentation
|
||||
date: 2022-06-22
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -13,7 +12,7 @@ The documentation has been re-written partly and includes now many animations, g
|
||||
|
||||
We are still figuring things out and highly depend on your feedback.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## Why we migrated
|
||||
|
||||
@@ -33,4 +32,4 @@ After we've reviewed your changes, we'll merge your PR and this documentation wi
|
||||
|
||||
## TL;DR
|
||||
|
||||
The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability.
|
||||
The documentation on GitHub Wiki will soon disappear. It will be replaced with this Docusaurus-powered website. You'll find much more information here with higher detail and searchability.
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
slug: translations
|
||||
title: Help us make Homarr multilingual
|
||||
date: 2022-08-28
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -14,8 +13,8 @@ We've been working hard to offer Homarr in many languages. The next update of Ho
|
||||
 <br/>
|
||||
Yes, we need **YOU**! We want to make Homarr accessible to as many people as possible. Thus, we need to translate all content into many different languages. We need your help for that! [You can help us now to translate Homarr into as many languages as possible](/docs/community/translations).
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
### About global translations / translations per user
|
||||
|
||||
We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎
|
||||
We are aware that many people would like to configure translations globally or configure language per user. Currently, translations are stored in the Cookies, which will expire after some time (currently, it's set to 30 days). We will implement more granular language settings in future versions of Homarr, since it requires major changes to Homarr. Stay tuned! 😎
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "We present: Homarr 0.11 - the most complex Homarr version yet!"
|
||||
date: 2023-01-11
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -21,7 +20,7 @@ It contains many breaking changes and huge improvements to your favourite dashbo
|
||||
- Huge design and usability improvements to older components to Homarr
|
||||
- Better handeling of exceptions and unexpected issues
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## Complete implementation of highly customizable drag and drop library "Gridstack"
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "We present: Homarr 0.12 - The fastest homarr version ever!"
|
||||
date: 2023-04-16
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -16,12 +15,14 @@ This applies to all widgets, but in the case of the IFrame widget, it enables yo
|
||||
- Integrate monitoring tools like Grafana, Splunk or Elasticsearch
|
||||
- Integrate your services (for example Sonarr, Radarr and Overseerr) directly into Homarr
|
||||
|
||||

|
||||
|
||||
## Edit mode password
|
||||
We've added a setting to disable editing completely. This will allow you to restrict edit access without password-protection.
|
||||
use the `EDIT_MODE_PASSWORD` ENV variable to set a password for edit mode.
|
||||
turn on `DISABLE_EDIT_MODE=TRUE` to disable edit mode on startup.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## 😎 Blazingly fast
|
||||
We've implemented a new caching system, that will cache all data in your browser, meaning that Homarr will load within milliseconds, as if it was *powered by rust and blazingly fast* !!!
|
||||
@@ -71,4 +72,4 @@ Now, you can use the following environment variables to configure this:
|
||||
- ``DOCKER_HOST``
|
||||
- ``DOCKER_PORT``
|
||||
|
||||
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
|
||||
**Full Changelog**: https://github.com/ajnart/homarr/compare/v0.11.5...v0.12.0
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "Authentication in Homarr - 0.14 is here!"
|
||||
date: 2023-11-10
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
@@ -24,7 +23,7 @@ provide you a viable solution today.
|
||||
- 📝 Overhauled notepad widget with a complex graphical editor that enables easy editing right from your browser.
|
||||
- :globe_with_meridians: Live Translation on Crowdin that enable you to directly translate using images to see what exactly you're translating
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## Breaking changes
|
||||
|
||||
@@ -84,4 +83,4 @@ We are aware that this update has many breaking changes and may confuse some use
|
||||
For that reason, we updated our documentation and will enhance it further within the next few weeks.
|
||||
We've also added this small button at the bottom of the page were you can ask **your questions** to an AI assistant:
|
||||
|
||||

|
||||

|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: "Updated documentation with better organization & design"
|
||||
date: 2023-12-22
|
||||
authors:
|
||||
- manuel-rw
|
||||
tags: [homarr, documentation, design, docs]
|
||||
@@ -9,7 +8,7 @@ tags: [homarr, documentation, design, docs]
|
||||
We'd like to let you know that we've updated our documentation and reorganized the pages.
|
||||
This means, that some links may no longer be valid - we apologise if this causes any inconvenience for you.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
A few highlights include:
|
||||
|
||||
@@ -19,4 +18,4 @@ A few highlights include:
|
||||
- Bug fixes for image zoom
|
||||
- Added documentation for management pages and [new CLI tool](/docs/advanced/command-line/password-recovery/)
|
||||
|
||||
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
|
||||
Please [let us know](/docs/community/get-in-touch) is something is not working as expected.
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
title: Homarr 1.0 - complete rewrite of all systems
|
||||
date: 2024-09-23
|
||||
authors:
|
||||
- manuel-rw
|
||||
---
|
||||
|
||||
# Homarr 1.0 - complete rewrite of all systems
|
||||
We proudly present to you - Homarr ``1.0``.
|
||||
We have started work on this new version back in 2023 and have worked tirelessly for months on this brand-new dashboard.
|
||||
|
||||
@@ -29,14 +28,14 @@ reliability and scalability compared to < ``1.0``.
|
||||
- **Improved performance**: We have upgraded the underlying framework of Homarr and take advantage
|
||||
of an in-memory message bus and websockets to deliver data near realtime to you.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## Breaking changes
|
||||
<Alert type="warning">
|
||||
:::warning
|
||||
|
||||
Please consult the breaking changes list & upgrade guide if you used third-party installation methods.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
- Docker Image
|
||||
- Removed support for ``arm/v7`` -> upgrade to newer architectures as an alternative
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: Open Beta 1.0
|
||||
date: 2024-12-17
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# Open Beta 1.0
|
||||
|
||||
We are proud to announce the open beta of Homarr 1.0. This is a major milestone for us and we are excited to share it with you.
|
||||
The open beta allows us to polish the last rough edges and gather feedback from you.
|
||||
We are looking forward to your feedback and hope you enjoy the new features.
|
||||
Let us know throgh discord or a [github issues](https://github.com/homarr-labs/homarr/issues/new/choose) if you encounter any issues.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## How to use the beta with docker
|
||||
|
||||
@@ -36,4 +36,4 @@ services:
|
||||
|
||||
Then, run `docker compose up -d` in the same directory. This will start the Homarr container in the background.
|
||||
Please note that the mounts have changed and it's not backwards compatible with versions before 1.0.0-beta.1.
|
||||
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
|
||||
You can find a list of all breaking changes [here](/blog/2024/09/23/version-1.0)
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Migrate from 0.15.10+ to 1.0.0
|
||||
date: 2025-01-19
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# Migrate from 0.15.10+ to 1.0.0
|
||||
|
||||
In this guide, we will show you how to migrate from Homarr 0.15.10+ to 1.0.0.
|
||||
If you are still on a version before 0.15.10, please update to 0.15.10 where we introduced the migration export page.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||
## Preserve your old installation
|
||||
|
||||
@@ -22,17 +22,17 @@ Plan to recreate your apps, integrations, and boards manually in the new install
|
||||
|
||||
We highly recommend to create a parallel installation of Homarr 1.0.0.
|
||||
So if something with for example the import does not quite work, the data in the old installation is still available.
|
||||
To do this you can follow the appropriate [installation guide](/docs/getting-started) for your system.
|
||||
To do this you can follow the appropriate [installation guide](https://homarr.dev/docs/category/installation-1) for your system.
|
||||
Please note that if you are using docker you need to specify a diffrent external port for the new installation as the old one had the same port.
|
||||
So in your compose file the ports would be `7576:7575` instead of `7575:7575`.
|
||||
|
||||
## Import your data
|
||||
|
||||
<Alert type="warning" title="Legacy guide">
|
||||
:::warning Legacy guide
|
||||
|
||||
The pre-1.0 ZIP importer is no longer included in current Homarr releases. Keep the old installation available until you have manually verified the replacement. Compatible SQLite backups from current releases can still be restored from the onboarding welcome screen.
|
||||
|
||||
</Alert>
|
||||
:::
|
||||
|
||||
## Remove the old installation
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: How to use Argus with Homarr
|
||||
date: 2025-08-02
|
||||
authors:
|
||||
- meierschlumpf
|
||||
---
|
||||
|
||||
# How to use Argus with Homarr
|
||||
|
||||
In `v1.32.0` we added the endpoint `/api/info` which returns the current version of Homarr and can therefore be used in Argus to watch for updates.
|
||||
This guide will show you how to set up a user with apiKey to access the endpoint without providing admin access through it.
|
||||
|
||||
{/* Historical excerpt boundary. */}
|
||||
<!-- truncate -->
|
||||
|
||||

|
||||
|
||||
@@ -64,4 +64,4 @@ Now you can configure Argus to use the user and apiKey we created.
|
||||
|
||||

|
||||
|
||||
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
|
||||
Replace the `<secret>` with the apiKey you created and the deployed version url with the url of your Homarr instance.
|
||||
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 126 KiB |