Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6143e25686 | ||
|
|
384d83ef76 | ||
|
|
998161b1d9 | ||
|
|
a6e060265a | ||
|
|
d8b278d716 | ||
|
|
813a91cb01 | ||
|
|
990452a877 | ||
|
|
0165e2e415 | ||
|
|
514c6edc8f | ||
|
|
fdcfb775fa | ||
|
|
0843ccda99 | ||
|
|
2b6eb88a53 | ||
|
|
e65c3cd801 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: homarr-custom-widget
|
||||
description: Author safe Homarr Custom JSX v2 widgets from API documentation.
|
||||
---
|
||||
|
||||
# Homarr Custom Widget
|
||||
|
||||
Author one widget at a time. When connected to Homarr, retrieve `homarr://custom-widgets/schema` and only the component resources needed for the design. The live resources are authoritative for the installed release.
|
||||
|
||||
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.
|
||||
|
||||
Use `{option:name}` or `$option` for saved options. Use `{param:name}` or `$param` for values supplied by `SubFetch`, `ActionButton`, or `ToggleSwitch`. Load queries cannot use invocation parameters. Templates read `data`, `status`, `options`, and temporary `inputs`.
|
||||
|
||||
`SubFetch` with `trigger="manual"` renders its own load button. Pass a card or image through `triggerContent` with `triggerAriaLabel` when that content should launch the request. Its callback is `(result, meta)`; never author `onClick` or a fetch callback.
|
||||
|
||||
Use standard Mantine compound names and deliberate visual hierarchy, spacing, restrained semantic color, and responsive layouts. Avoid excessive nested cards.
|
||||
|
||||
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,16 +0,0 @@
|
||||
# Runtime
|
||||
|
||||
Templates read `data.requestId`, `status.requestId`, `options.name`, and temporary `inputs.name`. Status contains `loading`, `ok`, `status`, `statusText`, and `error`.
|
||||
|
||||
`bind="search"` creates an in-memory input. It is never persisted. Supply invocation values only through `params`, for example:
|
||||
|
||||
```jsx
|
||||
<TextInput bind="search" label="Search" />
|
||||
<SubFetch requestId="search" params={{ query: inputs.search }}>
|
||||
{(items) => <Stack>{(items ?? []).map(item => <Text key={item.id}>{item.name}</Text>)}</Stack>}
|
||||
</SubFetch>
|
||||
```
|
||||
|
||||
`SubFetch` with `trigger="manual"` renders its own load button. To make a card or image launch the request, pass that node through `triggerContent` and provide `triggerAriaLabel`; Homarr supplies the click and keyboard behavior. Its optional second callback argument contains `{ ok, status, statusText, loading: false }`; loading and request failures are rendered by `SubFetch` before the callback runs. Do not author `onClick` or a fetch callback.
|
||||
|
||||
Use expression callbacks for supported collection methods and trusted slots. Do not use callback blocks, IIFEs, authored recursion, or raw events. Bounded regex literals work only with safe string matching/replacement operations.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Schema
|
||||
|
||||
```ts
|
||||
interface HomarrCustomWidgetV2 {
|
||||
$schema: "homarr-custom-widget-v2";
|
||||
name: string;
|
||||
description?: string;
|
||||
iconUrl?: string;
|
||||
sources: Record<string, CustomWidgetSource>;
|
||||
requests: Record<string, CustomWidgetRequest>;
|
||||
options?: Record<string, CustomWidgetOption>;
|
||||
template: string;
|
||||
}
|
||||
```
|
||||
|
||||
Sources require `default`. A request defaults to the default source, query, GET, load, inherited auth, and view permission. An action defaults to manual and modify permission. DELETE uses full permission and confirmation.
|
||||
|
||||
Use stable real URLs for public APIs and clear suggested URLs for self-hosted services. Homarr collects the installer's server URL, network scope, and credentials as source setup; credentials remain outside the manifest.
|
||||
|
||||
Paths use `{option:name}` and `{param:name}`. Query/body data uses `{ "$option": "name" }` and `{ "$param": "name" }`. Load queries cannot use params. Runtime parameter names and primitive values are inferred from references.
|
||||
|
||||
Every option has `label`, `control`, and `default`. Optional fields are `description`, `choices`, `choicesFrom`, `min`, `max`, `step`, `advanced`, and `group`.
|
||||
@@ -1,7 +0,0 @@
|
||||
# Security
|
||||
|
||||
All requests use Homarr's protected server executor. Source origin, network scope, DNS, redirects, SSRF, rate limits, permissions, size limits, timeouts, and encrypted credential injection remain enforced.
|
||||
|
||||
The JSX interpreter blocks imports, hooks, refs, raw event callbacks, browser requests, eval, arbitrary functions, prototype access, unsafe URLs, global CSS escape, arbitrary portals, bigint, statement blocks, IIFEs, and recursion. Regex literals must be bounded and reject backreferences, lookbehind, nested quantifiers, excessive length, and unsupported flags.
|
||||
|
||||
Credentials are stored separately and never exported or returned to an agent. A published self-hosted source URL is only a suggestion: installers must confirm or replace private and loopback URLs for their own Homarr deployment. Source origins cannot be controlled through widget options.
|
||||
@@ -1,206 +0,0 @@
|
||||
---
|
||||
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
|
||||
│ ├── old-import/ # Legacy Homarr import
|
||||
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||
│ └── 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
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
description: Keep documentation in sync with code changes
|
||||
globs:
|
||||
- packages/definitions/src/**
|
||||
- packages/widgets/src/**
|
||||
- packages/integrations/src/**
|
||||
- packages/api/src/**
|
||||
- apps/nextjs/src/**
|
||||
- apps/docs/**
|
||||
---
|
||||
|
||||
# Documentation Sync Rule
|
||||
|
||||
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
||||
|
||||
## What triggers a doc update
|
||||
|
||||
| Code change | Doc location |
|
||||
|---|---|
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
||||
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
||||
| New/changed env vars | `apps/docs/docs/advanced/` |
|
||||
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
||||
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
||||
| UI/UX changes | Relevant getting-started or management pages |
|
||||
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
||||
|
||||
## Integration doc pattern
|
||||
|
||||
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
||||
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
||||
- Import metadata from `@homarr/definitions` where possible
|
||||
|
||||
## Widget doc pattern
|
||||
|
||||
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
||||
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
||||
|
||||
## Development
|
||||
|
||||
- Run docs locally: `pnpm dev:docs` from root
|
||||
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
||||
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
||||
@@ -1,622 +0,0 @@
|
||||
# Migrating Tables from mantine-react-table to mantine-datatable
|
||||
|
||||
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
|
||||
|
||||
## Migration Candidates
|
||||
|
||||
Current `mantine-react-table` consumers that could be migrated:
|
||||
|
||||
**Widgets (in `packages/widgets/`):**
|
||||
- `docker/component.tsx` — Docker container table
|
||||
- `media-server/component.tsx` — Media server sessions
|
||||
|
||||
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
|
||||
- `tools/docker/docker-table.tsx`
|
||||
- `tools/tasks/_components/tasks-table.tsx`
|
||||
- `tools/api/components/api-keys.tsx`
|
||||
- `users/_components/user-list.tsx`
|
||||
- `users/invites/_components/invite-list.tsx`
|
||||
- `tools/kubernetes/` — 8 Kubernetes resource tables
|
||||
|
||||
**Modals:**
|
||||
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
|
||||
|
||||
**Shared hook to remove once all migrated:**
|
||||
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
|
||||
|
||||
---
|
||||
|
||||
## 1. Core Migration: mantine-react-table → mantine-datatable
|
||||
|
||||
### Import changes
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
|
||||
// AFTER
|
||||
import { DataTable, useDataTableColumns } from "mantine-datatable";
|
||||
import type { DataTableColumn, DataTableSortStatus } from "mantine-datatable";
|
||||
```
|
||||
|
||||
### Column definition shape
|
||||
|
||||
```typescript
|
||||
// BEFORE (MRT)
|
||||
const columns: MRT_ColumnDef<MyRow>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Name",
|
||||
size: 200,
|
||||
enableSorting: true,
|
||||
Cell: ({ row }) => <Text>{row.original.name}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
// AFTER (mantine-datatable)
|
||||
const columns: DataTableColumn<MyRow>[] = [
|
||||
{
|
||||
accessor: "name", // not accessorKey
|
||||
title: "Name", // not header
|
||||
width: 200, // not size
|
||||
sortable: true, // not enableSorting
|
||||
render: (record) => <Text>{record.name}</Text>, // not Cell
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
Key differences:
|
||||
- `accessorKey` → `accessor`
|
||||
- `header` → `title`
|
||||
- `size` → `width`
|
||||
- `Cell: ({ row }) =>` → `render: (record) =>`
|
||||
- `enableSorting` → `sortable`
|
||||
- `enableColumnActions` → does not exist (no column action menus)
|
||||
- `mantineTableBodyCellProps` → `cellsStyle` in `defaultColumnProps`
|
||||
|
||||
### Table component
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
const table = useTranslatedMantineReactTable({ columns, data, ... });
|
||||
return <MantineReactTable table={table} />;
|
||||
|
||||
// AFTER
|
||||
return (
|
||||
<DataTable
|
||||
records={data}
|
||||
columns={columns}
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
...
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Sorting is manual
|
||||
|
||||
mantine-datatable does NOT sort for you. You must sort the data yourself:
|
||||
|
||||
```typescript
|
||||
const [sortStatus, setSortStatus] = useState<DataTableSortStatus<MyRow>>({
|
||||
columnAccessor: "name",
|
||||
direction: "asc",
|
||||
});
|
||||
|
||||
const sortedData = useMemo(() => {
|
||||
const mult = sortStatus.direction === "desc" ? -1 : 1;
|
||||
return [...data].toSorted((a, b) => {
|
||||
const aVal = a[sortStatus.columnAccessor];
|
||||
const bVal = b[sortStatus.columnAccessor];
|
||||
if (typeof aVal === "string") return aVal.localeCompare(bVal as string) * mult;
|
||||
return ((aVal as number) - (bVal as number)) * mult;
|
||||
});
|
||||
}, [data, sortStatus]);
|
||||
|
||||
// Pass sortedData to records, not data
|
||||
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Making Tables Transparent (Widget Context)
|
||||
|
||||
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
|
||||
|
||||
Create a CSS file alongside the component:
|
||||
|
||||
```css
|
||||
/* styles.css */
|
||||
.my-table,
|
||||
.my-table .mantine-datatable-table,
|
||||
.my-table .mantine-datatable-table thead,
|
||||
.my-table .mantine-datatable-table tbody,
|
||||
.my-table .mantine-datatable-table tfoot,
|
||||
.my-table th,
|
||||
.my-table td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.my-table .mantine-datatable-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Hover: translucent instead of opaque */
|
||||
.my-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-default-hover) 40%, transparent) !important;
|
||||
}
|
||||
|
||||
/* Headers: frosted glass effect */
|
||||
.my-table th {
|
||||
white-space: nowrap;
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 60%, transparent) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Cell overflow */
|
||||
.my-table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Hide sort icon until hover */
|
||||
.my-table .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.my-table th:hover .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Row expansion area */
|
||||
.my-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: color-mix(in srgb, var(--mantine-color-body) 40%, transparent) !important;
|
||||
}
|
||||
```
|
||||
|
||||
Import it: `import "./styles.css";` and apply via `className="my-table"`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsive Column Visibility
|
||||
|
||||
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
|
||||
|
||||
```typescript
|
||||
interface SizeConfig {
|
||||
fontSize: "xs" | "sm";
|
||||
iconSize: number;
|
||||
cellPadding: number;
|
||||
showSpeedColumns: boolean;
|
||||
showTimeColumn: boolean;
|
||||
showStateColumn: boolean;
|
||||
}
|
||||
|
||||
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
|
||||
{ maxWidth: 300, config: { fontSize: "xs", iconSize: 12, cellPadding: 2, showSpeedColumns: false, showTimeColumn: false, showStateColumn: false } },
|
||||
{ maxWidth: 500, config: { fontSize: "xs", iconSize: 14, cellPadding: 4, showSpeedColumns: false, showTimeColumn: true, showStateColumn: false } },
|
||||
];
|
||||
|
||||
const DEFAULT_SIZE_CONFIG: SizeConfig = { fontSize: "sm", iconSize: 14, cellPadding: 4, showSpeedColumns: true, showTimeColumn: true, showStateColumn: true };
|
||||
|
||||
function getSizeConfig(width: number): SizeConfig {
|
||||
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
|
||||
if (width < maxWidth) return config;
|
||||
}
|
||||
return DEFAULT_SIZE_CONFIG;
|
||||
}
|
||||
|
||||
// Usage: memoize it
|
||||
const size = useMemo(() => getSizeConfig(width), [width]);
|
||||
```
|
||||
|
||||
Then use a visibility check lookup table for columns:
|
||||
|
||||
```typescript
|
||||
const columnVisibilityChecks: Partial<Record<string, (ctx: ColumnContext) => boolean>> = {
|
||||
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
|
||||
downSpeed: (ctx) => ctx.size.showSpeedColumns,
|
||||
time: (ctx) => ctx.size.showTimeColumn,
|
||||
state: (ctx) => ctx.size.showStateColumn,
|
||||
};
|
||||
|
||||
function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
|
||||
if (!ctx.optionsColumnSet.has(accessor)) return false;
|
||||
const check = columnVisibilityChecks[accessor];
|
||||
if (check && !check(ctx)) return false;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Column Persistence (Drag/Resize → Server)
|
||||
|
||||
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
|
||||
|
||||
### Hidden widget options
|
||||
|
||||
In `index.ts`, add hidden text options:
|
||||
|
||||
```typescript
|
||||
columnOrder: factory.text({ defaultValue: "" }),
|
||||
columnWidths: factory.text({ defaultValue: "" }),
|
||||
```
|
||||
|
||||
With visibility override:
|
||||
|
||||
```typescript
|
||||
{
|
||||
columnOrder: { shouldHide: () => true },
|
||||
columnWidths: { shouldHide: () => true },
|
||||
}
|
||||
```
|
||||
|
||||
### Translation keys required
|
||||
|
||||
Even hidden options need translation keys or the translation test fails:
|
||||
|
||||
```json
|
||||
"widget.mywidget.option.columnOrder.label": "Column order",
|
||||
"widget.mywidget.option.columnWidths.label": "Column widths"
|
||||
```
|
||||
|
||||
### Old-import mappings
|
||||
|
||||
Add to `packages/old-import/src/widgets/options.ts`:
|
||||
|
||||
```typescript
|
||||
columnOrder: () => undefined,
|
||||
columnWidths: () => undefined,
|
||||
```
|
||||
|
||||
### Dynamic storeKey
|
||||
|
||||
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
|
||||
|
||||
```typescript
|
||||
const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted().join(",")}`;
|
||||
```
|
||||
|
||||
### Hydration from server → localStorage
|
||||
|
||||
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
|
||||
|
||||
```typescript
|
||||
const { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } =
|
||||
useDataTableColumns<MyRow>({ key: storeKey, columns });
|
||||
|
||||
const lastStoreKey = useRef(storeKey);
|
||||
const hydrated = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastStoreKey.current !== storeKey) {
|
||||
lastStoreKey.current = storeKey;
|
||||
hydrated.current = false;
|
||||
}
|
||||
if (hydrated.current) return;
|
||||
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
|
||||
if (Object.keys(savedWidths).length > 0) {
|
||||
setMultipleColumnWidths(
|
||||
Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w })),
|
||||
);
|
||||
}
|
||||
requestAnimationFrame(() => { hydrated.current = true; });
|
||||
}, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]);
|
||||
```
|
||||
|
||||
### Persist localStorage → server
|
||||
|
||||
Watch for changes and write back:
|
||||
|
||||
```typescript
|
||||
const prevOrder = useRef(columnsOrder);
|
||||
const prevWidths = useRef(columnsWidth);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated.current) return;
|
||||
const orderChanged = JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
|
||||
const widthsChanged = JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
|
||||
prevOrder.current = columnsOrder;
|
||||
prevWidths.current = columnsWidth;
|
||||
|
||||
if (!orderChanged && !widthsChanged) return;
|
||||
|
||||
if (orderChanged) persistOption({ columnOrder: JSON.stringify(columnsOrder) });
|
||||
if (widthsChanged) {
|
||||
const widthMap: Record<string, number> = {};
|
||||
for (const entry of columnsWidth) {
|
||||
const key = Object.keys(entry)[0];
|
||||
if (!key) continue;
|
||||
const w = entry[key as keyof typeof entry];
|
||||
if (typeof w === "number") widthMap[key] = w;
|
||||
else if (typeof w === "string" && w.endsWith("px")) widthMap[key] = parseInt(w, 10);
|
||||
}
|
||||
persistOption({ columnWidths: JSON.stringify(widthMap) });
|
||||
}
|
||||
}, [columnsOrder, columnsWidth, persistOption]);
|
||||
```
|
||||
|
||||
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
|
||||
|
||||
---
|
||||
|
||||
## 5. Context Menu (Right-Click)
|
||||
|
||||
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
|
||||
|
||||
```typescript
|
||||
interface ContextMenuState {
|
||||
x: number;
|
||||
y: number;
|
||||
item: MyRow;
|
||||
}
|
||||
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
({ record, event }: { record: MyRow; event: React.MouseEvent }) => {
|
||||
event.preventDefault();
|
||||
setContextMenu({ x: event.clientX, y: event.clientY, item: record });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// On DataTable:
|
||||
<DataTable onRowContextMenu={handleContextMenu} />
|
||||
|
||||
// Render:
|
||||
{contextMenu && <RowContextMenu state={contextMenu} onClose={closeContextMenu} />}
|
||||
```
|
||||
|
||||
The menu component uses `Portal` for correct z-index:
|
||||
|
||||
```tsx
|
||||
function RowContextMenu({ state, onClose }: { state: ContextMenuState; onClose: () => void }) {
|
||||
return (
|
||||
<Portal>
|
||||
<Menu opened onClose={onClose} closeOnItemClick={false} position="right-start" offset={0}>
|
||||
<Menu.Target>
|
||||
<Box style={{ position: "fixed", left: state.x, top: state.y, width: 0, height: 0 }} />
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={() => { /* action */ onClose(); }}>Action</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Row Expansion
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
rowExpansion={{
|
||||
trigger: "click",
|
||||
allowMultiple: true,
|
||||
collapseProps: {
|
||||
transitionDuration: 200,
|
||||
animateOpacity: true,
|
||||
transitionTimingFunction: "ease-out",
|
||||
},
|
||||
content: ({ record, collapse }) => <ExpandedRow item={record} collapse={collapse} />,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
|
||||
|
||||
---
|
||||
|
||||
## 7. Common Pitfalls & Fixes
|
||||
|
||||
### Status indicator dots
|
||||
|
||||
Don't use `<Badge variant="dot" />` without children — it renders a pill with empty space. Use a plain `Box`:
|
||||
|
||||
```tsx
|
||||
<Box
|
||||
w={8}
|
||||
h={8}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
backgroundColor: `var(--mantine-color-${color}-filled)`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Tooltip overflow on long text columns
|
||||
|
||||
When a column has `ellipsis: true`, the tooltip target element can still extend beyond the cell bounds if inner elements don't clip. Fix:
|
||||
|
||||
```tsx
|
||||
<Tooltip position="bottom-start" ...>
|
||||
<Group style={{ overflow: "hidden" }}>
|
||||
...
|
||||
</Group>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
Use `position="bottom-start"` instead of `position="right"` so the tooltip never escapes the widget horizontally.
|
||||
|
||||
### Dark mode tooltips
|
||||
|
||||
Mantine tooltips default to the theme's tooltip color which may be white in light mode and stay white in dark mode. Always set `color="dark"` on informational tooltips inside widgets.
|
||||
|
||||
### Division by zero
|
||||
|
||||
Guard all ratio calculations:
|
||||
|
||||
```typescript
|
||||
if (item.size > 0) ratio = item.sent / item.size;
|
||||
if (totalDown > 0) ratio = totalUp / totalDown;
|
||||
```
|
||||
|
||||
### Composite idAccessor
|
||||
|
||||
When rows can come from multiple integrations, IDs may collide. Use a composite key:
|
||||
|
||||
```typescript
|
||||
idAccessor={(record) => `${record.integration.id}:${record.id}`}
|
||||
```
|
||||
|
||||
### JSON option parsing
|
||||
|
||||
Always wrap in try/catch:
|
||||
|
||||
```typescript
|
||||
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
|
||||
if (!value) return fallback;
|
||||
try { return JSON.parse(value) as T; }
|
||||
catch { return fallback; }
|
||||
}
|
||||
```
|
||||
|
||||
### Clipboard API
|
||||
|
||||
`navigator.clipboard.writeText()` can throw in non-HTTPS contexts. Always catch:
|
||||
|
||||
```typescript
|
||||
void navigator.clipboard.writeText(text).catch(() => {});
|
||||
```
|
||||
|
||||
### Sync sortStatus with widget options
|
||||
|
||||
If the widget has configurable default sort, sync it:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
setSortStatus({ columnAccessor: options.defaultSort, direction: defaultSortDirection });
|
||||
}, [options.defaultSort, defaultSortDirection]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Accessibility
|
||||
|
||||
- All `ActionIcon` components need `aria-label`
|
||||
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
|
||||
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
|
||||
|
||||
---
|
||||
|
||||
## 9. Error Handling
|
||||
|
||||
- Every mutation needs an `onError` callback with `showErrorNotification`
|
||||
- The `saveItemOptions` mutation (column persistence) also needs `onError`
|
||||
- Check `isError` on the main query and show a fallback UI
|
||||
- Use a shared `mutationOptions` object to avoid repeating `onSettled`/`onError`:
|
||||
|
||||
```typescript
|
||||
const mutationOptions = {
|
||||
onSettled: () => void utils.myQuery.invalidate(),
|
||||
onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }),
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. i18n
|
||||
|
||||
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
|
||||
- Column titles: `t("items.{accessor}.columnTitle")`
|
||||
- Detail labels: `t("items.{accessor}.detailsTitle")`
|
||||
- States: `t("states.{state}")`
|
||||
- Actions: `t("actions.{action}")`
|
||||
- Errors: `t("errors.{errorKey}")`
|
||||
- Hidden options still need `.label` translation keys
|
||||
|
||||
---
|
||||
|
||||
## 11. Edit Mode
|
||||
|
||||
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
|
||||
|
||||
```typescript
|
||||
let tablePointerEvents: React.CSSProperties["pointerEvents"];
|
||||
if (isEditMode) tablePointerEvents = "none";
|
||||
|
||||
let rowContextMenuHandler = handleContextMenu;
|
||||
if (isEditMode) rowContextMenuHandler = undefined;
|
||||
|
||||
<DataTable style={{ pointerEvents: tablePointerEvents }} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. DataTable Props Reference (Commonly Used)
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
records={sortedData}
|
||||
columns={effectiveColumns}
|
||||
storeColumnsKey={storeKey}
|
||||
withTableBorder={false}
|
||||
borderRadius={0}
|
||||
highlightOnHover
|
||||
striped="odd"
|
||||
stripedColor={{ dark: "dark.7", light: "gray.0" }}
|
||||
highlightOnHoverColor={{ dark: "dark.5", light: "gray.1" }}
|
||||
verticalAlign="center"
|
||||
fetching={isFetching && data.length === 0}
|
||||
loaderBackgroundBlur={2}
|
||||
fz={size.fontSize}
|
||||
textSelectionDisabled
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
noRecordsText={t("errors.noItems")}
|
||||
idAccessor={(record) => record.uniqueKey}
|
||||
height="100%"
|
||||
className="my-table"
|
||||
defaultColumnProps={{
|
||||
noWrap: true,
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
cellsStyle: () => ({ padding: `${size.cellPadding}px 8px` }),
|
||||
}}
|
||||
scrollAreaProps={{ type: "auto", scrollbarSize: 6 }}
|
||||
rowBackgroundColor={(record) => conditionalBgMap[record.state]}
|
||||
onRowContextMenu={handleContextMenu}
|
||||
rowExpansion={{ ... }}
|
||||
onScroll={() => { if (contextMenu) closeContextMenu(); }}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Layout Stability
|
||||
|
||||
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
|
||||
- Give non-name columns explicit `width` values so only the name column flexes
|
||||
- Use `ellipsis: true` on the name column — it handles `text-overflow: ellipsis` internally
|
||||
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
|
||||
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
|
||||
|
||||
---
|
||||
|
||||
## 14. Checklist for Each Migration
|
||||
|
||||
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
|
||||
2. Convert column definitions (`accessorKey` → `accessor`, `header` → `title`, `Cell` → `render`, etc.)
|
||||
3. Implement manual sorting with `useState` + `useMemo`
|
||||
4. Add CSS file for transparent backgrounds (if widget context)
|
||||
5. Add responsive column visibility (if widget context)
|
||||
6. Add hidden options + hydration logic (if column persistence needed)
|
||||
7. Add old-import mappings for new options
|
||||
8. Add translation keys (including hidden option labels)
|
||||
9. Add context menu if row actions exist
|
||||
10. Add row expansion if detail view exists
|
||||
11. Add error handling on all mutations
|
||||
12. Add accessibility attributes
|
||||
13. Test at multiple widget widths
|
||||
14. Update docs in `apps/docs/docs/widgets/` if applicable
|
||||
15. Run `pnpm turbo typecheck` — catches old-import mismatches
|
||||
16. Run translation spec — catches missing keys
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
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`
|
||||
@@ -3,6 +3,9 @@ version = 1
|
||||
[[analyzers]]
|
||||
name = "javascript"
|
||||
|
||||
[analyzers.meta]
|
||||
plugins = ["react"]
|
||||
environment = ["nodejs"]
|
||||
[analyzers.meta]
|
||||
plugins = ["react"]
|
||||
environment = ["nodejs"]
|
||||
|
||||
[[transformers]]
|
||||
name = "prettier"
|
||||
@@ -5,21 +5,7 @@ node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.turbo
|
||||
.pnpm-store
|
||||
**/.pnpm-store
|
||||
coverage
|
||||
**/coverage
|
||||
**/.docusaurus
|
||||
**/build
|
||||
.git
|
||||
.github
|
||||
dev
|
||||
.build
|
||||
e2e
|
||||
apps/docs/*
|
||||
!apps/docs/package.json
|
||||
apps/nextjs/.next
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
e2e
|
||||
@@ -12,6 +12,10 @@ AUTH_SECRET="supersecret"
|
||||
# or starting the project without any (which will show a randomly generated one).
|
||||
SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
||||
|
||||
# Cron job API key is generated every time the container starts as it is required for communication between nextjs-api and tasks-api
|
||||
# export CRON_JOB_API_KEY=$(openssl rand -base64 32)
|
||||
# CRON_JOB_API_KEY="your-generated-api-key"
|
||||
|
||||
LOG_LEVEL='info'
|
||||
|
||||
# This is how you can use the sqlite driver:
|
||||
@@ -41,50 +45,8 @@ DB_URL='FULL_PATH_TO_YOUR_SQLITE_DB_FILE'
|
||||
|
||||
TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
# Turborepo remote cache (optional, for faster CI/local builds)
|
||||
# Uncomment and fill in to enable remote caching locally.
|
||||
# CI uses GitHub secrets for these values automatically.
|
||||
# TURBO_API=https://your-cache-worker.workers.dev
|
||||
# TURBO_TEAM=team_your_team
|
||||
# TURBO_TOKEN=your-turbo-token
|
||||
# TURBO_REMOTE_CACHE_SIGNATURE_KEY=your-signature-key
|
||||
|
||||
# Comma-separated paths to Docker-compatible Unix sockets.
|
||||
# Useful for connecting to multiple runtimes (Docker + Podman) simultaneously.
|
||||
# When unset and no TCP hosts configured, defaults to /var/run/docker.sock.
|
||||
# Example: DOCKER_SOCKET_PATHS=/var/run/docker.sock,/run/user/1000/podman/podman.sock
|
||||
# DOCKER_SOCKET_PATHS=
|
||||
|
||||
# Enable kubernetes tool
|
||||
# ENABLE_KUBERNETES=true
|
||||
|
||||
# Enable mock integration
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
|
||||
# Public URL contract. Values must be absolute HTTP(S) URLs without credentials,
|
||||
# query strings, or fragments. Trailing slashes are normalized at startup.
|
||||
HOMARR_WEBSITE_URL=https://homarr.dev
|
||||
# PocketBase API used by Homarr and by the standalone documentation site.
|
||||
# Defaults to HOMARR_WEBSITE_URL.
|
||||
# WORKSHOP_API_URL=https://homarr.dev
|
||||
# Public Workshop page opened from Homarr. Defaults to HOMARR_WEBSITE_URL/workshop.
|
||||
# WORKSHOP_WEB_URL=https://homarr.dev/workshop
|
||||
|
||||
# Documentation and PocketBase deployment settings.
|
||||
# WORKSHOP_PUBLIC_ORIGIN=https://homarr.dev
|
||||
# PB_ALLOWED_ORIGINS=*
|
||||
|
||||
# Local Workshop stack:
|
||||
# HOMARR_WEBSITE_URL=http://127.0.0.1:3003
|
||||
# WORKSHOP_API_URL=http://127.0.0.1:8090
|
||||
# WORKSHOP_WEB_URL=http://127.0.0.1:3003/workshop
|
||||
# WORKSHOP_PUBLIC_ORIGIN=http://127.0.0.1:3003
|
||||
# Optional: GITHUB_CLIENT_ID=
|
||||
# Optional: GITHUB_CLIENT_SECRET=
|
||||
|
||||
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
|
||||
# DEMO_MODE=true
|
||||
|
||||
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
|
||||
# Set to false for writable preview deployments so the demo user can modify the dashboard.
|
||||
# DEMO_READ_ONLY=false
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
@@ -33,49 +33,6 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.74.0
|
||||
- 1.73.0
|
||||
- 1.72.0
|
||||
- 1.71.0
|
||||
- 1.70.0
|
||||
- 1.69.2
|
||||
- 1.69.0
|
||||
- 1.68.0
|
||||
- 1.67.0
|
||||
- 1.66.1
|
||||
- 1.66.0
|
||||
- 1.65.0
|
||||
- 1.64.0
|
||||
- 1.62.0
|
||||
- 1.61.0
|
||||
- 1.60.1-beta.1
|
||||
- 1.60.0
|
||||
- 1.59.3
|
||||
- 1.59.2
|
||||
- 1.59.1
|
||||
- 1.59.0
|
||||
- 1.58.1
|
||||
- 1.58.0
|
||||
- 1.57.1
|
||||
- 1.57.0
|
||||
- 1.56.1
|
||||
- 1.56.0
|
||||
- 1.55.0
|
||||
- 1.54.0
|
||||
- 1.53.0
|
||||
- 1.52.0
|
||||
- 1.51.0
|
||||
- 1.50.1
|
||||
- 1.50.0
|
||||
- 1.49.1
|
||||
- 1.49.0
|
||||
- 1.48.0
|
||||
- 1.47.0
|
||||
- 1.46.0
|
||||
- 1.45.3
|
||||
- 1.45.2
|
||||
- 1.45.1
|
||||
- 1.45.0
|
||||
- 1.44.0
|
||||
- 1.43.1
|
||||
- 1.43.0
|
||||
|
||||
@@ -25,16 +25,16 @@ body:
|
||||
label: Public API available?
|
||||
description: Is there a public API available, that we can consume in Homarr?
|
||||
options:
|
||||
- "Yes, available on a website"
|
||||
- "Yes, available in the application itself"
|
||||
- "No"
|
||||
- Yes, available on a website
|
||||
- Yes, available in the application itself
|
||||
- No
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Are you willing to contribute this yourself?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- Yes
|
||||
- No
|
||||
validations:
|
||||
required: true
|
||||
@@ -24,7 +24,7 @@ body:
|
||||
attributes:
|
||||
label: Are you willing to contribute this yourself?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
- Yes
|
||||
- No
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,2 +0,0 @@
|
||||
.secrets
|
||||
!.secrets.example
|
||||
@@ -1,4 +0,0 @@
|
||||
TURBO_API=
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=
|
||||
@@ -1,36 +0,0 @@
|
||||
# Running CI locally
|
||||
|
||||
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
|
||||
|
||||
```bash
|
||||
pnpm ci:act:fast
|
||||
pnpm ci:act:docker
|
||||
pnpm ci:act:all
|
||||
pnpm ci:act:cleanup
|
||||
```
|
||||
|
||||
`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 pnpm ci:act:all
|
||||
```
|
||||
|
||||
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
|
||||
|
||||
## PR preview images
|
||||
|
||||
Successful non-draft pull requests from this repository publish an amd64 image:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/homarr-labs/homarr-test:pr-<number>
|
||||
```
|
||||
|
||||
The tag is replaced when the pull request is updated and deleted when it closes. Production Homarr images remain the supported multi-architecture releases. The `homarr-test` package must be configured as public in GHCR for unauthenticated testers.
|
||||
|
||||
## Dokploy preview cache
|
||||
|
||||
Keep `needs-demo` as the Dokploy preview label. Under the application's **Preview Build-time Secrets**, configure `TURBO_API`, `TURBO_TEAM`, `TURBO_TOKEN`, and `TURBO_REMOTE_CACHE_SIGNATURE_KEY`. Dokploy passes these to the Dockerfile as BuildKit secrets. Do not configure the token or signature key as Docker build arguments.
|
||||
|
||||
Rotate any credentials that have appeared in chat, logs, or committed configuration before enabling the cache.
|
||||
@@ -1,64 +0,0 @@
|
||||
name: Extract Build Artifact
|
||||
description: Extracts artifacts from an existing Docker image to be used for from source installation
|
||||
inputs:
|
||||
digest:
|
||||
description: Digest of Docker image to use
|
||||
required: true
|
||||
architecture:
|
||||
description: Name of architecture, will be used to build artifact content (amd64 or arm64)
|
||||
required: true
|
||||
release-tag:
|
||||
description: Tag of the release to which the artifact will be attached
|
||||
required: true
|
||||
repository:
|
||||
description: Repository to which the release belongs, e.g. owner/repo
|
||||
required: true
|
||||
token:
|
||||
description: GitHub token with permissions to upload release assets
|
||||
required: true
|
||||
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 \
|
||||
-e "SECRET_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000" \
|
||||
--detach --rm ${{ inputs.digest }}
|
||||
shell: bash
|
||||
- name: Prepare extraction
|
||||
run: |
|
||||
docker exec homarr cp /etc/nginx/templates/nginx.conf /app && \
|
||||
mkdir -p ${{ runner.temp }}/extraction/${{ inputs.architecture }}
|
||||
shell: bash
|
||||
- name: Extract source from ${{ inputs.architecture }} container (alpine)
|
||||
run: |
|
||||
docker exec homarr tar -czf extraction-alpine.tar.gz -C /app . && \
|
||||
docker cp homarr:/app/extraction-alpine.tar.gz ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz && \
|
||||
docker exec homarr rm /app/extraction-alpine.tar.gz
|
||||
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
|
||||
- name: Stop ${{ inputs.architecture }} container
|
||||
if: always()
|
||||
run: docker container remove --force --volumes homarr
|
||||
shell: bash
|
||||
- name: Add build archive to release (alpine)
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
run: gh release upload --repo ${{ inputs.repository }} ${{ inputs.release-tag }} ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-alpine-${{ inputs.architecture }}.tar.gz --clobber
|
||||
shell: bash
|
||||
- name: Add build archive to release (debian)
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
run: gh release upload --repo ${{ inputs.repository }} ${{ inputs.release-tag }} ${{ runner.temp }}/extraction/${{ inputs.architecture }}/build-debian-${{ inputs.architecture }}.tar.gz --clobber
|
||||
shell: bash
|
||||
@@ -1,42 +0,0 @@
|
||||
name: Prebuilt dependencies for debian
|
||||
description: Provides prebuilt dependencies for debian based docker images
|
||||
inputs:
|
||||
architecture:
|
||||
description: Name of architecture, will be used to build docker image (e.g. amd64, arm64)
|
||||
required: true
|
||||
outputs:
|
||||
path:
|
||||
description: Path to extracted prebuilt dependencies
|
||||
value: ${{ runner.temp }}/prebuilts
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build docker image for ${{ inputs.architecture }}
|
||||
id: build
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: false
|
||||
load: true
|
||||
context: ./deployments/prebuilt-debian
|
||||
platforms: linux/${{ inputs.architecture }}
|
||||
tags: prebuilt-debian
|
||||
- name: Start docker container for ${{ inputs.architecture }}
|
||||
run: |
|
||||
docker run --name prebuilt-debian \
|
||||
--detach --rm prebuilt-debian
|
||||
shell: bash
|
||||
- name: Extract prebuilt dependencies from ${{ inputs.architecture }} container
|
||||
run: |
|
||||
mkdir -p ${{ runner.temp }}/prebuilts && \
|
||||
docker cp prebuilt-debian:/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node ${{ runner.temp }}/prebuilts/better_sqlite3.node
|
||||
shell: bash
|
||||
- name: Stop ${{ inputs.architecture }} container
|
||||
if: always()
|
||||
run: docker container remove --force --volumes prebuilt-debian
|
||||
shell: bash
|
||||
@@ -11,11 +11,3 @@
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] 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:**
|
||||
|
||||
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
|
||||
tests (e.g. what happens if it is offline)
|
||||
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
|
||||
provide support and maintain the integration (e.g. in case of breaking or failure)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
extends: ["config:recommended", ":dependencyDashboardApproval"],
|
||||
extends: ["config:recommended"],
|
||||
packageRules: [
|
||||
{
|
||||
matchPackageNames: ["^@homarr/"],
|
||||
@@ -11,18 +11,12 @@
|
||||
matchPackageNames: ["tsx"],
|
||||
enabled: false,
|
||||
},
|
||||
// Reenable once bug with undici v8 is fixed (https://github.com/homarr-labs/homarr/issues/5467)
|
||||
{
|
||||
matchPackageNames: ["undici"],
|
||||
matchNewValue: "8.*",
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
matchUpdateTypes: ["minor", "patch", "pin", "digest"],
|
||||
automerge: true,
|
||||
},
|
||||
{
|
||||
// disallow automerge for allowBuilds dependencies (native builds need manual verification)
|
||||
// disallow automerge for onlyBuiltDependencies
|
||||
matchPackageNames: [
|
||||
"@tree-sitter-grammars/tree-sitter-yaml",
|
||||
"bcrypt",
|
||||
@@ -37,17 +31,11 @@
|
||||
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,
|
||||
baseBranchPatterns: ["dev"],
|
||||
dependencyDashboard: true,
|
||||
baseBranches: ["dev"],
|
||||
dependencyDashboard: false,
|
||||
// prevent special characters in branch names (otherwise git errors occur on windows)
|
||||
branchNameStrict: true,
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Formatting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: ["*"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
format:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Check changed files
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
|
||||
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
|
||||
| xargs -0 -r pnpm exec oxfmt --check --
|
||||
@@ -0,0 +1,29 @@
|
||||
name: "[Dependency Updates] Auto Approve"
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-automatic-prs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 # Id of renovate bot and crowdin bot see https://api.github.com/users/homarr-renovate%5Bbot%5D and https://api.github.com/users/homarr-crowdin%5Bbot%5D and https://api.github.com/users/homarr-update-contributors%5Bbot%5D
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve pull request
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
- name: Approve automatic PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review ${{github.event.pull_request.number}} --approve --body "Automatically approved by GitHub Action"
|
||||
@@ -1,43 +0,0 @@
|
||||
name: "[Bots] Reconcile Auto-merge"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/30 * * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
reconcile:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Re-enable auto-merge for prepared bot PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
gh pr list --repo "${{ github.repository }}" --base dev --state open --limit 100 \
|
||||
--json number,author,reviewDecision,mergeStateStatus,autoMergeRequest \
|
||||
--jq '.[] | select(.author.login as $login | ["app/homarr-renovate", "app/homarr-crowdin", "app/homarr-update-contributors", "app/homarr-releases"] | index($login)) | select(.reviewDecision == "APPROVED") | select(.mergeStateStatus == "CLEAN") | select(.autoMergeRequest == null) | .number' \
|
||||
| while read -r pr; do
|
||||
echo "Re-enabling auto-merge on PR #$pr"
|
||||
gh pr merge "$pr" --auto --squash
|
||||
done
|
||||
@@ -1,47 +0,0 @@
|
||||
name: "[Bots] Approve and Auto-merge"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-and-automerge:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
# Extracted from https://api.github.com/users/<username>
|
||||
if: |
|
||||
(github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175441) &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.base.ref == 'dev'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve a pull request and enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Approve and enable auto-merge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json reviewDecision --jq .reviewDecision)" != "APPROVED" ]; then
|
||||
gh pr review "$PR_NUMBER" --approve --body "Automatically approved by Homarr bot policy"
|
||||
fi
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json autoMergeRequest --jq '.autoMergeRequest != null')" != "true" ]; then
|
||||
gh pr merge "$PR_NUMBER" --auto --squash
|
||||
fi
|
||||
@@ -1,407 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches: ["*"]
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches: ["*"]
|
||||
push:
|
||||
branches: [main, dev, beta, next, v2]
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
operation:
|
||||
description: Operation to run
|
||||
required: true
|
||||
default: validate
|
||||
type: choice
|
||||
options:
|
||||
- validate
|
||||
- publish
|
||||
- cleanup
|
||||
pr_number:
|
||||
description: PR number for publish or cleanup
|
||||
required: false
|
||||
type: number
|
||||
ref:
|
||||
description: Git ref to validate or publish
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# Every event for a PR shares one group so a close event cancels stale image
|
||||
# publication and a newer synchronize run cannot be overwritten by an older run.
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
SKIP_ENV_VALIDATION: true
|
||||
TURBO_TELEMETRY_DISABLED: 1
|
||||
REGISTRY: ghcr.io
|
||||
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
|
||||
|
||||
jobs:
|
||||
fast-gate:
|
||||
name: Fast gate
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
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 }}
|
||||
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:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Validate manual inputs
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'publish'
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
BUILD_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
test -n "$PR_NUMBER" || { echo "pr_number is required for publish"; exit 1; }
|
||||
test -n "$BUILD_REF" || { echo "ref is required for publish"; exit 1; }
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
|
||||
- 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: pnpm lint:ws
|
||||
|
||||
- name: Validate Custom Widget architecture
|
||||
run: pnpm check:custom-widgets
|
||||
|
||||
- name: Validate checked-in OpenAPI schema
|
||||
run: pnpm check:openapi
|
||||
|
||||
- name: Validate Custom Widget bundle budget
|
||||
run: |
|
||||
pnpm --filter nextjs build
|
||||
pnpm check:custom-widget-bundle
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
container:
|
||||
name: Container and E2E
|
||||
needs: fast-gate
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(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:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build container once
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
push: false
|
||||
tags: |
|
||||
homarr-ci
|
||||
homarr-e2e
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.ref.name=pr-${{ github.event.pull_request.number || inputs.pr_number || 'none' }}
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
cache-to: type=gha,scope=homarr-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 }}
|
||||
|
||||
- name: Build container once 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: |
|
||||
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"
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm test:e2e
|
||||
|
||||
- name: Upload E2E artifacts
|
||||
if: failure() && !env.ACT
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Log in to GHCR for preview publication
|
||||
if: |
|
||||
!env.ACT &&
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push amd64 preview image by digest
|
||||
id: preview-build
|
||||
if: |
|
||||
!env.ACT &&
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
- name: Show Act publication result
|
||||
if: env.ACT
|
||||
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
|
||||
|
||||
preview-arm64:
|
||||
name: Build arm64 preview image
|
||||
needs: fast-gate
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push arm64 preview image by digest
|
||||
id: preview-build
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/arm64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-arm64
|
||||
cache-to: type=gha,scope=homarr-linux-arm64,mode=max
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
publish-preview:
|
||||
name: Publish multi-platform preview image
|
||||
needs: [container, preview-arm64]
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
env:
|
||||
PREVIEW_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
steps:
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Verify PR head before publication
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
with:
|
||||
script: |
|
||||
const { data: pull } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
});
|
||||
if (pull.state !== "open") {
|
||||
core.setFailed(`PR #${pull.number} is ${pull.state}; refusing to publish its preview.`);
|
||||
return;
|
||||
}
|
||||
if (pull.head.sha !== process.env.EXPECTED_HEAD_SHA) {
|
||||
core.setFailed(`PR #${pull.number} moved from ${process.env.EXPECTED_HEAD_SHA} to ${pull.head.sha}.`);
|
||||
}
|
||||
|
||||
- name: Publish preview manifest
|
||||
if: "!env.ACT"
|
||||
env:
|
||||
AMD64_DIGEST: ${{ needs.container.outputs.digest }}
|
||||
ARM64_DIGEST: ${{ needs.preview-arm64.outputs.digest }}
|
||||
run: |
|
||||
docker buildx imagetools create -t "${PREVIEW_IMAGE}:${PREVIEW_TAG}" \
|
||||
"${PREVIEW_IMAGE}@${AMD64_DIGEST}" \
|
||||
"${PREVIEW_IMAGE}@${ARM64_DIGEST}"
|
||||
|
||||
- name: Verify preview manifest platforms
|
||||
if: "!env.ACT"
|
||||
run: |
|
||||
image="${PREVIEW_IMAGE}:${PREVIEW_TAG}"
|
||||
manifest="$(docker buildx imagetools inspect "$image" --raw)"
|
||||
for architecture in amd64 arm64; do
|
||||
jq --exit-status --arg architecture "$architecture" \
|
||||
'any(.manifests[]; .platform.os == "linux" and .platform.architecture == $architecture)' \
|
||||
<<<"$manifest" >/dev/null
|
||||
done
|
||||
docker buildx imagetools inspect "$image"
|
||||
|
||||
cleanup:
|
||||
name: Clean up preview image
|
||||
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
packages: write
|
||||
env:
|
||||
IMAGE_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
steps:
|
||||
- name: Validate cleanup input
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
run: test -n "$PR_NUMBER" || { echo "pr_number is required for cleanup"; exit 1; }
|
||||
|
||||
- name: Delete GHCR package version
|
||||
if: "!env.ACT"
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const packageName = "homarr-test";
|
||||
const org = context.repo.owner;
|
||||
const tag = process.env.IMAGE_TAG;
|
||||
const versions = await github.paginate(
|
||||
github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg,
|
||||
{ package_type: "container", package_name: packageName, org, per_page: 100 },
|
||||
);
|
||||
const target = versions.find((version) =>
|
||||
version.metadata?.container?.tags?.includes(tag),
|
||||
);
|
||||
if (!target) {
|
||||
core.info(`No package version found with tag "${tag}"`);
|
||||
return;
|
||||
}
|
||||
await github.rest.packages.deletePackageVersionForOrg({
|
||||
package_type: "container",
|
||||
package_name: packageName,
|
||||
org,
|
||||
package_version_id: target.id,
|
||||
});
|
||||
core.info(`Deleted package version ${target.id} (${tag})`);
|
||||
|
||||
- name: Show Act cleanup result
|
||||
if: env.ACT
|
||||
run: echo "Act validated cleanup for ${IMAGE_TAG}; external GHCR writes are disabled locally."
|
||||
@@ -0,0 +1,112 @@
|
||||
name: "[Quality] Code Analysis"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
push:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
# You can leverage Vercel Remote Caching with Turbo to speed up your builds
|
||||
# @link https://turborepo.org/docs/core-concepts/remote-caching#remote-caching-on-vercel-builds
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Copy env
|
||||
shell: bash
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint && pnpm lint:ws
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Format
|
||||
run: pnpm format
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Typecheck
|
||||
run: turbo typecheck
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
- name: "Report Coverage"
|
||||
# Set if: always() to also generate the report if tests are failing
|
||||
# Only works if you set `reportOnFailure: true` in your vite config as specified above
|
||||
if: always()
|
||||
uses: davelosert/vitest-coverage-report-action@v2
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Build docker image
|
||||
id: build-docker-image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
tags: homarr-e2e
|
||||
network: host
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Install playwright browsers
|
||||
run: pnpm exec playwright install chromium
|
||||
- name: Run E2E Tests
|
||||
shell: bash
|
||||
run: pnpm test:e2e
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Copy env
|
||||
shell: bash
|
||||
run: cp .env.example .env
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
@@ -13,8 +13,7 @@ permissions:
|
||||
jobs:
|
||||
validate-pull-request-title:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -10,19 +10,18 @@ permissions:
|
||||
|
||||
jobs:
|
||||
download-crowdin-translations:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.CROWDIN_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.CROWDIN_APP_ID }}
|
||||
app-id: ${{ secrets.CROWDIN_APP_ID }}
|
||||
permission-contents: write # required to commit to crowdin branch
|
||||
permission-pull-requests: write # required to create pull request
|
||||
|
||||
|
||||
@@ -14,11 +14,10 @@ jobs:
|
||||
# Don't run this action if the downloaded translations are being pushed
|
||||
if: "!contains(github.event.head_commit.message, 'chore(lang)')"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Upload Crowdin translations
|
||||
uses: crowdin/github-action@v2
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
name: "[Deployment] Beta Release"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
send-notifications:
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
description: Send notifications
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Next Version
|
||||
id: semver
|
||||
uses: ietf-tools/semver-action@v1
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
branch: next
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
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: ${{ github.event.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "Created a release PR ${{ steps.create-pull-request.outputs.url }} for version ${{ steps.semver.outputs.next }}-beta (new behaviour: ${{ steps.semver.outputs.bump }})"
|
||||
- name: Obtain token
|
||||
id: obtainApprovalToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainApprovalToken.outputs.token }}
|
||||
run: |
|
||||
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: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # write to beta branch (due to merge)
|
||||
permission-pull-requests: write # merge pull request
|
||||
- id: automerge
|
||||
if: ${{ steps.semver.outputs.bump != 'major' }}
|
||||
name: automerge
|
||||
uses: "pascalgn/automerge-action@v0.16.4"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainMergeToken.outputs.token }}
|
||||
MERGE_METHOD: merge # we prefer merge commits for merging to beta
|
||||
MERGE_COMMIT_MESSAGE: "chore(release): release ${{ steps.semver.outputs.next }}-beta"
|
||||
MERGE_DELETE_BRANCH: false # never set to true!
|
||||
PULL_REQUEST: "${{ steps.create-pull-request.outputs.pr_number }}"
|
||||
MERGE_RETRIES: 20 # 20 retries * MERGE_RETRY_SLEEP until step fails
|
||||
MERGE_RETRY_SLEEP: 10000 # 10 seconds * MERGE_RETRIES until step fails
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
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' && 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() && github.event.inputs.send-notifications
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
with:
|
||||
args: "The beta release workflow [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) has failed"
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- beta
|
||||
- next
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -33,13 +32,12 @@ jobs:
|
||||
release:
|
||||
name: Create tag and release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' || github.ref_name == 'next' }}
|
||||
SKIP_RELEASE: ${{ github.event_name == 'workflow_dispatch' || github.ref_name == 'dev' }}
|
||||
outputs:
|
||||
version: ${{ steps.read-semver.outputs.version || steps.version-fallback.outputs.version }}
|
||||
git_ref: ${{ steps.read-git-ref.outputs.ref || github.ref }}
|
||||
skipped: ${{ env.SKIP_RELEASE }}
|
||||
|
||||
steps:
|
||||
- run: echo "Skipping release for workflow_dispatch event"
|
||||
if: env.SKIP_RELEASE == 'true'
|
||||
@@ -51,23 +49,23 @@ jobs:
|
||||
- name: Obtain token
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
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
|
||||
app-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to commit package.json & changelog changes, merge them to dev and publish the release
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v5
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: pnpm/action-setup@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- uses: actions/setup-node@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
node-version: 24.11.1
|
||||
cache: "pnpm"
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- name: Install dependencies
|
||||
@@ -96,161 +94,109 @@ jobs:
|
||||
id: read-git-ref
|
||||
run: |
|
||||
echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
- name: Update dev / next branch
|
||||
- name: Update dev branch
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
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' }}
|
||||
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:${{ 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 fetch origin dev
|
||||
git checkout dev
|
||||
git pull origin dev
|
||||
git rebase ${{ github.ref_name }}
|
||||
git push origin ${{ env.UPSTREAM_BRANCH }}
|
||||
git push origin dev
|
||||
build-amd64:
|
||||
name: Build docker image for amd64
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: "${{ env.GHCR_REPO }}"
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
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 }}
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
|
||||
build-arm64:
|
||||
name: Build docker image for arm64
|
||||
needs: release
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: "${{ env.GHCR_REPO }}"
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/arm64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
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 }}
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
extract-asset-amd64:
|
||||
name: Extract amd64 asset from docker image
|
||||
needs: [release, build-amd64]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Extract amd64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }}"
|
||||
architecture: amd64
|
||||
release-tag: ${{ needs.release.outputs.version }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
|
||||
extract-asset-arm64:
|
||||
name: Extract arm64 asset from docker image
|
||||
needs: [release, build-arm64]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Extract arm64
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
uses: homarr-labs/homarr/.github/actions/extract-build-artifact@dev
|
||||
with:
|
||||
digest: "${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}"
|
||||
architecture: arm64
|
||||
release-tag: ${{ needs.release.outputs.version }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: ${{ github.repository }}
|
||||
publish:
|
||||
name: Complete deployment and notify
|
||||
needs: [release, build-amd64, build-arm64, extract-asset-amd64, extract-asset-arm64]
|
||||
needs: [release, build-amd64, build-arm64]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
NEXT_VERSION: ${{ needs.release.outputs.version }}
|
||||
DEPLOY_LATEST: ${{ github.ref_name == 'main' }}
|
||||
DEPLOY_BETA: ${{ github.ref_name == 'beta' }}
|
||||
steps:
|
||||
- name: Obtain token
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to publish the release
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -276,16 +222,9 @@ jobs:
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-amd64.outputs.digest }} \
|
||||
${{ env.GHCR_REPO }}@${{ needs.build-arm64.outputs.digest }}
|
||||
|
||||
- name: Publish release
|
||||
if: needs.release.outputs.skipped == 'false'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh release edit --repo ${{ github.repository }} ${{ needs.release.outputs.version }} --draft=false
|
||||
|
||||
- name: Discord notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: "Successfully deployed images for branch **${{ github.ref_name }}**. Tagged as **${{env.NEXT_VERSION}}**."
|
||||
@@ -1,56 +0,0 @@
|
||||
name: "[Deployment] Documentation"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev"]
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "packages/definitions/src/docs/**"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Build docs
|
||||
run: pnpm turbo build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: "apps/docs/build"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -3,7 +3,6 @@ name: "[Deployment] Automatic Weekly Release"
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 19 * * 5" # https://crontab.guru/#0_19_*_*_5
|
||||
timezone: Europe/Zurich
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
send-notifications:
|
||||
@@ -19,17 +18,16 @@ permissions:
|
||||
jobs:
|
||||
create-and-merge-pr:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- name: Discord notification
|
||||
if: ${{ github.event.inputs.send-notifications }}
|
||||
if: ${{ github.events.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Next Version
|
||||
@@ -39,23 +37,22 @@ jobs:
|
||||
token: ${{ github.token }}
|
||||
branch: dev
|
||||
- name: Create pull request
|
||||
id: create-pull-request
|
||||
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: ${{ github.event.inputs.send-notifications }}
|
||||
if: ${{ github.events.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
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
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
app-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write
|
||||
- name: Approve PR
|
||||
env:
|
||||
@@ -64,10 +61,10 @@ jobs:
|
||||
gh pr review --approve --body "Automatically approved by GitHub Action"
|
||||
- name: Obtain token
|
||||
id: obtainMergeToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
app-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
|
||||
@@ -76,7 +73,7 @@ jobs:
|
||||
uses: "pascalgn/automerge-action@v0.16.4"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainMergeToken.outputs.token }}
|
||||
MERGE_METHOD: merge # we prefer merge commits for merging to main
|
||||
MERGE_METHOD: merge # we prefer merge commits for merging to master
|
||||
MERGE_COMMIT_MESSAGE: "chore(release): automatic release ${{ steps.semver.outputs.next }}"
|
||||
MERGE_DELETE_BRANCH: false # never set to true!
|
||||
PULL_REQUEST: "${{ steps.create-pull-request.outputs.pr_number }}"
|
||||
@@ -85,23 +82,23 @@ jobs:
|
||||
MERGE_REQUIRED_APPROVALS: 0 # do not require approvals
|
||||
|
||||
- name: Merged Discord notification
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.event.inputs.send-notifications }}
|
||||
if: ${{ steps.automerge.outputs.mergeResult == 'merged' && github.events.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
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.inputs.send-notifications }}
|
||||
if: ${{ steps.semver.outputs.bump == 'major' && github.events.inputs.send-notifications }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
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.inputs.send-notifications
|
||||
if: failure() && github.events.inputs.send-notifications
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@0.4.0
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: "The automatic release workflow [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) has failed"
|
||||
@@ -1,58 +0,0 @@
|
||||
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
|
||||
@@ -1,39 +0,0 @@
|
||||
name: "[Docs] Update Contributors"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 12 * * FRI"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
HOMARR_CROWDIN_TOKEN: "${{ secrets.HOMARR_CROWDIN_TOKEN }}"
|
||||
HOMARR_LABS_CROWDIN_TOKEN: "${{ secrets.HOMARR_LABS_CROWDIN_TOKEN }}"
|
||||
SKIP_ENV_VALIDATION: true
|
||||
|
||||
jobs:
|
||||
dump:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Dump contributors
|
||||
working-directory: apps/docs
|
||||
run: pnpm api-dump-contributors
|
||||
|
||||
- name: Commit updated contributors
|
||||
uses: EndBug/add-and-commit@v10
|
||||
with:
|
||||
add: "apps/docs/static"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update contributors"
|
||||
push: true
|
||||
@@ -1,66 +0,0 @@
|
||||
name: "[Docs] Update OpenAPI Spec"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
|
||||
jobs:
|
||||
update-spec:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Copy env
|
||||
run: cp .env.example .env
|
||||
|
||||
- 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/static/api/"
|
||||
default_author: github_actor
|
||||
message: "chore(docs): update OpenAPI spec"
|
||||
push: true
|
||||
@@ -1,45 +0,0 @@
|
||||
name: "[Deployments] Validate prebuilt debian dependencies"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
paths: [".github/actions/prebuilt-debian/**", "deployments/prebuilt-debian/**"]
|
||||
|
||||
jobs:
|
||||
prebuilt-debian-validate-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Validate prebuilt dependencies for amd64
|
||||
id: validate-amd64
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
with:
|
||||
architecture: amd64
|
||||
|
||||
- name: Check extracted files for amd64
|
||||
run: |
|
||||
if [ ! -f "${{ steps.validate-amd64.outputs.path }}/better_sqlite3.node" ]; then
|
||||
echo "better_sqlite3.node not found for amd64!"
|
||||
exit 1
|
||||
fi
|
||||
prebuilt-debian-validate-arm64:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Validate prebuilt dependencies for arm64
|
||||
id: validate-arm64
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
with:
|
||||
architecture: arm64
|
||||
|
||||
- name: Check extracted files for arm64
|
||||
run: |
|
||||
if [ ! -f "${{ steps.validate-arm64.outputs.path }}/better_sqlite3.node" ]; then
|
||||
echo "better_sqlite3.node not found for arm64!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -11,9 +11,8 @@ on:
|
||||
jobs:
|
||||
renovate-validate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v5
|
||||
- run: |
|
||||
npx --yes --package renovate -- \
|
||||
renovate-config-validator --strict .github/renovate.json5
|
||||
@@ -2,20 +2,19 @@ permissions: {}
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
types: [published]
|
||||
|
||||
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
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_DOCS_RELEASE_APP_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||
app-id: ${{ vars.HOMARR_DOCS_RELEASE_APP_ID }}
|
||||
owner: homarr-labs
|
||||
repositories: |
|
||||
documentation
|
||||
@@ -37,38 +36,39 @@ jobs:
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
update-bug-report-template:
|
||||
name: Update Bug Report Template
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_MERGE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
app-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
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
ref: dev
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- run: pnpm run scripts:update-bug-report-template
|
||||
env:
|
||||
NEXT_VERSION: ${{ github.event.release.tag_name }}
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.email "175486441+homarr-releases[bot]@users.noreply.github.com"
|
||||
git config --global user.name "Releases Homarr"
|
||||
git checkout -b update-bug-report-template
|
||||
git add .
|
||||
git commit -m "chore: update bug report template"
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
add-paths: .github/ISSUE_TEMPLATE/bug_report.yml
|
||||
commit-message: "chore: update bug report template"
|
||||
author: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com>
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-bug-report-template
|
||||
base: dev
|
||||
|
||||
@@ -7,10 +7,9 @@ on:
|
||||
|
||||
jobs:
|
||||
skip-stability-days:
|
||||
if: ${{ !startsWith(github.head_ref, 'renovate/') && github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
|
||||
name: Skip Stability Days
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- name: Add status check
|
||||
env:
|
||||
|
||||
@@ -14,26 +14,25 @@ permissions:
|
||||
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
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
app-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 repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -54,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-contributors
|
||||
|
||||
@@ -17,18 +17,17 @@ jobs:
|
||||
group: update-integration
|
||||
cancel-in-progress: false
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
private-key: ${{ secrets.HOMARR_UPDATE_CONTRIBUTORS_PRIVATE_KEY }}
|
||||
client-id: ${{ vars.HOMARR_UPDATE_CONTRIBUTORS_APP_ID }}
|
||||
app-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
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
@@ -45,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
branch: update-integrations-readme
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
name: Workshop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [dev, v2, main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
run_install: false
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm --filter @homarr/workshop typecheck
|
||||
- run: pnpm --filter @homarr/docs typecheck
|
||||
- run: docker compose --env-file apps/workshop/.env.staging.example -f apps/workshop/docker-compose.staging.yml config --quiet
|
||||
- run: pnpm test:workshop
|
||||
- run: pnpm test:workshop-image
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push' && (github.ref_name == 'v2' || github.ref_name == 'main')
|
||||
needs: validate
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: docker/setup-qemu-action@v4
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/metadata-action@v6
|
||||
id: metadata
|
||||
with:
|
||||
images: ghcr.io/homarr-labs/workshop
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=long
|
||||
type=raw,value=v2,enable=${{ github.ref_name == 'v2' }}
|
||||
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
|
||||
- uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: apps/workshop/Dockerfile
|
||||
target: production
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.metadata.outputs.tags }}
|
||||
labels: ${{ steps.metadata.outputs.labels }}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnpm-store/
|
||||
.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
@@ -16,7 +15,6 @@ out/
|
||||
next-env.d.ts
|
||||
|
||||
# artifacts
|
||||
.ai-evaluations/
|
||||
packages/db/migrations/*/migrate.cjs
|
||||
|
||||
# nitro
|
||||
@@ -68,19 +66,5 @@ e2e/shared/tmp
|
||||
#personal backgrounds
|
||||
apps/nextjs/public/images/background.png
|
||||
|
||||
|
||||
|
||||
# personal agent config
|
||||
.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
|
||||
.pnpm-store
|
||||
benchmark-results
|
||||
# next-intl
|
||||
en.d.json.ts
|
||||
@@ -1,116 +0,0 @@
|
||||
---
|
||||
target: Workshop store in the docs
|
||||
total_score: 20
|
||||
p0_count: 1
|
||||
p1_count: 3
|
||||
timestamp: 2026-07-21T11-32-50Z
|
||||
slug: apps-docs-src-components-workshop
|
||||
---
|
||||
|
||||
# Workshop Store Critique
|
||||
|
||||
## Design Health Score
|
||||
|
||||
| # | Heuristic | Score | Key issue |
|
||||
| --------- | ----------------------------------- | --------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Visibility of system status | 1/4 | Fetch failure can show beside an empty-store message, so the system communicates two incompatible states. |
|
||||
| 2 | Match between system and real world | 3/4 | Widget, CSS, voting, revisions, and comments use familiar language. |
|
||||
| 3 | User control and freedom | 2/4 | Filters are reversible, but the crashed route and failed fetch state offer no useful recovery. |
|
||||
| 4 | Consistency and standards | 2/4 | The submission editor uses CodeMirror, while revision editing falls back to a textarea; several tiny custom controls do not follow the shared button vocabulary. |
|
||||
| 5 | Error prevention | 2/4 | Submission validation exists, but disabled progression does not explain what is missing and destructive actions use a brief inline confirmation. |
|
||||
| 6 | Recognition rather than recall | 3/4 | Labels, type choices, and the submission stepper are understandable without documentation. |
|
||||
| 7 | Flexibility and efficiency | 2/4 | Search covers titles only, signed-out users can select Yours, and installation is reduced to ambiguous Copy and Download actions. |
|
||||
| 8 | Aesthetic and minimalist design | 3/4 | The restrained visual system is clean, but the browse toolbar and card action rows become dense. |
|
||||
| 9 | Help users recover from errors | 0/4 | The production route crashes with React error 130; the fetch error has no retry action or useful context. |
|
||||
| 10 | Help and documentation | 2/4 | The editor explains its fields, but the store does not teach the full inspect, trust, download, and install journey. |
|
||||
| **Total** | | **20/40** | **Needs work before release** |
|
||||
|
||||
## Anti-Patterns Verdict
|
||||
|
||||
The surface does not look like obvious AI-generated decoration. Its visual restraint, familiar controls, and practical editor are credible product UI. The weakness is product behavior rather than styling: contradictory states, tiny action controls, an overloaded responsive toolbar, and a primary installation journey that is not explicit.
|
||||
|
||||
The deterministic Impeccable scan returned zero findings for `apps/docs/src/components/workshop`. This is useful but incomplete. The detector did not catch runtime failures, semantic state conflicts, touch-target sizing, or responsive grouping. Browser evidence found a full route crash, React error 130, and an earlier state that rendered both `Something went wrong.` and `No submissions yet`.
|
||||
|
||||
No visual overlay was injected because the available browser evaluation surface is read-only. Screenshots, DOM snapshots, console logs, and source inspection were used instead.
|
||||
|
||||
## Overall Impression
|
||||
|
||||
The submission modal is now one of the stronger parts of the Workshop. The store around it is not release-ready. The single biggest opportunity is to make discovery and installation a dependable, explicit path: load reliably, show one truthful state, help users evaluate a submission, and give them one obvious next action.
|
||||
|
||||
## What Is Working
|
||||
|
||||
1. The visual system is restrained and consistent with the Homarr documentation. Typography, color, radii, and spacing do not compete with the content.
|
||||
2. The submission flow chunks a complex task into Type, Details, and Media. The shared JSON/CSS editor, drag-and-drop import, metadata autofill, and capped media previews reduce creator effort.
|
||||
3. The detail design exposes revision, author, reports, changelog, source content, and comments. This supports inspection rather than treating community code as a blind install.
|
||||
|
||||
## Priority Issues
|
||||
|
||||
### P0: The Workshop route crashes in the production docs build
|
||||
|
||||
**Why it matters:** No visual polish matters if the store becomes a Docusaurus error page. A fresh independent tab consistently reached `This page crashed` with React error 130. The console attributed the thrown error to the Kapa widget bundle.
|
||||
|
||||
**Fix:** Isolate or disable the Kapa script on Workshop routes, prevent third-party widget failures from reaching the Docusaurus route boundary, and add a route-level Workshop boundary that preserves navigation and retry. Reproduce against the built Docker docs image, not only the development server.
|
||||
|
||||
**Suggested command:** `$impeccable harden`
|
||||
|
||||
### P1: Failure and empty states contradict each other
|
||||
|
||||
**Why it matters:** Showing `Something went wrong.` followed by `No submissions yet` makes users believe the store is both broken and empty. The generic message gives no cause and no recovery.
|
||||
|
||||
**Fix:** Make loading, failure, empty, and results mutually exclusive. The failure state should say that listings could not be loaded, preserve the current filters, and provide `Try loading again`. Do not render filters or an empty store as authoritative data after the initial request fails.
|
||||
|
||||
**Suggested command:** `$impeccable harden`
|
||||
|
||||
### P1: Mobile discovery controls are too dense and do not form a responsive hierarchy
|
||||
|
||||
**Why it matters:** The toolbar's inner row contains four type choices, sorting, and outdated visibility without wrapping. Most controls are 24 to 28 pixels tall. At narrow widths this is difficult to scan, easy to clip, and below a comfortable touch target.
|
||||
|
||||
**Fix:** Put a full-width search first on mobile, keep All, Widgets, and CSS as the primary segmented choice, move sorting and outdated visibility into a compact secondary row or popover, and hide Yours when signed out or make it a sign-in affordance. Use at least 40 to 44 pixel touch targets. Add `aria-pressed` or proper tab semantics to the type filter.
|
||||
|
||||
**Suggested command:** `$impeccable adapt`
|
||||
|
||||
### P1: The store does not make the install journey obvious
|
||||
|
||||
**Why it matters:** A first-time user sees Copy and Download without knowing which is recommended, what will be copied, how to inspect safety, or where the file goes in Homarr. This weakens the store's main purpose.
|
||||
|
||||
**Fix:** Make the detail page the decision surface. Use one primary action named for its result, such as `Download widget JSON` or `Copy CSS`, with a short `Install in Homarr` explanation linked to the correct in-app flow. Cards should prioritize title, preview, compatibility, score, and `View details`; secondary utilities should not compete with evaluation.
|
||||
|
||||
**Suggested command:** `$impeccable clarify`
|
||||
|
||||
### P2: Card and moderation actions are too small and too numerous
|
||||
|
||||
**Why it matters:** Vote, copy, download, comment, delete, and report controls compete in a compact footer. Low-contrast icon buttons are hard to discover and hard to use by touch. CSS columns also create a top-to-bottom visual reading order that can differ from the expected row order.
|
||||
|
||||
**Fix:** Use a regular responsive grid, remove hover scaling, keep one or two visible card actions, and move owner/moderation actions to a menu. Keep voting visible but increase its target size. Correct the report copy: reports and reporter identities are moderation data, not public social proof.
|
||||
|
||||
**Suggested command:** `$impeccable distill`
|
||||
|
||||
## Persona Red Flags
|
||||
|
||||
### Jordan, first-time self-hoster
|
||||
|
||||
Jordan opens Workshop to install a widget. The route can crash before any listing appears. In the partial failure state, Jordan sees both an error and an empty store. If listings load, Copy and Download do not explain the Homarr destination or recommended path. Jordan is likely to leave before installing anything.
|
||||
|
||||
### Alex, experienced Homarr administrator
|
||||
|
||||
Alex can understand the JSON and appreciates the source viewer, but title-only search and limited filtering make a larger catalog inefficient. Compatibility and required secret information are not prominent at browse time. Tiny moderation and owner controls slow repeated management tasks.
|
||||
|
||||
### Morgan, mobile user
|
||||
|
||||
Morgan encounters a toolbar with more controls than fit comfortably in one decision group, 24 to 28 pixel targets, and a signed-out Yours filter that leads to an unhelpful no-results state. Submission cards add another dense row of small actions.
|
||||
|
||||
## Minor Observations
|
||||
|
||||
- Future submission steps are focusable buttons even when clicking them does nothing. Disable them or render non-interactive progress markers.
|
||||
- The report textarea relies on placeholder text and needs a persistent accessible label.
|
||||
- The revision editor should reuse the same JSON/CSS CodeMirror component as creation.
|
||||
- Search should include title, description, and author at minimum.
|
||||
- The timed inline `Confirm?` deletion pattern is easy to miss and weak for keyboard and touch users.
|
||||
- The detail-page changelog uses an uppercase tracked label, which is unnecessary in this otherwise restrained interface.
|
||||
- Loading comments should use the same skeleton vocabulary as listings rather than centered status text.
|
||||
|
||||
## Questions to Consider
|
||||
|
||||
1. Is Workshop primarily a catalog for discovery, or a technical exchange for copying source? The primary card and detail actions should commit to one answer.
|
||||
2. Should users evaluate compatibility and required secrets before opening a listing, or only on the detail page?
|
||||
3. Does Workshop need every moderation and ownership action visible inline, or should those actions live behind a menu?
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
target: Workshop store in the docs
|
||||
total_score: 38
|
||||
p0_count: 0
|
||||
p1_count: 0
|
||||
timestamp: 2026-07-21T12-29-41Z
|
||||
slug: apps-docs-src-components-workshop
|
||||
---
|
||||
|
||||
# Workshop Store Critique — Post-Polish
|
||||
|
||||
## Design Health Score
|
||||
|
||||
| # | Heuristic | Score | Evidence |
|
||||
| --------- | ----------------------------------- | --------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Visibility of system status | 4/4 | Loading, initial failure, stale refresh failure, empty, and results states are mutually exclusive; recovery actions are visible. |
|
||||
| 2 | Match between system and real world | 4/4 | Actions describe their result: Download widget JSON, Copy CSS, View details, and Install in Homarr. |
|
||||
| 3 | User control and freedom | 4/4 | Filters can be cleared, errors retried, dialogs cancelled, and destructive submission deletion requires explicit confirmation. |
|
||||
| 4 | Consistency and standards | 4/4 | Creation and revision editing share CodeMirror, buttons follow the shared system, and cards use a predictable responsive grid. |
|
||||
| 5 | Error prevention | 3/4 | Validation, disabled progression, pending states, RLS, and confirmations are strong; disabled form progression could explain unmet requirements more explicitly. |
|
||||
| 6 | Recognition rather than recall | 4/4 | Persistent labels, type descriptions, step status, source previews, and installation instructions minimize recall. |
|
||||
| 7 | Flexibility and efficiency | 4/4 | Search spans title, description, and author; signed-in filtering, sorting, responsive controls, and direct detail actions support repeat use. |
|
||||
| 8 | Aesthetic and minimalist design | 3/4 | The catalog is restrained and scannable; long source documents and the complete creator form necessarily remain dense on small screens. |
|
||||
| 9 | Help users recover from errors | 4/4 | Route, listing, edit, report, copy, OAuth, and detail failures remain local and provide a clear next action. |
|
||||
| 10 | Help and documentation | 4/4 | The detail page explains the exact Homarr destination and links to updated Workshop documentation. |
|
||||
| **Total** | | **38/40** | **Release-quality; minor polish remains** |
|
||||
|
||||
## Anti-Patterns Verdict
|
||||
|
||||
The surface is product-led rather than decorative. The redesign removes the overloaded card footer, hover scaling, tiny touch controls, public moderation details, contradictory states, and ambiguous install actions. The deterministic Impeccable detector reports zero findings.
|
||||
|
||||
## What Improved
|
||||
|
||||
1. The production route no longer collapses into the Docusaurus crash screen without recovery. A route-local boundary protects navigation, and the fresh-volume production path renders without console errors.
|
||||
2. Loading, failure, empty, stale-results, and populated states are truthful and mutually exclusive.
|
||||
3. Search and filters form a clear responsive hierarchy. Mobile targets are 40–44 pixels, signed-out users do not see Yours, and cards no longer overflow at 390 pixels.
|
||||
4. Cards prioritize evaluation: title, author, type, score, description, comments, and View details. Copy, download, reporting, editing, and deletion moved to the detail page.
|
||||
5. The detail page now teaches the installation journey and names its primary action precisely.
|
||||
6. Reports and reporter identities are admin-only through PocketBase rules. Public catalog UI no longer turns reports into social proof.
|
||||
7. The creator and revision dialogs share the CodeMirror editor, use bounded internal scrolling, expose pending/error states, and avoid step-selection layout shifts.
|
||||
|
||||
## Remaining Minor Opportunities
|
||||
|
||||
### P2: Explain disabled progression inline
|
||||
|
||||
The Type and Details steps are understandable, but a disabled Next button could state the missing requirement for keyboard and first-time users. This is a refinement, not a blocker, because required fields and validation messages are already present.
|
||||
|
||||
### P2: Consider a compact source summary on very small screens
|
||||
|
||||
The read-only source viewer is intentionally complete and horizontally scrollable. A future collapse/expand summary could reduce page length for nontechnical visitors without hiding inspection entirely.
|
||||
|
||||
## Verification Evidence
|
||||
|
||||
- Fresh Docker Compose PocketBase volume and production docs image.
|
||||
- Populated catalog with widget, CSS, and outdated examples.
|
||||
- Desktop and 390px mobile layouts in light and dark themes.
|
||||
- Widget detail, installation instructions, CodeMirror source view, and comments.
|
||||
- Signed-in submission wizard through Type and Details, including the shared editor.
|
||||
- Zero browser console errors after the final fixes.
|
||||
- Impeccable deterministic detector: zero findings.
|
||||
|
||||
## Compatibility Note
|
||||
|
||||
An existing local development volume created from an earlier unreleased version of the reset migration still has a stale Workshop view. It is intentionally not migrated or deleted. A fresh volume created from the current unreleased migration is the supported validation target and works correctly.
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
target: Homarr Workshop docs UI
|
||||
total_score: 27
|
||||
p0_count: 0
|
||||
p1_count: 2
|
||||
timestamp: 2026-07-22T15-58-15Z
|
||||
slug: apps-docs-src-components-workshop
|
||||
---
|
||||
|
||||
## Design Health Score
|
||||
|
||||
| # | Heuristic | Score | Key Issue |
|
||||
| --------- | ------------------------------- | --------: | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Visibility of System Status | 3 | Strong loading, retry, stale-data, pending, and copy states; successful report/edit actions still end silently. |
|
||||
| 2 | Match System / Real World | 3 | Core Workshop language is clear, but `networkScope`, query/action, and schema terminology assumes technical fluency. |
|
||||
| 3 | User Control and Freedom | 3 | Back, cancel, clear filters, vote toggling, and destructive confirmations are present; destructive actions have no undo. |
|
||||
| 4 | Consistency and Standards | 3 | Shared primitives are coherent, though Docusaurus chrome and the denser app-store vocabulary still feel slightly stitched together. |
|
||||
| 5 | Error Prevention | 2 | Validation and confirmations are good, but install safety and credential context appears after download/copy actions on small screens. |
|
||||
| 6 | Recognition Rather Than Recall | 3 | Author, revision, source, filters, and capabilities are visible; installation still asks users to remember a separate Homarr navigation path. |
|
||||
| 7 | Flexibility and Efficiency | 2 | Search, filters, sort, drag-and-drop, autofill, and comment shortcuts help; the install handoff remains manual. |
|
||||
| 8 | Aesthetic and Minimalist Design | 2 | Hierarchy is clear, but repeated panels, badges, uppercase metadata labels, and overlapping install actions add noise. |
|
||||
| 9 | Error Recognition and Recovery | 3 | Errors are plain-language and usually preserve work; the global action error can be detached from the failed control. |
|
||||
| 10 | Help and Documentation | 3 | The installation guide and source explanation are useful; security terminology lacks inline definitions. |
|
||||
| **Total** | | **27/40** | **Acceptable, significant improvements needed** |
|
||||
|
||||
## Anti-Patterns Verdict
|
||||
|
||||
**LLM assessment:** Moderate AI-assisted feel, not outright slop. Domain-specific copy, extensive state coverage, and transparent source/capability information make the feature credible. The generic card grid, repeated rounded panels, icon tiles, uppercase tracked labels, and decorative changelog stripe still expose a component-recipe grammar. It looks competent but not fully edited into a distinctive Homarr store.
|
||||
|
||||
**Deterministic scan:** `detect.mjs --json apps/docs/src/components/workshop` exited 0 with exact output `[]`: zero findings, zero triggered rules, and no false positives. The scan found none of Impeccable's deterministic anti-patterns.
|
||||
|
||||
**Visual overlays:** No reliable user-visible overlay is available. The independent detector agent had no browser backend. The parent fallback opened a fresh browser tab and visually inspected the listing, but the browser API did not expose a mutable evaluation method for the required preflight, so injection and `[Human]` visibility were correctly skipped. Browser/source evidence was used instead.
|
||||
|
||||
## Overall Impression
|
||||
|
||||
The Workshop is already substantially better than a generic gallery: it communicates authorship, source code, API capabilities, secrets, revision history, reports, and discussion. The biggest opportunity is to turn that information into a deliberate trust sequence. Today the interface lets the user act first and understand safety later, especially on mobile.
|
||||
|
||||
## What's Working
|
||||
|
||||
- **State coverage is unusually strong.** Listing failures, stale data, loading skeletons, empty results, filter recovery, comment errors, pending actions, and destructive confirmations have explicit UI.
|
||||
- **Pre-install transparency is excellent.** API hosts, methods, configurable options, network scope, and credential requirements are visible instead of hidden behind a download.
|
||||
- **The new primitive layer is coherent.** Search, filters, menus, switches, dialogs, and code presentation share one restrained interaction vocabulary across light and dark themes.
|
||||
|
||||
## Priority Issues
|
||||
|
||||
### [P1] Safety context arrives after the install action on mobile
|
||||
|
||||
**Why it matters:** Download and copy actions appear in the header, while source capabilities, credential requirements, and installation guidance live in an aside that drops below the long source and discussion sections under the desktop breakpoint. A first-time mobile user can commit before understanding what the widget contacts or what secrets it needs.
|
||||
|
||||
**Fix:** Put a compact “Before you install” summary immediately below the header on small screens: API hosts, network scopes, credential count, and action/query counts. Keep the full sticky details panel on desktop.
|
||||
|
||||
**Suggested command:** `$impeccable layout`
|
||||
|
||||
### [P1] Primary button and own-comment contrast misses AA
|
||||
|
||||
**Why it matters:** The requested theme's primary red with white foreground is about 3.4:1, below 4.5:1 for small text. The same pairing affects primary buttons and the author's comment bubbles.
|
||||
|
||||
**Fix:** Darken the primary red or use a dark foreground in light mode, then verify default buttons and comment bubbles in both themes.
|
||||
|
||||
**Suggested command:** `$impeccable colorize`
|
||||
|
||||
### [P2] The listing's server-rendered fallback is blank
|
||||
|
||||
**Why it matters:** Before hydration, the listing renders an empty block instead of preserving the page's hierarchy and indicating progress. This creates a visible dead zone on slower self-hosted deployments.
|
||||
|
||||
**Fix:** Render the listing heading, toolbar silhouette, and shared skeleton cards as the `BrowserOnly` fallback, matching the detail page's loading treatment.
|
||||
|
||||
**Suggested command:** `$impeccable harden`
|
||||
|
||||
### [P2] Download, copy, source, and install compete for the same task
|
||||
|
||||
**Why it matters:** “Download widget,” “Copy JSON,” a full source viewer, and a separate installation panel all appear as parallel paths. Users must infer the recommended workflow.
|
||||
|
||||
**Fix:** Make “Download widget JSON” the single primary header action. Keep copy beside the source viewer and place the next import step directly beside or immediately after download.
|
||||
|
||||
**Suggested command:** `$impeccable clarify`
|
||||
|
||||
### [P2] Repeated metadata scaffolding creates visual noise
|
||||
|
||||
**Why it matters:** Card shells, badges, icon tiles, uppercase tracked labels, and the changelog stripe make the detail page feel assembled from recipes and slow scanning.
|
||||
|
||||
**Fix:** Flatten the widget details into a compact definition list, reserve badges for status, and remove the decorative changelog stripe.
|
||||
|
||||
**Suggested command:** `$impeccable distill`
|
||||
|
||||
## Persona Red Flags
|
||||
|
||||
**Jordan, first-timer:** “Network scope,” queries, actions, options, and the manifest schema appear without inline explanation. Download versus copy has no stated recommendation. On mobile, credential and installation context arrives after source and discussion.
|
||||
|
||||
**Sam, accessibility-dependent user:** Primary controls and author comment bubbles miss small-text contrast. Copy, optimistic votes, filter result changes, and successful report/edit completion do not expose explicit live-region behavior. Gallery controls and dots are below comfortable touch-target sizes.
|
||||
|
||||
**Casey, distracted mobile user:** The most important safety panel falls to the bottom, while primary actions stay at the top. Long source and discussion content separate the decision from its explanation. The submit type selector remains dense at narrow widths.
|
||||
|
||||
## Minor Observations
|
||||
|
||||
- The internal installation guide uses an external-link icon.
|
||||
- The final publication action says “Submit” rather than “Publish submission.”
|
||||
- Dark-theme tokens are declared in two blocks, increasing drift risk.
|
||||
- Screenshot dots rely on opacity alone and lack `aria-current`.
|
||||
- Successful report and edit actions deserve a small confirmation instead of silent dismissal.
|
||||
|
||||
## Questions to Consider
|
||||
|
||||
- If source review is important enough to say “Review exactly what will be installed,” why can users download before seeing the capability and credential summary?
|
||||
- Is Workshop primarily a code catalog or a safety-conscious app store? The current action hierarchy tries to be both.
|
||||
- Should the successful end state be a downloaded file, or a confident handoff into Homarr with the next step already visible?
|
||||
@@ -1,2 +0,0 @@
|
||||
[tools]
|
||||
node = "24.18.0"
|
||||
@@ -1,2 +1,6 @@
|
||||
# pnpm v11: non-auth settings moved to pnpm-workspace.yaml
|
||||
# Keep this file for any future registry/auth configuration
|
||||
# Expo doesn't play nice with pnpm by default.
|
||||
# The symbolic links of pnpm break the rules of Expo monorepos.
|
||||
# @link https://docs.expo.dev/guides/monorepos/#common-issues
|
||||
node-linker=hoisted
|
||||
strict-peer-dependencies=false
|
||||
engine-strict=true
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"printWidth": 120,
|
||||
"endOfLine": "lf",
|
||||
"sortPackageJson": true,
|
||||
"ignorePatterns": [
|
||||
"**/*.cjs",
|
||||
"**/lang/**",
|
||||
"static-data/**",
|
||||
"CHANGELOG.md",
|
||||
"apps/docs/.docusaurus/**",
|
||||
"apps/docs/build/**",
|
||||
"apps/docs/blog/**",
|
||||
"packages/custom-widgets/src/core/component-catalog.generated.json",
|
||||
"apps/docs/static/custom-widgets/component-catalog-v1.json"
|
||||
]
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "import", "jsx-a11y"],
|
||||
"categories": {
|
||||
"correctness": "error",
|
||||
"suspicious": "warn",
|
||||
"pedantic": "off",
|
||||
"perf": "off",
|
||||
"style": "off",
|
||||
"restriction": "off",
|
||||
"nursery": "off"
|
||||
},
|
||||
"rules": {
|
||||
"eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||
"typescript/consistent-type-imports": ["warn", { "prefer": "type-imports", "fixStyle": "separate-type-imports" }],
|
||||
"typescript/no-non-null-assertion": "error",
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
"unicorn/no-new-array": "warn",
|
||||
"unicorn/no-useless-spread": "warn",
|
||||
"unicorn/no-useless-length-check": "warn",
|
||||
"unicorn/no-useless-fallback-in-spread": "warn",
|
||||
"jsx-a11y/prefer-tag-over-role": "warn",
|
||||
"oxc/const-comparisons": "warn",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"react/no-children-prop": "warn",
|
||||
"jsx-a11y/no-autofocus": "warn",
|
||||
"jsx-a11y/img-redundant-alt": "warn",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
},
|
||||
"options": {
|
||||
"reportUnusedDisableDirectives": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/*.config.js",
|
||||
"**/*.config.mjs",
|
||||
"**/*.cjs",
|
||||
"**/lang/*.d.json.ts",
|
||||
".next/**",
|
||||
"apps/docs/.docusaurus/**",
|
||||
"apps/docs/build/**"
|
||||
],
|
||||
"settings": {
|
||||
"react": { "version": "19" }
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
{
|
||||
"successComment": false,
|
||||
"failComment": false,
|
||||
"draftRelease": true,
|
||||
"releaseBodyTemplate": "<%= _.truncate(nextRelease.notes, { 'length': 124000, 'omission': '' }) %>"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"oxc.oxc-vscode",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"million.million-lint",
|
||||
"lokalise.i18n-ally"
|
||||
|
||||
@@ -31,5 +31,6 @@ scopeRangeRegex: "(?:const|let|var)\\s+t\\s*=\\s*(?:await\\s+)?(?:getScopedI18n|
|
||||
# refactorTemplates:
|
||||
# - i18n.get("$1")
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
monopoly: true
|
||||
monopoly: true
|
||||
@@ -15,7 +15,9 @@
|
||||
"request": "launch",
|
||||
"command": "pnpm docker:dev",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lint fix",
|
||||
@@ -23,7 +25,9 @@
|
||||
"request": "launch",
|
||||
"command": "pnpm lint:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "format fix",
|
||||
@@ -31,7 +35,9 @@
|
||||
"request": "launch",
|
||||
"command": "pnpm format:fix",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "db push",
|
||||
@@ -63,7 +69,9 @@
|
||||
"request": "launch",
|
||||
"command": "pnpm test",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "test ui",
|
||||
@@ -71,7 +79,9 @@
|
||||
"request": "launch",
|
||||
"command": "pnpm test:ui",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"skipFiles": ["<node_internals>/**"]
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"js/ts.tsdk.path": "node_modules\\typescript\\lib",
|
||||
"eslint.workingDirectories": [
|
||||
{
|
||||
"mode": "auto"
|
||||
}
|
||||
],
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"js/ts.implicitProjectConfig.experimentalDecorators": true,
|
||||
"prettier.configPath": "./tooling/prettier/index.mjs",
|
||||
"cSpell.words": [
|
||||
"ajnart",
|
||||
"cqmin",
|
||||
@@ -27,7 +34,11 @@
|
||||
],
|
||||
"i18n-ally.dirStructure": "auto",
|
||||
"i18n-ally.displayLanguage": "en",
|
||||
"i18n-ally.enabledFrameworks": ["custom"],
|
||||
"i18n-ally.localesPaths": ["packages/translation/src/lang"],
|
||||
"i18n-ally.keystyle": "nested"
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"custom"
|
||||
],
|
||||
"i18n-ally.localesPaths": [
|
||||
"packages/translation/src/lang",
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
# Homarr Agent Rules
|
||||
|
||||
## 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-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
|
||||
│ ├── old-import/ # Legacy Homarr import
|
||||
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
│ └── github/ # CI setup action
|
||||
├── tools/
|
||||
│ └── homarr-dev/ # Go CLI for local and PR Docker images
|
||||
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
|
||||
├── e2e/ # E2E test specs
|
||||
└── Dockerfile # Multi-stage production build
|
||||
```
|
||||
|
||||
## Documentation Sync
|
||||
|
||||
The documentation site lives at `apps/docs/` (Docusaurus 3, `@homarr/docs`).
|
||||
|
||||
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.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
|
||||
- New cron job → `apps/docs/docs/management/tasks.mdx`
|
||||
|
||||
## Monorepo Commands
|
||||
|
||||
- `pnpm dev` — Next.js app only
|
||||
- `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `pnpm db:seed` — seed default database data explicitly
|
||||
- `pnpm docker:dev:up` — start the Redis development service in the background
|
||||
- `pnpm dev:docs` — Docusaurus docs site only
|
||||
- `pnpm turbo build` — build all packages
|
||||
- `pnpm turbo build --filter=@homarr/docs` — build docs only
|
||||
- `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
|
||||
- Icons: @tabler/icons-react
|
||||
- Docs app can import from `@homarr/definitions` for shared types
|
||||
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `pnpm dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `pnpm dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
- Run Go checks from `tools/homarr-dev` with `go test ./...` and `go vet ./...`.
|
||||
@@ -1,96 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Goal
|
||||
|
||||
Remove widget-only services from Homarr's global Integration system.
|
||||
|
||||
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
|
||||
|
||||
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
|
||||
|
||||
## Why
|
||||
|
||||
These services do not need shared integration state.
|
||||
|
||||
- Release providers are selected per Releases widget repository.
|
||||
- Search.ch is only used by the Timetable widget.
|
||||
|
||||
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Release Providers
|
||||
|
||||
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
|
||||
- Removed release providers from `IntegrationKind`.
|
||||
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
|
||||
- Updated the Releases widget to store provider configuration directly in widget options:
|
||||
- `provider`
|
||||
- `identifier`
|
||||
- `versionFilter`
|
||||
- `providerUrl`
|
||||
- Updated the Releases widget editor to use a provider select instead of an integration select.
|
||||
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
|
||||
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
|
||||
|
||||
### Timetable / Search.ch
|
||||
|
||||
- Removed Search.ch as an integration.
|
||||
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
|
||||
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
|
||||
- Updated the Timetable API to receive `baseUrl` directly from widget options.
|
||||
|
||||
### ArchiveTeam Warrior
|
||||
|
||||
- Kept as a proper integration (not removed).
|
||||
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
|
||||
|
||||
### Integration Cleanup
|
||||
|
||||
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
|
||||
- Removed unused dependencies from `packages/integrations/package.json`.
|
||||
|
||||
### Database Migration
|
||||
|
||||
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
|
||||
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
|
||||
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
|
||||
|
||||
### Docs
|
||||
|
||||
- Updated Releases widget docs to describe providers instead of integrations.
|
||||
- Updated Timetable docs to remove Search.ch integration setup.
|
||||
- Removed obsolete integration docs pages for the deleted integration kinds.
|
||||
- Updated docs sitemap and integration docs mappings.
|
||||
|
||||
### Translations
|
||||
|
||||
- Added English labels for the new widget options:
|
||||
- `widget.timetable.option.baseUrl.label`
|
||||
- `widget.releases.option.repositories.providerUrl.label`
|
||||
|
||||
## How It Works Now
|
||||
|
||||
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
|
||||
|
||||
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
|
||||
|
||||
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
|
||||
|
||||
## Deliberate Non-Goals
|
||||
|
||||
- No provider secret/token support was added.
|
||||
- No new secret storage model was introduced.
|
||||
- No compatibility layer was kept in `packages/integrations`.
|
||||
|
||||
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
|
||||
|
||||
## Verification So Far
|
||||
|
||||
- Focused typechecks passed for affected packages.
|
||||
- Full `pnpm turbo typecheck` passed.
|
||||
- Full `pnpm lint` passed with existing warnings.
|
||||
- Focused release provider normalization tests passed.
|
||||
- Widget translation test passed.
|
||||
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
|
||||
- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
|
||||
@@ -1,234 +1 @@
|
||||
# ── 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,53 +1,24 @@
|
||||
# syntax=docker/dockerfile:1.25
|
||||
|
||||
FROM node:24.18.0-alpine AS base
|
||||
FROM node:24.11.1-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
# 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 ./
|
||||
# @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
|
||||
|
||||
RUN apk add --no-cache libc6-compat curl bash
|
||||
RUN apk update
|
||||
COPY . .
|
||||
|
||||
RUN corepack enable pnpm && pnpm install --recursive --frozen-lockfile
|
||||
|
||||
# Copy static data as it is not part of the build
|
||||
COPY static-data ./static-data
|
||||
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 \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
||||
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
RUN corepack enable pnpm && pnpm build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
@@ -71,8 +42,9 @@ RUN mkdir -p /var/cache/nginx && \
|
||||
|
||||
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/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
|
||||
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
|
||||
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
||||
|
||||
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
||||
@@ -95,6 +67,5 @@ ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
EXPOSE 7575
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
@@ -1,475 +0,0 @@
|
||||
# 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,39 +0,0 @@
|
||||
# Product
|
||||
|
||||
## Register
|
||||
|
||||
product
|
||||
|
||||
## Users
|
||||
|
||||
Homarr is for self-hosters and administrators who assemble dashboards for services across home labs and private infrastructure. They need dense, reliable controls and status at a glance, while retaining enough guidance to configure advanced features without hiding how they work.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
Homarr provides one place to organize, observe, and operate self-hosted services. Success means users can build useful dashboards quickly, understand their state, recover from configuration errors, act on services, and reshape their workspace without repeatedly leaving the board. Boards must load immediately, stay visually stable, and keep widgets legible across screen sizes.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Direct, capable, precise, and approachable. Homarr should feel calm under load, be truthful about system state, and reward users who refine details.
|
||||
|
||||
## Anti-references
|
||||
|
||||
- Decorative dashboards, oversized metric cards, and motion without state meaning.
|
||||
- Wizard flows that fight ordinary scrolling or rigid layouts that force one workflow.
|
||||
- Hidden configuration magic and implementation complexity exposed before it is needed.
|
||||
- Clever custom controls where familiar platform or Mantine interactions already work.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. Keep configuration, consequences, and system state visible.
|
||||
2. Make simple tasks short without limiting advanced workflows.
|
||||
3. Preserve expert depth while prioritizing urgent information at small sizes.
|
||||
4. Reveal dense workspaces progressively through a consistent advanced focus mode.
|
||||
5. Isolate failures and show loading, stale, partial, empty, and failed states truthfully.
|
||||
6. Use the existing Mantine design language consistently across management and board surfaces.
|
||||
7. Keep spatial actions direct: moving, resizing, nesting, and using fixed rails happen on the canvas with immediate feedback.
|
||||
8. Preserve predictable widget composition with fixed logical tile sizes and horizontal scrolling before controls become inaccessible.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Target WCAG 2.2 AA. Every pointer interaction needs a keyboard and coarse-pointer path. Support screen readers, visible and restored focus, sufficient contrast, reduced motion, non-color status cues, responsive layouts, actionable error messages, and keyboard equivalents for drag and resize operations.
|
||||
@@ -1,263 +0,0 @@
|
||||
# PR #6502 widget-modernization ultra-review
|
||||
|
||||
## Executive result
|
||||
|
||||
**Merge recommendation: changes required.** No P0 was found, but the current branch still has five P1 issues: a pre-existing Custom API authorization/redirect weakness exposed by this subsystem review, an inaccessible manual advanced-focus interaction, a likely fixed-position/transform geometry defect, systemic false empty states for failed queries, and unvalidated integration-provided navigation URLs. Eight P2 issues cover raw error disclosure, unsafe runtime-ref interfaces, localization, responsive/theming drift, test coverage, and inaccurate PR copy.
|
||||
|
||||
Audit health (Impeccable rubric): accessibility 1/4, performance 2/4, responsive design 2/4, theming 2/4, anti-patterns 3/4; **10/20 (Acceptable, significant work required)**. The UI does not look generically AI-generated; its problem is inconsistent technical behavior beneath an otherwise coherent Mantine vocabulary.
|
||||
|
||||
## Reviewed revision and environment
|
||||
|
||||
| Item | Value |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| PR | #6502, `feat: modernize dashboard widgets` |
|
||||
| Reviewed head | `96e92485a5fd45f3648cfac0453e614779c414ba` |
|
||||
| Base branch / fetched base | `dev` / `d58a47b776a55d04dee2220f59d072b9af8bbed3` |
|
||||
| Merge base | `d58a47b776a55d04dee2220f59d072b9af8bbed3` |
|
||||
| Diff | 359 files, +15,721/-4,478 |
|
||||
| PR state | open, non-draft, `BLOCKED`; no approval decision |
|
||||
| Hosted checks at audit start | Fast gate, Container and E2E, CodeQL, JS/TS analysis, Actions analysis, title, auto-fix: green |
|
||||
| Inline threads | 34 total, 7 unresolved; only 2 remain substantively valid |
|
||||
| Mantine | repository lock: `@mantine/core`, hooks, charts and dates 9.4.1 |
|
||||
| Local tools | Node 24.16.0 and pnpm 9.5.0; repository requires Node >=24.18.0 and pnpm >=11.15.1 |
|
||||
|
||||
The former red baseline `7b7045202d289b7d83946befcee5d2845ea4a706` had a green Fast gate and a failed Container/E2E run (`30748255760`, job `91498308523`). The latest head has a green Container/E2E job (`30756067198`, job `91519039504`). This proves the current suite passed once; it does not satisfy the requested three-run flake characterization.
|
||||
|
||||
## PR-description traceability
|
||||
|
||||
| Claim | Result | Evidence / correction |
|
||||
| --------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| “all 54 widget renderers with compact and advanced modes” | **Inaccurate** | 54 `WidgetKind` values exist. `displayMode="advanced"` reaches 53; `app` is deliberately excluded in `item-content.tsx:51` and documented as compact-only. Change copy to “53 widget renderers; app tiles remain compact,” or implement app advanced mode. |
|
||||
| advanced focus: hover, keyboard, touch, geometry | **Partial** | Entry paths, Escape ownership, reduced motion, close focus, touch button and zero-dimension guards exist. WID-002/003 remain. |
|
||||
| richer widget context actions | **Partial** | `Menu.ContextMenu` is a good Mantine choice and query scope is item-aware. Dispatch still crosses `any`; Custom API action authorization is not represented. |
|
||||
| standardized query identity/cache/status | **Partial** | Query keys, matchers, runtime capture, recovery and tests exist. Thirty-nine of 53 renderer query calls do not read errors; runtime ref registration is render-time mutable state. |
|
||||
| safe polling | **Mostly confirmed** | defaults are limited to `widget` namespace; Custom API resume immediately refetches; per-widget intervals exist. State/ref typing still needs redesign. |
|
||||
| partial-failure rendering | **Partial** | settlement helper and several routers are strong; many renderer-level total failures still appear empty/disconnected. |
|
||||
| bounded integration responses | **Mostly confirmed** | common bounded reader, Custom API 2 MiB, timetable 512 KiB, Traefik bounds and Beszel queue bounds exist. Custom action execution remains outside the hardened fetch path. |
|
||||
| timetable hardening | **Confirmed** | board/item permission checks, saved-value comparison, public-address resolution, private ranges, per-request DNS re-resolution, pinned dispatcher, redirect rejection, timeouts, response bounds and schemas are present. |
|
||||
| precise placement | **Mostly confirmed** | mouse, touch tolerance, keyboard, occupied-cell validation, dynamic bounds, nearest fit and immediate persistence exist. Failure/race/live responsive coverage remains insufficient. |
|
||||
| widget catalog documentation | **Partial** | all kinds have pages and shared interactions; wording overclaims focus behavior and validation counts are stale. |
|
||||
| validation: 40 files/162 tests, 6/6 E2E | **Stale** | current diff contains 57 changed unit-spec files (including three E2E specs); repository has 42 widget spec files and six E2E specs. Update from the exact final run. |
|
||||
| independent reviews “pass” | **Not supported** | unresolved valid findings, current P1s, and unavailable live QA contradict an unqualified pass. |
|
||||
|
||||
## Mantine suitability ledger
|
||||
|
||||
Legend: **U** already used appropriately; **C** consolidation/replacement candidate; **R** relevant but current implementation can stay; **N** not relevant to widget runtime; **D** dependency/version addition. Exact 9.4.1 package archives were inspected in `/tmp`; live docs/Context7 include 9.5.0.
|
||||
|
||||
### Core catalog
|
||||
|
||||
- **U:** ActionIcon, Alert, Anchor, Avatar, Badge, Box, Button, Card, Center, Code, Divider, Flex, Grid, Group, Image, Loader, Menu (`Menu.ContextMenu`), Overlay, Paper, Popover, Portal, Progress, RingProgress, ScrollArea, SimpleGrid, Skeleton, Stack, Table, Tabs, Text, ThemeIcon, Title, Tooltip, UnstyledButton.
|
||||
- **C:** Accordion (dense integration sections), DataList (semantic label/value stats), EmptyState (replace bespoke empty state), FocusTrap and Modal compound API (WID-002), LoadingOverlay (background refetch without deleting content), NumberFormatter, OverflowList/Scroller (responsive action and chip rows), Table.ScrollContainer, Timeline (activity/feed histories), VisuallyHidden and Kbd, CloseButton, Dialog/HoverCard/Notification where persistent errors/actions need semantics.
|
||||
- **R:** Affix, AppShell, AspectRatio, BackgroundImage, Breadcrumbs, Burger, Checkbox, Chip, Collapse, ColorInput/ColorPicker/ColorSwatch/AlphaSlider/AngleSlider/HueSlider, Combobox/ComboboxPopover, Container, CopyButton, Drawer, Fieldset, FileButton/FileInput, FloatingIndicator/FloatingWindow, Highlight, Indicator, Input/JsonInput, List, Mark, Marquee, MaskInput, Menubar, MultiSelect/NativeSelect/NumberInput/PasswordInput/PillsInput/PinInput/Radio/RangeSlider/Rating/SegmentedControl/Select/Slider/Switch/TagsInput/Textarea/TextInput, Pagination, Pill, SemiCircleProgress, Space, Splitter, Spoiler, Stepper, Transition, Tree/TreeSelect, Typography. Use only where an existing widget requirement matches; do not churn working layouts.
|
||||
- **N:** Alpha/color/angle primitives outside configuration forms, AppShell/navigation primitives, TableOfContents, CorePackage documentation.
|
||||
- **D (9.5, not 9.4.1):** Cascader. **RollingNumber and Splitter are present in 9.4.1.**
|
||||
|
||||
### Hooks catalog
|
||||
|
||||
- **C:** `useFocusReturn`, `useFocusTrap`, `useReducedMotion`, `useRovingIndex`, `useLongPress`, `useScroller`, `useElementSize`, `useViewportSize`, `useHotkeys`, `useMediaQuery`, `useResizeObserver`.
|
||||
- **U/R:** focus-within, hover, disclosure, click-outside, merged-ref, interval/timeout, local-storage, event/window listeners, throttled/debounced values and list/set/map/state helpers are available in 9.4.1 and should replace local mechanics only when behavior and tests improve.
|
||||
- **N:** document title/favicon/hash/headroom/page-leave/OS/eye-dropper/fullscreen/network/orientation and other page-level utilities have no direct widget-remediation need. Every hook listed under `## Hooks` in `MANTINE.md` is covered by this rule; no hook requires 9.5 for the recommendations above.
|
||||
|
||||
### Form, dates, charts, schedule, extensions, theme and styles
|
||||
|
||||
- **Form:** existing `@homarr/form` should remain the abstraction over all cataloged form APIs. No widget-runtime replacement; use current validation/status APIs for widget editors (**R**).
|
||||
- **Dates:** all catalog entries are in installed 9.4.1 dates. `Calendar`, `MiniCalendar`, and `TimeValue` are candidates; other picker/input entries are editor-only (**R**). Importing dates directly into `@homarr/widgets` requires adding that workspace dependency (**D**).
|
||||
- **Charts:** 9.4.1 exports Area, Bar, BarsList, Bubble, Composite, Donut, Funnel, Heatmap, Line, Pie, Radar, RadialBar, Sankey, Scatter, Sparkline and Treemap (**C/R**). Prefer Sparkline for tiny trends, BarsList for ranked values, Heatmap for calendar/activity density, Composite for mixed analytics. **BulletChart and SunburstChart are 9.5-only (D)**. Replacement requires accessibility and bundle comparison, not blanket Recharts removal.
|
||||
- **Schedule:** all `AgendaView`, day/week/month/year/resource views and Schedule pages require new `@mantine/schedule` dependency (**D**). AgendaView is the only plausible calendar-widget candidate; its weight and semantics must beat the existing grouped list before adoption.
|
||||
- **Guides/FAQ/Other:** server components, portal testing, scroll lock, accessibility, nested popovers, zoom, responsive styling and version changelogs are relevant references (**R**); framework/setup/contribution/old migration entries are **N**. The 9.5 changelog is informational only.
|
||||
- **Theming/styles:** Colors, ColorSchemes, DefaultProps, MantineProvider, ThemeObject, CSS variables, data attributes, CSS Modules, responsive/RTL/style props/Styles API/performance are **U/R**. WID-009 requires shared layer/color tokens. Emotion, Sass, Vanilla Extract and unrelated global setup are **N**.
|
||||
- **Extensions:** existing modals, notifications, spotlight and rich-text abstractions are **U**; Carousel could consolidate Immich only after behavior parity; Dropzone/CodeHighlight/NProgress are **N** for this audit.
|
||||
|
||||
## Complete 54-widget coverage matrix
|
||||
|
||||
“Test” means a focused widget-local spec exists, not that all required states are covered. Every row still requires the common contract suite in WID-013.
|
||||
|
||||
| WidgetKind | Family | Advanced | Focused test | Principal audit gap |
|
||||
| --------------------------- | ----------------------- | ----------- | ------------ | ---------------------------------------------------------- |
|
||||
| clock | basic | yes | no | query error, locale-format options |
|
||||
| weather | basic/feed | yes | yes | query error; dayjs locale/time patterns |
|
||||
| app | basic/link | **no** | no | PR wording; URL contract |
|
||||
| iframe | custom content | yes | yes | manual focus/iframe trapping; URL privacy thread now fixed |
|
||||
| video | custom content | yes | no | media/focus/live behavior |
|
||||
| notebook | notes | yes (inner) | yes | editor focus inside manual surface |
|
||||
| anchorNote | notes | yes | no | query error, save/error regression |
|
||||
| dnsHoleSummary | network | yes | no | total query error, hard-coded chart colors |
|
||||
| dnsHoleControls | network/security | yes | no | total error and permission matrix |
|
||||
| smartHome-entityState | smart home | yes | no | raw state in aria; action permissions |
|
||||
| smartHome-executeAutomation | smart home | yes | no | mutation/permission/edit-mode E2E |
|
||||
| stockPrice | analytics | yes | yes | total error |
|
||||
| mediaServer | media | yes | no | total error and table a11y |
|
||||
| calendar | calendar | yes | yes | total error; fixed HH:mm; schedule tradeoff |
|
||||
| downloads | media | yes | yes | four-state/component coverage, arbitrary z-index |
|
||||
| mediaRequests-requestList | media | yes | no | total error; external URLs/actions |
|
||||
| mediaRequests-requestStats | media | yes | no | total error; links/colors |
|
||||
| mediaTranscoding | media | yes | yes | total error and responsive table |
|
||||
| mediaMissing | media | yes | yes | total error and external URL |
|
||||
| minecraftServerStatus | infrastructure | yes | no | total error/malformed payload |
|
||||
| networkControllerSummary | networking | yes | no | total error; WAN/unknown localization |
|
||||
| networkControllerStatus | networking | yes | no | responsive rule duplication |
|
||||
| rssFeed | feed | yes | yes | strong stale/partial state; locale relative time |
|
||||
| bookmarks | basic/link | yes | yes | recomputation; URL contract |
|
||||
| indexerManager | media | yes | no | total error; runtime action ref |
|
||||
| healthMonitoring | infrastructure | yes | yes | integration query error |
|
||||
| releases | infrastructure/feed | yes | yes | secret/item authorization needs E2E |
|
||||
| mediaReleases | media | yes | no | total error; external URLs |
|
||||
| dockerContainers | infrastructure | yes | yes | total error; z-index/table semantics |
|
||||
| firewall | networking/security | yes | yes | partial-query matrix; loose router generics |
|
||||
| notifications | infrastructure | yes | no | total error; untrusted href |
|
||||
| systemResources | infrastructure | yes | yes | total error; previous network thread fixed |
|
||||
| coolify | infrastructure | yes | yes | total error; URL validation |
|
||||
| systemDisks | infrastructure | yes | yes | total error |
|
||||
| timetable | calendar | yes | no | backend strong; component locale/live tests absent |
|
||||
| immich-serverStats | media | yes | yes | two ignored query errors |
|
||||
| immich-albumCarousel | media | yes | yes (query) | query error and nested overlay/focus E2E |
|
||||
| paperlessNgx | analytics | yes | no | former error thread fixed, no regression spec |
|
||||
| patchmon | infrastructure | yes | yes | total error |
|
||||
| bazarr | media | yes | yes | total error |
|
||||
| tracearr | analytics | yes | yes | total error |
|
||||
| speedtestTracker | analytics | yes | yes | total error |
|
||||
| uptimeKuma | infrastructure | yes | yes | total error |
|
||||
| audioStats | media | yes | yes | good dual-error handling; partial-state tests needed |
|
||||
| umami | analytics | yes | no | runtime query registration; chart a11y |
|
||||
| vpn | networking/security | yes | no | total error; status/permission matrix |
|
||||
| archiveTeamWarrior | infrastructure | yes | yes | suspense path; docs present |
|
||||
| ups | infrastructure | yes | no | total error |
|
||||
| beszelSystemTable | monitoring | yes | yes | nested chart/focus and live subscription |
|
||||
| beszelSystemGrid | monitoring | yes | no | live/error UI; no focused renderer test |
|
||||
| beszelAlerts | monitoring | yes | no | partial error and status semantics |
|
||||
| beszelSystemStats | monitoring | yes | yes | runtime refs, locale tooltip |
|
||||
| traefik | networking | yes | no | total error; bounded backend positive |
|
||||
| customApi | custom content/security | yes | yes | WID-001/007/008; polling resume fixed |
|
||||
|
||||
## Severity-ranked findings
|
||||
|
||||
### WID-001 — P1 confirmed, pre-existing: Custom API actions lack resource authorization and hardened fetch policy
|
||||
|
||||
- **Location/claim:** `packages/api/src/router/custom-widget/custom-widget-router.ts:57-102,392-451`; “harden Custom API” and action-permission semantics.
|
||||
- **Evidence/reproduction:** any authenticated account can call `customWidget.all`, learn every definition ID and URL, then call `customWidget.execute({definitionId})`. The mutation verifies neither board nor item nor an action permission. It decrypts administrator-provided secrets, uses `new URL()` only, and `redirect: "follow"`. The PR hardened `widget.customApi.getData`, but not this sibling execution path.
|
||||
- **Impact/rule:** unauthorized side effects against internal services and authenticated redirect chains; broken object-level authorization. A view-only account can invoke administrator-created controls outside any board context.
|
||||
- **Correction:** require `{boardId,itemId,definitionId}`; load the item and verify it is `customApi`, belongs to the board, references the definition, and the caller has an explicit execute policy. Until that policy exists, restrict to admin. Share `validateCustomApiUrl`, timeout, redirect rejection, bounded disposal and redacted errors with getData. Compatibility: old direct callers must send item/board context.
|
||||
- **Tests:** procedure tests for anonymous, unrelated authenticated, view-only, modifier, admin, mismatched item/definition, disabled definition, redirects, credential URL, timeout and secret non-disclosure; E2E action button across roles.
|
||||
|
||||
### WID-002 — P1 confirmed, PR-introduced: manual advanced focus is not an accessible modal
|
||||
|
||||
- **Location:** `advanced-focus/context.tsx:194-209`, `items/item-content.tsx:113-189`.
|
||||
- **Evidence:** manual mode renders an Overlay plus a `role="region"` Card. There is no FocusTrap, `aria-modal`, background inertness, scroll lock or backdrop dismissal. Only the close button is initially focused; Tab can leave the surface and activate dimmed board controls.
|
||||
- **Impact/rule:** keyboard/screen-reader users lose the interaction boundary; WCAG 2.1.2, 2.4.3 and ARIA dialog pattern. Iframes/editors make escape especially likely.
|
||||
- **Correction/Mantine:** render manual mode through `Modal.Root` compound parts (or ModalBase), using its portal, FocusTrap, scroll lock, Escape/return-focus and semantic content. Preserve preview mode as non-modal. Configure nested overlays and `removeScrollProps`; keep a 44px `Modal.CloseButton` and explicit title via VisuallyHidden if needed.
|
||||
- **Tests:** RTL portal/focus-cycle/return tests; Playwright Tab/Shift+Tab, nested Menu/Select/Popover/editor/iframe, backdrop and every close path; reduced motion and 200% zoom.
|
||||
|
||||
### WID-003 — P1 probable, PR-introduced: fixed advanced surface remains under transformed Gridstack ancestry
|
||||
|
||||
- **Location:** `advanced-focus.module.css:35-42`; Card remains inside the Gridstack item while only the backdrop is portaled.
|
||||
- **Evidence:** CSS fixed descendants establish their containing block at a transformed ancestor. Gridstack uses transforms for positioned items, so viewport-derived `left/top` can be interpreted relative to the item on affected layouts. A Portal wrapper exists only for Overlay.
|
||||
- **Impact:** off-screen, clipped or incorrectly scaled focus surface across breakpoints/remounts.
|
||||
- **Correction:** portal the focused surface itself (naturally achieved by WID-002) and calculate geometry in the same viewport coordinate system. Do not compensate with transform-specific offsets.
|
||||
- **Tests:** real Gridstack at every layout/viewport, scrolled page, header, dynamic section and resize/remount; assert bounding rect within viewport and finite scale.
|
||||
|
||||
### WID-004 — P1 confirmed, regression-exposed/systemic: query failures still become valid empty/disconnected UI
|
||||
|
||||
- **Location:** 39 of 53 renderer query calls do not read `error`, including bookmarks, weather, calendar, media server, system resources, Coolify, DNS Hole, Tracearr, notifications, Immich, VPN and others listed in the matrix. Query defaults do not set `throwOnError` (`trpc.tsx:69-93`).
|
||||
- **Evidence:** TanStack Query returns `data=undefined` on total failure. Components default to `[]`, `null`, or empty branches, so the board ErrorBoundary is never reached. Network Controller and Paperless were fixed, proving the pattern; it was not completed.
|
||||
- **Impact:** operators are told “empty” or “disconnected” when monitoring failed, violating the product’s truthful-state requirement.
|
||||
- **Correction/Mantine:** introduce a shared `WidgetQueryState` contract distinguishing initial Skeleton, background LoadingOverlay, stale Alert, partial warning, true EmptyState and terminal error. Either explicitly throw only when no usable cached data or render a shared error Alert. Preserve stale data.
|
||||
- **Tests:** parameterized contract tests for every queried widget: initial, success-empty, total failure, stale+failure, partial failure, refetch, remount/persisted cache and sibling isolation.
|
||||
|
||||
### WID-005 — P1 confirmed, PR-introduced/systemic: integration-provided links bypass a shared URL policy
|
||||
|
||||
- **Location:** examples include `media-missing/component.tsx:291`, `notifications/component.tsx:77`, media requests list/stats, Coolify resource/server links, releases, bookmarks and iframe. Only RSS and custom JSX visibly use explicit protocol sanitizers.
|
||||
- **Evidence:** dynamic strings are assigned directly to `href`/new-tab controls. Integration APIs and remote feeds are trust boundaries; no renderer-wide `http/https` guard is enforced.
|
||||
- **Impact:** unsafe schemes, credential-bearing URLs, internal detail disclosure and inconsistent opener behavior.
|
||||
- **Correction:** add one typed application URL helper returning a sanitized URL/undefined, with allowed schemes per use case, credential stripping/rejection, consistent `noopener noreferrer`, and disabled non-link presentation. Validate again server-side for action endpoints.
|
||||
- **Tests:** `javascript:`, `data:`, malformed, credential, relative, http/https and missing URLs for each shared link primitive.
|
||||
|
||||
### WID-006 — P2 confirmed, PR-introduced: runtime query/action state mutates untyped refs during render
|
||||
|
||||
- **Location:** `definition.ts:121-162`; runtime registration calls in media missing/transcoding, audio, calendar, Umami, Beszel and Immich; `widget-context-menu.tsx:157-174`.
|
||||
- **Evidence:** `setWidgetRuntimeQueries` mutates a shared object during render; concurrent/aborted renders can publish uncommitted query identity. Action dispatch casts the definition to `Record<string,unknown>` and `any`, then casts its result back.
|
||||
- **Correction:** make definition generics preserve kind/options/action types end-to-end; replace stringly opaque ref slots with a typed per-item registration API updated in a committed effect (or use `useSyncExternalStore`). Separate query registration from imperative actions.
|
||||
- **Tests:** StrictMode, interrupted/remount, options change and cleanup; compile-time `tsd`/type assertions rejecting wrong action props.
|
||||
|
||||
### WID-007 — P2 confirmed, regression-exposed: raw backend errors are exposed to users and assistive text
|
||||
|
||||
- **Location:** Custom API UI `component.tsx:561-576`; API `custom-api.ts:79-85`; several partial fallbacks use `error.message`.
|
||||
- **Impact:** internal hostnames, request paths and implementation details can appear in tooltips/aria labels and logs.
|
||||
- **Correction/Mantine:** return stable error codes plus redacted public messages; keep detailed causes server-side with correlation IDs. Render localized Alert/Notification details, never raw throwable text.
|
||||
- **Tests:** errors containing credentials, query strings, private hosts and multiline backend bodies must not reach tRPC payload, DOM, accessibility tree or notifications.
|
||||
|
||||
### WID-008 — P2 confirmed, PR-introduced/regression-exposed: locale handling remains incomplete
|
||||
|
||||
- **Locations:** Beszel chart/tooltip dayjs formats, calendar event `HH:mm`, downloads `YYYY-MM-DD HH:mm`, timetable `HH:mm:ss`, weather day/month/time formats; `_inputs/widget-location-input.tsx:199` hardcodes `Intl.NumberFormat("en")`.
|
||||
- **Also:** `beszel/_shared/disk-usage.tsx:41`, stats loading aria label, Network Controller `WAN`/`unknown` are English/raw accessible strings.
|
||||
- **Correction:** centralize app-locale date/time/number/duration/status helpers; load canonical dayjs locale only at the boundary; use localized translation keys for aria/status. NumberFormatter is suitable for simple display but application helpers should own locale.
|
||||
- **Tests:** at least `en-US`, `fr-FR`, `de-DE`, RTL locale, 12/24-hour preference and long translations, including accessibility names.
|
||||
|
||||
### WID-009 — P2 confirmed: layer and color tokens are inconsistent
|
||||
|
||||
- **Locations:** downloads z-index 1000 and rgba row colors; item title z-index 4; item menu/dynamic menu 10; media server white rgba; Indexer Manager hex colors; DNS Hole rgba palette; Umami hex tick colors.
|
||||
- **Correction:** define shared semantic layer tokens relative to Mantine CSS z-index variables and semantic status/chart tokens respecting light/dark/forced colors. Do not replace intentional service branding.
|
||||
- **Tests:** light/dark/forced-colors with nested overlays and tooltip ordering.
|
||||
|
||||
### WID-010 — P2 confirmed: responsive decisions are duplicated and under-tested
|
||||
|
||||
- **Location:** Network Controller Status `component.tsx:44,59-78` (open valid thread); Bookmark `component.tsx:94` recomputation (valid but low-impact thread); similar width/height literals across renderers.
|
||||
- **Correction:** small typed breakpoint lookup helpers per family, memoized only for genuinely expensive layout. Prefer container-query Grid/SimpleGrid where structural. Do not cargo-cult `useMemo` for a cheap pure calculation; benchmark Bookmark before accepting the review suggestion.
|
||||
- **Tests:** boundary tables for minimum/typical/wide/short/advanced sizes and 200% zoom.
|
||||
|
||||
### WID-011 — P2 confirmed: PR/docs overstate behavior and validation
|
||||
|
||||
- **Location:** PR body, board/getting-started copy “advanced preview in place,” validation numbers, and unqualified review pass.
|
||||
- **Correction:** state 53 advanced renderers plus compact-only app; call manual focus an overlay only after WID-002/003; publish exact final-SHA check/test counts and disclose unsupported/live-untested states.
|
||||
|
||||
### WID-012 — P2 confirmed: focused automated coverage is far below the 54-widget/data-state claim
|
||||
|
||||
- **Evidence:** 42 widget-local spec files are helper-heavy; 20+ renderers have no focused spec. Six E2E files exist, but only board interactions and Custom JSX directly exercise this modernization. The earlier Custom JSX login wait failed once and current exact-route wait remains.
|
||||
- **Correction:** add shared renderer contract harness plus family E2Es. Replace Custom JSX’s exact post-submit URL race with a shared login helper that waits for authenticated session/stable destination, then run it three times in CI.
|
||||
|
||||
### WID-013 — P2 confirmed: open review-thread state no longer matches code
|
||||
|
||||
| Thread | Classification |
|
||||
| ------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| iframe complete URL | already fixed/false positive at current line: only host is rendered |
|
||||
| system resources network gate | already fixed, outdated |
|
||||
| RSS total error | already fixed: no-data error throws; stale data retained |
|
||||
| bookmarks memoization | valid P3/P2-low; benchmark first |
|
||||
| network-controller total error | already fixed, outdated |
|
||||
| network-controller breakpoint consolidation | valid P2 maintainability |
|
||||
| Paperless total error | already fixed, outdated |
|
||||
|
||||
Resolve the five stale threads with evidence; address or explicitly disposition the two live suggestions. Re-run review because CodeRabbit was paused during active development.
|
||||
|
||||
## Systemic remediation roadmap
|
||||
|
||||
1. **Security/authorization:** WID-001 and WID-005; add negative-role/protocol tests before merge.
|
||||
2. **Interaction accessibility:** replace manual focus with portaled Mantine modal semantics (WID-002/003), then test nested widget content.
|
||||
3. **Truthful data state:** build the shared query-state contract and migrate all 39 ambiguous sites; keep valid stale data and partial results.
|
||||
4. **Typed interfaces:** replace `widgetStateRef` slot mutation and `any` context dispatch with committed typed registrations.
|
||||
5. **Widget families:** media/downloads, infrastructure, networking/security, smart-home, analytics/calendar/feed, then Beszel; apply URL, state, locale, responsive and a11y contracts in that order.
|
||||
6. **Design-system cleanup:** semantic layers/colors and restrained Mantine consolidation. Do not add Schedule or upgrade to 9.5 without a separate dependency/bundle decision.
|
||||
7. **Docs/PR:** correct renderer count, behavior and exact validation evidence.
|
||||
8. **Final proof:** frozen install on supported Node/pnpm; format, lint, full typecheck, affected and full unit tests, production and docs builds; Custom JSX three times; complete production-container E2E; desktop/tablet/mobile keyboard/touch/light/dark/reduced-motion/zoom/a11y-tree QA. Push nothing until all refer to one unchanged SHA; close every review thread and obtain required approval.
|
||||
|
||||
## Positive findings
|
||||
|
||||
- Timetable is the strongest security work in the PR: saved resource authorization, private-range rejection, DNS rebinding resistance through pinned lookup, redirect rejection, bounds and schemas are coherently tested.
|
||||
- Bounded response helpers, Beszel bounded queue/cancellation, and partial-integration settlement materially reduce failure blast radius.
|
||||
- RSS preserves stale data and distinguishes partial feed failure; Network Controller and Paperless now correctly escalate total failures.
|
||||
- Item-scoped query matchers and tests address sibling-widget refresh isolation; global polling defaults are restricted to the widget namespace.
|
||||
- Touch long-press has movement tolerance; focus scale guards prevent Infinity/NaN; reduced-motion and remount cleanup are present.
|
||||
- Existing Homarr theme, Mantine components and shared UI vocabulary are generally preserved. Most proposed Mantine work is consolidation, not redesign.
|
||||
|
||||
## Validation evidence and uncertainty
|
||||
|
||||
- `MANTINE.md` was fetched from `https://mantine.dev/llms.txt` and passed byte-for-byte `cmp`; `llms-full.txt` stayed in `/tmp`.
|
||||
- Context7 current Mantine docs were queried for overlay/focus, responsive/data-state/accessibility, charts and Schedule. Static 9.4.1 archives verified exact exports; Cascader, BulletChart and SunburstChart are 9.5-only.
|
||||
- Local frozen install could not start because host Node/pnpm are below repository engine requirements. No dependencies, source files or machine toolchain were changed.
|
||||
- Docker API access is denied, so production-container E2E could not be rerun locally. Hosted exact-head Fast gate and Container/E2E are green.
|
||||
- `agent-browser` is not installed. Temporary `npx` execution was rejected by the environment’s security reviewer, so live exploratory QA/a11y-tree inspection was not performed.
|
||||
- External integrations were not available; malformed/partial/permission combinations are static-analysis conclusions unless a focused test already exists.
|
||||
- This review intentionally changes no production code and makes no commit, push, thread resolution or PR edit.
|
||||
|
||||
Finalization must re-query the PR head/checks/threads. If the head differs from the reviewed SHA, this report is a snapshot and the delta requires review before merge.
|
||||
|
||||
**Final refresh (2026-08-03):** head remained `96e92485a5fd45f3648cfac0453e614779c414ba`; all reported hosted checks remained successful; merge state remained `BLOCKED` with no approval decision; unresolved threads remained 7 of 34. The branch did not change during the audit.
|
||||
@@ -1,6 +0,0 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.git
|
||||
@@ -1 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
@@ -1,58 +0,0 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
.idea/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.pnpm-store/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
*.tsbuildinfo
|
||||
|
||||
# storybook
|
||||
storybook-static
|
||||
static/data/configs
|
||||
|
||||
# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
|
||||
# Yarn v2
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# docusaurus
|
||||
.docusaurus
|
||||
|
||||
# CI Stuff
|
||||
screenshots
|
||||
test-results
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"$schema": "../../node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["eslint", "typescript", "unicorn", "oxc", "react", "import", "jsx-a11y"],
|
||||
"categories": {
|
||||
"correctness": "error",
|
||||
"suspicious": "warn",
|
||||
"pedantic": "off",
|
||||
"perf": "off",
|
||||
"style": "off",
|
||||
"restriction": "off",
|
||||
"nursery": "off"
|
||||
},
|
||||
"rules": {
|
||||
"eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||
"typescript/consistent-type-imports": "off",
|
||||
"typescript/no-non-null-assertion": "warn",
|
||||
"import/consistent-type-specifier-style": "off",
|
||||
"jsx-a11y/alt-text": "warn",
|
||||
"jsx-a11y/control-has-associated-label": "warn",
|
||||
"jsx-a11y/prefer-tag-over-role": "warn",
|
||||
"react/jsx-key": "warn",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
"eslint/no-unused-expressions": "warn"
|
||||
},
|
||||
"ignorePatterns": ["**/*.config.js", "**/*.config.mjs", "**/*.cjs", ".docusaurus/**", "build/**"],
|
||||
"settings": {
|
||||
"react": { "version": "19" }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"cSpell.words": ["Homarr", "Lidarr", "Nextcloud", "Radarr", "Readarr", "Sonarr"]
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) 2025 Meier Lukas, Thomas Camlong and Homarr Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,111 +0,0 @@
|
||||
# Homarr Documentation (`@homarr/docs`)
|
||||
|
||||
The official [homarr.dev](https://homarr.dev) documentation site, built with [Docusaurus 3](https://docusaurus.io/) and living inside the Homarr monorepo.
|
||||
|
||||
## Why it's in the monorepo
|
||||
|
||||
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
|
||||
# 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 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
|
||||
# Build docs only
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
|
||||
# Build everything (docs + main app + tasks + websocket)
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Lint & Format
|
||||
|
||||
Uses oxlint and oxfmt (same as the rest of the monorepo):
|
||||
|
||||
```bash
|
||||
pnpm lint # oxlint
|
||||
pnpm format # oxfmt (check)
|
||||
pnpm format:fix # oxfmt (fix)
|
||||
```
|
||||
|
||||
## Typecheck
|
||||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
## Content Structure
|
||||
|
||||
```
|
||||
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
|
||||
pnpm verify:search
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| 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 |
|
||||
|
||||
## Contributing
|
||||
|
||||
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,35 +0,0 @@
|
||||
---
|
||||
slug: documentation-migration
|
||||
title: Migration of Documentation
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
tags: [documentation, migration]
|
||||
---
|
||||
|
||||
We are happy to announce that the documentation of Homarr has been migrated to Docusaurus - An opensource documentation tool.
|
||||
The documentation has been re-written partly and includes now many animations, guides and additional crucial information.
|
||||
|
||||
We are still figuring things out and highly depend on your feedback.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Why we migrated
|
||||
|
||||
The default GitHub Wiki feature is decent - but very confusing to read.
|
||||
Our possibilites for searching the documentation in the Wiki are highly limited.
|
||||
Additionally, Users are unable to contribute to the documentation if they have not sufficient permissions.
|
||||
|
||||
We could not review any chanegs made to the documentation. This is why we migrated to Docusaurus.
|
||||
|
||||
## How you can contribute
|
||||
|
||||
The documentation will soon be merged into the master branch of Homarr.
|
||||
You'll find a ``doc/`` directory in the root of Homarr.
|
||||
We'll obviously not include this in our Docker images.
|
||||
In the future, everyone may modify the documentation by creating a pull request.
|
||||
After we've reviewed your changes, we'll merge your PR and this documentation will automatically update.
|
||||
|
||||
## 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.
|
||||
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,20 +0,0 @@
|
||||
---
|
||||
slug: translations
|
||||
title: Help us make Homarr multilingual
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
tags: [translations, homarr, update, contributions]
|
||||
---
|
||||
|
||||
We've been working hard to offer Homarr in many languages. The next update of Homarr will be using [i18next](https://www.i18next.com/) to translate almost any part of your Dashboard.
|
||||
|
||||
### We need your help
|
||||
 <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).
|
||||
|
||||
<!-- 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! 😎
|
||||
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,67 +0,0 @@
|
||||
---
|
||||
title: "We present: Homarr 0.11 - the most complex Homarr version yet!"
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
- walkx
|
||||
- meierschlumpf
|
||||
tags: [homarr, update, breaking changes, version, gridstack, dnd, documentation]
|
||||
---
|
||||
|
||||
We are *incredibly* proud to serve you Homarr 0.11!
|
||||
It contains many breaking changes and huge improvements to your favourite dashboard app:
|
||||
|
||||
- Integration of Gridstack for much more complex dashboard configuration and drag and drop
|
||||
- Added icon picker with smart search and automatic icons
|
||||
- Complete overhaul of the module system
|
||||
- Added an edit mode for making changes easier
|
||||
- Form validation for better user experience
|
||||
- Better security with new built-in credentials system
|
||||
- Huge design and usability improvements to older components to Homarr
|
||||
- Better handeling of exceptions and unexpected issues
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Complete implementation of highly customizable drag and drop library "Gridstack"
|
||||
|
||||
In 0.11, we have replaced our quite limited drag and drop system with Gridstack.
|
||||
Gridstack enables you to create much more complex and advanced dashboards while keeping everything organized.
|
||||
|
||||
<video style={{ maxWidth: "min(100%, 600px)", borderRadius: "0.5rem"}} controls preload='auto' autoplay >
|
||||
<source src="https://user-images.githubusercontent.com/30572287/217098893-5880e7de-13d0-42c5-b505-f7921593396f.mp4" type="video/mp4" />
|
||||
<p>
|
||||
To view this video please enable JavaScript, and consider upgrading to a
|
||||
web browser that
|
||||
<a href="https://videojs.com/html5-video-support/" target="_blank">
|
||||
supports HTML5 video
|
||||
</a>
|
||||
</p>
|
||||
</video>
|
||||
|
||||
We've also introduced more advanced categories this time:
|
||||
|
||||

|
||||
|
||||
We'll continute to improve this system according to your feedback.
|
||||
|
||||
## Modules have been overhauled and renamed to widgets
|
||||
|
||||
Widgets are now much snappier and have been completely recoded.
|
||||
This fixes some of the bugs a few users have previously experienced and improves overall customization.
|
||||
|
||||

|
||||
|
||||
## Fancy icon picker for speeding up your creation process
|
||||
|
||||
We've also added a new fancy icon picker, you can use to get access to any icons much faster.
|
||||
We will add more icon sources in the future, as we made this icon picker very modular, and we plan to make local icons accessible via this picker very soon.
|
||||
|
||||

|
||||
|
||||
## Design and documentation improvements to homarr.dev
|
||||
|
||||
https://homarr.dev has been overhauled with better design, updated documentation, more images and better explanations.
|
||||
|
||||
## Download Homarr now!
|
||||
|
||||
Homarr is completly free, even for commercial usage!
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
title: "We present: Homarr 0.12 - The fastest homarr version ever!"
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
- walkx
|
||||
- meierschlumpf
|
||||
tags: [homarr, update, breaking changes, version, dnd, documentation]
|
||||
---
|
||||
|
||||
## Multiple widgets of the same type
|
||||
**It's finally here!** You can decorate your dashboard now with as many widgets as you want.
|
||||
This applies to all widgets, but in the case of the IFrame widget, it enables you to:
|
||||
- Integrate as many websites as you want
|
||||
- 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.
|
||||
|
||||
<!-- 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* !!!
|
||||
|
||||
## Media request widgets (Overseerr & Jellyseerr)
|
||||
We've implemented a new widget, that displays a history of all requests.
|
||||
You can't approve from the widget yet, but we plan to add this in the future.
|
||||
|
||||

|
||||
|
||||
## Multiple RSS feeds
|
||||
You can now consume multiple RSS feeds at the same time:
|
||||
|
||||

|
||||
|
||||
## Tab refresh / close confirmation
|
||||
Since some have lost their changes, by refreshing the page in the edit mode. We've added a popup that will ask you to confirm you want to reload the page :
|
||||
|
||||

|
||||
|
||||
## Environment variable for default theme
|
||||
You can now set the default color scheme using environment variables:
|
||||
|
||||
``DEFAULT_COLOR_SCHEME`` = ``[dark, light]``
|
||||
|
||||
## Global error catching for widgets
|
||||
Previously, widgets were able to crash your entire dashboard and just a blank page would be displayed.
|
||||
Now, users will still be able to use their dashboard and an information message will be displayed instead.
|
||||
|
||||
## Migration to Mantine V6
|
||||
We've migrated Mantine to version 6. This will bring some upstream changes for responsiveness and usability.
|
||||
|
||||
## Fix width and height in the calendar
|
||||
In 0.11.5 and previous versions, the calendar scaled poorly if the widget was resized.
|
||||
Now, the calendar will fill out the entire available space and scale nicely with your dashboard.
|
||||
|
||||

|
||||
|
||||
## Performance improvements
|
||||
We now use several methods to improve performance and reduce network requests.
|
||||
Homarr will load now within milliseconds and cache data in your browser.
|
||||
|
||||
## Docker Socket via TCP
|
||||
Previously, it was not possible to mount sockets via TCP (eg. ``tcp://traefik-docker-socket-proxy:2375``).
|
||||
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
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
title: "Authentication in Homarr - 0.14 is here!"
|
||||
authors:
|
||||
- ajnart
|
||||
- manuel-rw
|
||||
- tagashi
|
||||
- meierschlumpf
|
||||
tags: [homarr, update, breaking changes, version, authentication, notepad, security]
|
||||
---
|
||||
|
||||
One of the most requested features ever has arrived.
|
||||
We worked for months to prepare Homarr for this update and we're happy that we can finally
|
||||
provide you a viable solution today.
|
||||
|
||||
## Highlights at a glance
|
||||
|
||||
- :rocket: Completly overhauled authentication system with user management
|
||||
- :sparkles: Improved UI for boards (renamed dashboards to boards).
|
||||
- :computer: Server side preferences -> Dark mode and language are not synced accross all your devices
|
||||
- :link: Invitation links to easily onboard new users
|
||||
- :busts_in_silhouette: Overhauled board management with easy control if anonymous users can view your board
|
||||
- :brain: AI assistant on the documentation to answer your questions: https://homarr.dev/
|
||||
- 📝 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
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- Removed ``PASSWORD`` environment variable
|
||||
- Removed ``DISABLE_EDIT_MODE`` environment variable
|
||||
- Removed ``EDIT_MODE_PASSWORD`` environment variable
|
||||
- Added mounting point ``/data`` for your database
|
||||
- Require at least one administrator account on your instance. An onboarding process will automatically set this up.
|
||||
- We've added analytics to the documentation and Homarr itself. We use [Umami Analytics](https://umami.is/) which collects anonymous data exclusively and protects your personal data rights. You can disable this using the environment variable ``DISABLE_ANALYTICS``.
|
||||
|
||||
## Changes in detail
|
||||
|
||||
### Management pages
|
||||

|
||||
One of the most requested features ever has been ___authentication___. We've completely *removed* the ``PASSWORD``, ``DISABLE_EDIT_MODE`` and ``EDIT_MODE_PASSWORD`` and replaced it by [a secure authentication system](https://next-auth.js.org/) that enables you to manage multiple users easily. We will allow for more detailed permission and user management soon. LDAP and OIDC integrations are currently also in progress and will arrive in a future version.
|
||||
|
||||
### New dashboard layout
|
||||

|
||||
|
||||
We've updated the dashboard user interface slightly to an updated design.
|
||||
|
||||
### Server side preferences page
|
||||

|
||||
|
||||
Previously, preferences were stored per dashboard and applied to all users.
|
||||
In this release, we moved this page to a separate link and store this on the server.
|
||||
This means that regardless what device your on - your preferences will stay the same when you're logged in.
|
||||
|
||||
### Invitations for new users
|
||||

|
||||
|
||||
Tired of creating user accounts by yourself? With the new "invites" feature you can invite new users by simply sending them a link.
|
||||
This link is valid only once and must have an expiration date - more info on that in our documentation.
|
||||
|
||||
Additionally, you can deny users, that are not logged into an account, to access your board with a simple toggle:
|
||||
|
||||

|
||||
|
||||
### New "boards" page
|
||||
Previously, dashboards were managed in an ugly and hard to control popup.
|
||||
We've moved this now to the new management pages as well and it's now much easier to control:
|
||||
|
||||

|
||||
|
||||
|
||||
### Read-only for non-administrators and guests
|
||||
Yes, managing access to your dashboard is now much easier!
|
||||
Guests and non-administrators cannot edit your dashboard in any way (edit is hidden):
|
||||
|
||||

|
||||
|
||||
This is not an definite solution and will be significantly improved in an upcoming update.
|
||||
|
||||
-----
|
||||
|
||||
We are aware that this update has many breaking changes and may confuse some users.
|
||||
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,21 +0,0 @@
|
||||
---
|
||||
title: "Updated documentation with better organization & design"
|
||||
authors:
|
||||
- manuel-rw
|
||||
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.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
A few highlights include:
|
||||
|
||||
- Updated navigation bar for much easier navigation
|
||||
- Improvements to the landing page
|
||||
- Completely revamped [about us page.](/about-us)
|
||||
- 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.
|
||||
@@ -1,88 +0,0 @@
|
||||
---
|
||||
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.
|
||||
|
||||
This isn't our usual update. ``1.0`` doesn't use the same code from previous versions, but has been written from the ground up.
|
||||
Therefore, this version contains an unusual amount of breaking changes.
|
||||
This will be a one time occurrence and version > ``1.0`` will have less breaking changes.
|
||||
This time more important than every; **read the breaking changes** list and **create a backup** before updating.
|
||||
Some third party installation methods may take multiple days or weeks to update to the new version.
|
||||
|
||||
See [this guide on how to import your previous Homarr dashboard](/docs/getting-started/after-the-installation#importing-a-zip-from-version-before-100)
|
||||
|
||||
## Changes at a glance
|
||||
- **Asynchronous integrations system**: In versions prior to ``1.0``, we fetched data upon each request synchronously.
|
||||
This means that the user had to wait for the requests to finish so any data would appear.
|
||||
A page reload will trigger another request - and worse, this will happen for every user accessing your dashboard.
|
||||
In ``1.0``, we have implemented a new system that gets rid of this system and scales linearly.
|
||||
It will fetch data in background, even if you are not on the page.
|
||||
- **Permissions system**: You can now use detailed permissions to allow or prohibit users from certain actions.
|
||||
For easier management, you can organize users into groups and manage them all at the same time.
|
||||
- **Integration testing**: Starting from ``1.0``, we will require you to test the integration before you can use it.
|
||||
This guarantees that you've configured your integration correctly and helps you find errors faster.
|
||||
- **Re-imagined widgets**: All widgets have been rewritten & improved. They offer much better performance,
|
||||
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.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Breaking changes
|
||||
:::warning
|
||||
|
||||
Please consult the breaking changes list & upgrade guide if you used third-party installation methods.
|
||||
|
||||
:::
|
||||
|
||||
- Docker Image
|
||||
- Removed support for ``arm/v7`` -> upgrade to newer architectures as an alternative
|
||||
- Renamed image from ``ghcr.io/ajnart/homarr`` to ``ghcr.io/homarr-labs/homarr``. We now use semantic versioning for our release numbers.
|
||||
- Environment Variables
|
||||
- New required env variable ``SECRET_ENCRYPTION_KEY`` used for encryption of secrets in the database
|
||||
- Removed ``DISABLE_ANALYTICS``, can now be configured via UI
|
||||
- Renamed ``AUTH_PROVIDER`` to ``AUTH_PROVIDERS``
|
||||
- Renamed ``DATABASE_URL`` to ``DB_URL`` and made it optional conditionally. If ``DB_HOST`` is set, this is optional - otherwise it's required.
|
||||
- Changed ``DOCKER_HOST`` to two new variables for hostnames (``DOCKER_HOSTNAMES``) and ports (``DOCKER_PORTS``). This allows for multiple docker integrations to be configured.
|
||||
- ``AUTH_LOGOUT_REDIRECT_URL`` must now be a valid Url
|
||||
- Increased minimum length of ``AUTH_OIDC_CLIENT_ID`` to 1
|
||||
- Increased minimum length of ``AUTH_OIDC_CLIENT_SECRET`` to 1
|
||||
- Removed ``AUTH_OIDC_TIMEOUT``
|
||||
- Removed ``AUTH_LDAP_ADMIN_GROUP``, ``AUTH_LDAP_OWNER_GROUP``, ``AUTH_OIDC_ADMIN_GROUP`` and ``AUTH_OIDC_OWNER_GROUP`` -> groups are now managed without manual env binding
|
||||
- ``CI`` will now also trigger an env validation skip like ``SKIP_ENV_VALIDATION``
|
||||
- Mounts
|
||||
- Removed ``/app/data/configs`` -> JSON configs have been removed in favour of relation databases
|
||||
- Removed ``/app/public/icons`` -> Images can not be uploaded via the [media management](/docs/management/media/) in the UI
|
||||
- Moved ``/data`` to ``/appdata`` and restructured contents (not backwards compatible!) -> Create a backup before migration of the entire directory to copy data to new Homarr
|
||||
- Widgets:
|
||||
- Calendar:
|
||||
- Removed option ``Use Sonarr v4 API``
|
||||
- Removed option ``Use Sonarr v5 API``
|
||||
- Removed option ``Font Size``
|
||||
- Removed option ``Hide week days``
|
||||
- Download speed: We have removed this widget in favour of the brand new download widget which will also display this data.
|
||||
- Dash. widget: We have removed this widget in favour of the System Health Widget. It will also display all data and some more than the old widget.
|
||||
- Database:
|
||||
- Changed schema of the database to a completely new schema. They are not backwards compatible. Please export the data from old Homarr in the UI and import it in ``1.0``
|
||||
- Removed all usage of JSON files to store data. Import JSON files in Homarr ``1.0`` to import your dashboards.
|
||||
- Removed ``schemaVersion`` from JSON and use the drizzle migrations to keep track of schema versions and take care of migrations.
|
||||
- Web Server:
|
||||
- Added an internal Nginx proxy to forward requests to two servers inside the container for technical reasons.
|
||||
This could impact edge cases with iframes, cookies and custom setups.
|
||||
- Dependencies
|
||||
- We now depend on [Redis](https://redis.io/) as a real time messaging bus
|
||||
- Development (relevant for contributors):
|
||||
- Migrated to [pnpm](https://pnpm.io/)
|
||||
- Migrated to a mono-repository
|
||||
- All source code has been migrated to the [homarr-labs GitHub organization](https://github.com/homarr-labs)
|
||||
- Platform specific breaking changes:
|
||||
- TrueNAS
|
||||
- The TrueCharts app chart is permanently deprecated and no longer maintained / supported. Please migrate to Electric Eel or a different Kubernetes platform.
|
||||
- Unraid
|
||||
- The Homarr app must be reinstalled. The existing directories on your disk are not compatible with Homarr 1.0. Export your old data in < `` 1.0`` and install using different file system locations.
|
||||
- Kubernetes
|
||||
- The official Helm chart has moved from https://github.com/oben01/charts to https://github.com/homarr-labs/charts
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
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.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## How to use the beta with docker
|
||||
|
||||
To use the beta with docker you can use the following docker-compose.yml configuration:
|
||||
|
||||
```yml title="docker-compose.yml"
|
||||
#---------------------------------------------------------------------#
|
||||
# Homarr - A simple, yet powerful dashboard for your server. #
|
||||
#---------------------------------------------------------------------#
|
||||
services:
|
||||
homarr:
|
||||
container_name: homarr
|
||||
image: ghcr.io/homarr-labs/homarr:beta
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
ports:
|
||||
- '7575:7575'
|
||||
```
|
||||
|
||||
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)
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,55 +0,0 @@
|
||||
---
|
||||
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.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
## Export your data
|
||||
|
||||
As 1.0 is a complete rewrite of Homarr and therefor not compatible with the old docker mounts, we need to export your data first.
|
||||
For this you can navigate to the `Migrate to 1.0` page which you can find under `Management` > `Tools` > `Migrate to 1.0`.
|
||||
Here you can select which things you want to export and download the zip file.
|
||||
It will also show you a secret key which you will need to import the secret data like user password hashes, salts and integration secrets.
|
||||
|
||||

|
||||
|
||||
## Create a parallel installation
|
||||
|
||||
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](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
|
||||
Once the installation is completed you can access the new installation. During the onboarding you'll need to select `Import from Homarr before 1.0`.
|
||||
|
||||

|
||||
|
||||
After that you can upload the zip file you downloaded in the first step.
|
||||
|
||||

|
||||
|
||||
After that you'll get a list with all your boards that can be imported. You can configure how it should be imported and can select which screensizes it should import.
|
||||
|
||||

|
||||
|
||||
Once you have selected everything you can click on `Confirm import and continue` which will open the encryption key input dialog where you can input the secret key you got during the export.
|
||||
|
||||

|
||||
|
||||
After that you can continue the onboarding and you should see all your boards and data in the new installation.
|
||||
|
||||
See [After the installation](https://homarr.dev/docs/getting-started/after-the-installation) for more information.
|
||||
|
||||
## Remove the old installation
|
||||
|
||||
After you are happy with the new installation and everything is working as expected you can remove the old installation.
|
||||
Please make sure to backup the data before you remove the old installation.
|
||||
Also once the port 7575 is free again you can change the port mapping of the new installation to `7575:7575` if you want to.
|
||||
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -1,67 +0,0 @@
|
||||
---
|
||||
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.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||

|
||||
|
||||
Currently an apiKey has the same permissions as the user who created it, however you can only create an apiKey with admin permissions.
|
||||
This means we have to do some extra steps to create a user with only the permissions we need for Argus.
|
||||
|
||||
## Create a group
|
||||
|
||||
In a first step we have to create a group which will allow us to restrict the permissions of the user we will create later.
|
||||
|
||||
1. Go to `Management` > `Users` > `Groups`
|
||||
2. Click on `Create group`
|
||||
3. Enter a name for the group, e.g. `Argus`
|
||||
4. Click on `Create group`
|
||||
5. Click on the newly created group to edit it
|
||||
6. In the `Permissions` tab, select the admin permission and save the changes
|
||||
|
||||
## Create a user
|
||||
|
||||
Now we can create a user which will be used by Argus to access the `/api/info` endpoint.
|
||||
|
||||
1. Go to `Management` > `Users` > `Manage`
|
||||
2. Click on `Create new user`
|
||||
3. Enter a username, e.g. `argus`, click next
|
||||
4. Enter a password for the user, click next
|
||||
5. Add previously created group `Argus` to the user, click next
|
||||
6. Click on next to confirm the user creation
|
||||
|
||||
## Create an apiKey
|
||||
|
||||
Now we want to create therefore we have to log in with the user we just created.
|
||||
|
||||
1. Log out of Homarr
|
||||
2. Log in with the user we just created
|
||||
3. Go to `Management` > `Tools` > `API Keys`
|
||||
4. Click on `Create API Key`
|
||||
5. Copy the generated API key and save it somewhere, you will not be able to see it again
|
||||
6. Logout of Homarr
|
||||
|
||||
## Reduce permissions of the user
|
||||
|
||||
Now we can reduce the permissions of the user to only allow access to general authenticated, but not permission restricted endpoints.
|
||||
|
||||
1. Log in with your admin user
|
||||
2. Go to `Management` > `Users` > `Groups`
|
||||
3. Click on the `Argus` group
|
||||
4. In the `Permissions` tab, remove all permissions
|
||||
5. Save the changes
|
||||
|
||||
## Configure Argus
|
||||
|
||||
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.
|
||||
@@ -1,37 +0,0 @@
|
||||
manuel-rw:
|
||||
name: Manuel
|
||||
page: true
|
||||
url: https://github.com/manuel-rw
|
||||
image_url: https://avatars.githubusercontent.com/u/30572287?v=4
|
||||
socials:
|
||||
github: manuel-rw
|
||||
description: >
|
||||
Junior .NET developer from Switzerland. 🌌 .NET, Next.js, Node.js, Angular
|
||||
ajnart:
|
||||
name: Ajnart
|
||||
page: true
|
||||
url: https://github.com/ajnart
|
||||
image_url: https://avatars.githubusercontent.com/u/49837342?v=4
|
||||
socials:
|
||||
github: ajnart
|
||||
meierschlumpf:
|
||||
name: Meierschlumpf
|
||||
page: true
|
||||
url: https://github.com/Meierschlumpf
|
||||
image_url: https://avatars.githubusercontent.com/u/63781622?v=4
|
||||
socials:
|
||||
github: Meierschlumpf
|
||||
tagashi:
|
||||
name: Tagashi
|
||||
page: true
|
||||
url: https://github.com/tagaishi
|
||||
image_url: https://avatars.githubusercontent.com/u/26098587?v=4
|
||||
socials:
|
||||
github: tagaishi
|
||||
walkx:
|
||||
name: Walkx
|
||||
page: true
|
||||
url: https://github.com/walkxcode
|
||||
image_url: https://avatars.githubusercontent.com/u/71191962?v=4
|
||||
socials:
|
||||
github: walkxcode
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "base-nova",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/css/custom.css",
|
||||
"baseColor": "mist",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "tabler",
|
||||
"rtl": false,
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"label": "Advanced",
|
||||
"collapsed": true,
|
||||
"position": 4,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "Advanced Concepts or features"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"collapsed": true,
|
||||
"label": "Command line interface"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
# Developer Docker CLI
|
||||
|
||||
The host-side developer CLI builds and runs local Homarr images and pull-request previews. Run it directly from a checkout with `pnpm dev:cli -- ...`; no shell-specific setup script is required. It is separate from the recovery CLI included in production images.
|
||||
|
||||
To install an optional `homarr` binary, run `pnpm dev:cli:install` and make your Go bin directory available on `PATH`. GitHub CLI authentication is only needed for pull-request features.
|
||||
|
||||
## Browse development images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli --
|
||||
```
|
||||
|
||||
The main screen combines local `homarr:*` images with open pull requests and their GHCR preview status. Select an image to start or stop it, use `R` to build a remote pull request locally in the background or rebuild a local image, or use `p` to force a remote preview pull. Press `d` to switch to the instance dashboard and `d` again to return without losing the browser state. The explicit `dev` and `dash` subcommands start on either screen.
|
||||
|
||||
A locally built PR image is folded into its remote PR row only when its recorded revision exactly matches the current GitHub head SHA. The row is marked `◆ local`, and starting it prefers that local image without a registry pull. Outdated local builds remain separate rows. Navigation and dashboard switching remain available during background builds.
|
||||
|
||||
## Build images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli -- build <name>
|
||||
pnpm dev:cli -- build --pr <number> [name]
|
||||
```
|
||||
|
||||
The first command builds the current checkout as `homarr:<name>`. The pull-request form uses a depth-one clone in a temporary directory and defaults to `homarr:pr-<number>` when `name` is omitted.
|
||||
|
||||
## Rebuild images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli -- rebuild <name>
|
||||
```
|
||||
|
||||
Images built by this CLI record their source checkout, revision, and optional pull-request number. Rebuild uses that provenance to select the original checkout or create a fresh temporary pull-request checkout.
|
||||
|
||||
## Run images
|
||||
|
||||
```bash
|
||||
homarr run <name>
|
||||
homarr run --pr <number>
|
||||
```
|
||||
|
||||
Add `--detach` to run in the background, `--demo` to enable demo data, or repeat `--env KEY=VALUE` to provide additional environment variables.
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,17 +0,0 @@
|
||||
# Fix usernames
|
||||
|
||||
:::info
|
||||
|
||||
This guide requires that you understand [how to access the CLI](../).
|
||||
|
||||
:::
|
||||
|
||||
If you somehow get into a situation where you have users with usernames that contain uppercase characters and you are no longer able to log in, you can use the following command to fix the usernames.
|
||||
|
||||
```bash
|
||||
homarr fix-usernames
|
||||
```
|
||||
|
||||
This will convert all credential usernames to lowercase and update the database accordingly.
|
||||
|
||||

|
||||
|
Before Width: | Height: | Size: 195 KiB |