refactor(custom-widgets): extract domain package
This commit is contained in:
167 files changed
+12282
-10033
No files matched your search
@@ -0,0 +1,26 @@
|
||||
---
|
||||
sidebar_label: AI and MCP authoring
|
||||
---
|
||||
|
||||
# AI and MCP authoring
|
||||
|
||||
Homarr exposes an iterative, validation-first workflow to authenticated administrators. Agents should continue until validation, preview, and diagnostics are clean; they must not invent inline network access.
|
||||
|
||||
## Recommended loop
|
||||
|
||||
1. Read `customWidget_schema` and the generated component/capability reference.
|
||||
2. Read an existing definition with `customWidget_byId`, or create a v3 draft.
|
||||
3. Call `customWidget_validate`; fix every issue using its exact path.
|
||||
4. Call `customWidget_preview` for the base GET and retain the preview session ID.
|
||||
5. Test each named query with `customWidget_previewQuery`.
|
||||
6. Dry-run every action with `customWidget_simulatePreviewAction`.
|
||||
7. Inspect `customWidget_previewJournal` for status, duration, and method coverage.
|
||||
8. Revalidate, then call import/create or a focused update/patch operation.
|
||||
|
||||
Stored secret values are never readable. Omitting secrets during an update preserves them. MCP actions are simulation-only; enabling live preview actions remains a browser-only administrator decision.
|
||||
|
||||
## Template format
|
||||
|
||||
Use multiline JSX when reading or patching templates. Prefer focused template read/write/patch tools over rewriting an escaped one-line JSON string. When a complete portable artifact is needed, use the authoring bundle placeholder plus a separate fenced JSX block.
|
||||
|
||||
The AI prompt is generated from the same registry, examples, request rules, and limits as the validator. Agents should still trust validation output over prompt memory because the installed Homarr version is authoritative.
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
sidebar_label: Maintainer architecture
|
||||
---
|
||||
|
||||
# Maintainer architecture
|
||||
|
||||
Reusable Custom Widget behavior belongs to `@homarr/custom-widgets`:
|
||||
|
||||
- `core` owns schemas, descriptors, migrations, import/export, examples, diagnostics metadata, and prompt generation.
|
||||
- `jsx` owns AST parsing, shared policy, interpreter, bindings, registry, wrappers, and analysis.
|
||||
- `runtime` owns renderers and named-request controls through injected ports.
|
||||
- `server` owns manifests, network execution, caching, limits, preview sessions, and typed domain errors.
|
||||
- `workbench` owns form contracts, editor analysis, preview UI, response tree, and import review through injected ports.
|
||||
|
||||
The API package retains persistence and board authorization adapters. Widgets retain board registration and runtime transport wiring. Next.js routes retain navigation and shells. The domain package must never import API, widgets, or route modules.
|
||||
|
||||
## Add a display type
|
||||
|
||||
Extend the typed descriptor and discriminated schema, defaults/hydration/serialization, renderer, workbench section, preview, import round-trip fixtures, docs, and translations. Create, edit, import, export, MCP, and preview must all use the same transform.
|
||||
|
||||
## Add a JSX component
|
||||
|
||||
Classify the upstream export, document safety mode and supported props, add a wrapper if behavior needs constraining, and update registry tests. CI fails when a Mantine upgrade introduces an unclassified export.
|
||||
|
||||
## Change networking
|
||||
|
||||
Implement policy below the framework-neutral server boundary and expose infrastructure through a typed port. Add tests for permissions, typed substitution, DNS classes, redirects, headers, timeouts, limits, redaction, cache behavior, and action non-retry semantics before wiring the tRPC adapter.
|
||||
|
||||
Production files target 300 lines. Algorithmic AST/network modules may use a documented 400-line ceiling. Dependency-boundary, cycle, file-size, translation, generated-artifact, component-classification, focused unit, integration, E2E, accessibility, and docs-build gates protect the architecture. After a Next.js production build, `pnpm check:custom-widget-bundle` also enforces a 768 KiB gzip ceiling across the client chunks referenced by Custom JSX.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
sidebar_label: Authoring workbench
|
||||
---
|
||||
|
||||
# Authoring workbench
|
||||
|
||||
The Custom Widget form is a continuous workflow: choose a display, configure the HTTP connection, configure the display, then test it in the adjacent preview. Each section stays visible so you can revisit earlier choices without losing context.
|
||||
|
||||
## Choose a display
|
||||
|
||||
The display grid is the first product decision. Built-in displays are best for common metrics, tables, statuses, and actions. Choose Custom JSX when the layout needs multiple Mantine components, conditional presentation, charts, dates, or named requests.
|
||||
|
||||
Changing display type preserves shared connection fields and initializes safe display defaults. A Custom JSX template is never blank; new and migrated forms receive the tested starter fixture.
|
||||
|
||||
## Test and preview
|
||||
|
||||
The preview has four tabs:
|
||||
|
||||
- **Widget** renders at compact, standard, or wide board-like sizes.
|
||||
- **Response** uses a tree to inspect data, copy paths, and insert paths into JSX. The external-response action opens a temporary local blob and never publishes the response.
|
||||
- **Request** shows the redacted base request, named capabilities, status, duration, and preview journal.
|
||||
- **Diagnostics** shows template size, named requests, network scope, and blocking or advisory issues.
|
||||
|
||||
Only GET base requests run automatically. Preview actions are simulated until an administrator explicitly enables live actions for the five-minute preview session. DELETE still requires confirmation.
|
||||
|
||||
## Keyboard and clipboard
|
||||
|
||||
The JSX and JSON editors support native Ctrl/Cmd+Z and redo, plus visible toolbar controls. Cursor position, character count, and the 50,000-character limit remain visible.
|
||||
|
||||
On the list page, paste a complete v3 export with Ctrl+V or Command+V to import it as new. On an edit page, pasting opens a replacement review. Nothing is applied before the origin, methods, credential inheritance, and permissions are shown.
|
||||
|
||||
For AI output, prefer the authoring bundle format: a JSON block containing `"template": "__HOMARR_TEMPLATE__"`, followed by a JSX block. This keeps multiline JSX readable and avoids JSON escape errors.
|
||||
|
||||
## Save behavior
|
||||
|
||||
Errors block save; warnings do not. Server validation runs again on every create or update. Leaving with dirty changes triggers protection. Secrets omitted during edit remain unchanged, and secrets are never exported.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
sidebar_label: Custom JSX language
|
||||
---
|
||||
|
||||
# Custom JSX language
|
||||
|
||||
Custom JSX is a declarative expression language, not browser JavaScript. Homarr parses an explicit AST subset and emits React elements from a classified component registry. Templates cannot access browser globals, native prototypes, constructors, refs, portals, raw event handlers, or direct `fetch`.
|
||||
|
||||
## Data and rendering
|
||||
|
||||
The base GET response is available as `data`. Safe member access, conditionals, arrays, object literals, and interpreter-created collection callbacks can compose displays:
|
||||
|
||||
```jsx
|
||||
<Stack gap="xs">
|
||||
{data.services
|
||||
.filter((service) => service.visible)
|
||||
.map((service) => (
|
||||
<Group key={service.id} justify="space-between">
|
||||
<Text>{service.name}</Text>
|
||||
<Badge color={service.online ? "green" : "red"}>{service.status}</Badge>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
```
|
||||
|
||||
Properties are resolved own-only. Reflective names such as `constructor`, `prototype`, `__proto__`, `caller`, `callee`, `arguments`, `bind`, `call`, and `apply` are rejected even when computed or Unicode escaped.
|
||||
|
||||
## Components and props
|
||||
|
||||
Every Mantine export is classified as allowed, wrapped, or denied. The [generated component reference](./#component-reference) is authoritative. Props are allowlisted per component; unsupported props produce warnings and are ignored. Polymorphic roots, unsafe URLs, portals, positioning escapes, refs, and function props are stripped.
|
||||
|
||||
`SubData` is a convenience for simple response paths. Prefer the render-child pattern for rich nested results:
|
||||
|
||||
```jsx
|
||||
<SubFetch requestId="detail" params={{ id: data.selectedId }}>
|
||||
{(detail, meta) => (
|
||||
<Stack>
|
||||
<Title>{detail.name}</Title>
|
||||
<Text>HTTP {meta.status}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</SubFetch>
|
||||
```
|
||||
|
||||
## Safe helpers and budgets
|
||||
|
||||
Bindings include restricted conversion, string, array, object, JSON, math, URI, and pure date helpers. Native `Date` objects and native prototypes are not exposed.
|
||||
|
||||
Templates are limited to 50,000 characters, 10,000 rendered nodes, and explicit AST-depth, operation, collection, and string budgets. Budget errors stop only the Custom JSX renderer and are contained by its error boundary.
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
sidebar_label: Extension recipes
|
||||
---
|
||||
|
||||
# Custom Widget extension recipes
|
||||
|
||||
These recipes keep every authoring and runtime path on the same typed source of truth. Run `pnpm check:custom-widgets` after each structural change and add focused package tests before changing an adapter.
|
||||
|
||||
## Add a built-in display type
|
||||
|
||||
1. Add the discriminated configuration schema in `@homarr/custom-widgets/core`.
|
||||
2. Add one descriptor with its icon, category, localized metadata keys, defaults, hydration, serialization, and validation.
|
||||
3. Add the renderer to the runtime display map. It must consume normalized display data and injected actions only.
|
||||
4. Add a bounded workbench section that edits the descriptor-owned form fields.
|
||||
5. Add one fixture and assert create, edit, v2/v3 import, export, MCP, and preview round trips against it.
|
||||
6. Add translation keys and update the display table in the user guide.
|
||||
|
||||
Do not add route-specific conversion code. `buildDisplayFormValues` and `buildDisplayConfigFromFormValues` are the only form conversion boundary.
|
||||
|
||||
## Add or update a JSX component
|
||||
|
||||
1. Classify the Mantine export as `allowed`, `wrapped`, or `denied` in the component registry. Denied entries require a reason.
|
||||
2. Declare supported props and subcomponents. Function props, refs, portals, polymorphic roots, and unsafe positioning stay denied unless a wrapper removes the unsafe behavior.
|
||||
3. Implement wrappers in the JSX package and register them through the package runtime-component factory.
|
||||
4. Add analyzer and renderer tests for accepted props, stripped props, reflective-property attacks, and the rendered-node budget.
|
||||
5. Regenerate the docs reference and verify the editor completion and AI prompt include the same entry.
|
||||
|
||||
The component-classification test must pass against the locked Mantine version. Never expose an upstream namespace or native prototype as a shortcut.
|
||||
|
||||
## Add a named-request capability
|
||||
|
||||
1. Extend the request schema and manifest renderer in `core` and `server` without accepting a raw runtime URL, method, header, or body.
|
||||
2. Enforce permission, network scope, redirect, DNS, header, body, response, concurrency, and rate policies in the framework-neutral server service.
|
||||
3. Expose infrastructure through a typed server port. Translate domain errors only in the API adapter.
|
||||
4. Add the control to runtime through the injected request port. Queries use query semantics; actions require a gesture and use mutation semantics.
|
||||
5. Add preview simulation and redacted-journal coverage. Live actions remain an explicit administrator-only browser choice.
|
||||
6. Update MCP validation and simulation tools without exposing stored secrets or unrestricted action execution.
|
||||
|
||||
Tests must prove actions are never mounted automatically, polled, retried, cached, or redirected. DELETE must retain full permission and confirmation.
|
||||
|
||||
## Add a workbench feature
|
||||
|
||||
1. Put reusable state, analysis, or presentation in `@homarr/custom-widgets/workbench`.
|
||||
2. Receive localized messages and typed callbacks as props. The package cannot import the tRPC client, Next.js navigation, notifications, or Homarr widget adapters.
|
||||
3. Keep the Next.js component as a transport and page-shell adapter.
|
||||
4. Cover keyboard operation, focus order, reduced motion, 320/768/desktop layouts, dirty-state behavior, and error-boundary recovery.
|
||||
5. Keep each production module below 300 lines and preserve the continuous two-pane form behavior.
|
||||
|
||||
## Change examples or AI authoring
|
||||
|
||||
Examples live as typed shared fixtures. Tests, snippets, documentation data, and the AI prompt consume those fixtures. AI output uses an authoring bundle: a JSON block with `__HOMARR_TEMPLATE__`, followed by a readable multiline JSX block. Do not require agents to escape an entire template into one JSON string.
|
||||
|
||||
After changing a fixture, validate every example, render it, regenerate the component reference, and run the prompt snapshot tests. MCP agents must be able to read a widget, patch template lines with a revision, validate, preview named queries, simulate actions, inspect the journal, and update the definition without receiving secret values.
|
||||
|
||||
## Required checks
|
||||
|
||||
```bash
|
||||
pnpm check:custom-widgets
|
||||
pnpm test
|
||||
pnpm test:e2e
|
||||
pnpm turbo typecheck --affected
|
||||
pnpm turbo lint --affected
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
```
|
||||
|
||||
Also run the component-classification, generated-reference drift, bundle-budget, accessibility, and focused package/API suites configured by the repository. A change is incomplete if any create, edit, import, export, MCP, preview, or board-runtime path uses a separate transform.
|
||||
@@ -12,6 +12,17 @@ import { CustomJsxComponentReference } from "@site/src/components/custom-jsx-com
|
||||
|
||||
Custom widgets connect a dashboard item to an HTTP API without compiling a new Homarr widget. Administrators choose the endpoint and authentication, then use a built-in display type or a Custom JSX template to render the response.
|
||||
|
||||
## Guides
|
||||
|
||||
- [Authoring workbench](./authoring): create, edit, preview, paste, undo, import, and export.
|
||||
- [Custom JSX language](./custom-jsx): supported syntax, components, bindings, diagnostics, and limits.
|
||||
- [Named requests and security](./requests-and-security): permissions, network scopes, request manifests, and server protections.
|
||||
- [AI and MCP authoring](./agent-authoring): an iterative agent workflow that validates and previews before saving.
|
||||
- [Compatibility and migration](./migration): v1/v2 JSX and v2/v3 import behavior.
|
||||
- [Troubleshooting](./troubleshooting): common editor, preview, permission, and networking failures.
|
||||
- [Maintainer architecture](./architecture): package boundaries, extension points, and required tests.
|
||||
- [Extension recipes](./extension-recipes): step-by-step changes for displays, JSX components, requests, and workbench features.
|
||||
|
||||
:::caution
|
||||
|
||||
A custom widget can expose data or perform actions with the credentials configured for it. Import definitions only from sources you trust and review every network capability before using the widget on a shared board.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
sidebar_label: Compatibility and migration
|
||||
---
|
||||
|
||||
# Compatibility and migration
|
||||
|
||||
Homarr preserves existing display definitions while moving network-enabled Custom JSX to the named-request model.
|
||||
|
||||
## JSX API versions
|
||||
|
||||
Missing `jsxApiVersion` means v1. Display-only v1 templates can be opened and saved as v2 with an empty request manifest. Inline `url`, `method`, `headers`, and body props are inert in v2.
|
||||
|
||||
Static v1 network components can be converted by creating a named request and replacing inline capability props with `requestId` and typed `params`. Dynamic target construction requires an administrator to design an explicit path template and parameter list. Existing non-GET base definitions stop polling and show a network-review state.
|
||||
|
||||
## Import versions
|
||||
|
||||
Exports use `homarr-custom-widget-v3`; v2 imports remain accepted. Import migration normalizes display configuration, preserves v1 semantics, and never imports secret values. The review screen must be accepted before persistence.
|
||||
|
||||
Test migration with the same base response and compare rendered output, query results, action simulation, permissions, and journal entries before enabling the migrated definition on shared boards.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
sidebar_label: Requests and security
|
||||
---
|
||||
|
||||
# Named requests and security
|
||||
|
||||
Custom JSX v2 uses a saved request manifest. Runtime components pass only a `requestId` and declared primitive parameters; they cannot change the URL, method, headers, authentication, or body shape.
|
||||
|
||||
## Capability kinds
|
||||
|
||||
`SubFetch` references a GET `query`. `ActionButton` and `ToggleSwitch` reference a POST, PUT, PATCH, or DELETE `action`. Base data is GET-only. Actions require an explicit gesture and are never polled, cached, retried automatically, or run on mount.
|
||||
|
||||
Default board permissions are view for queries, modify for POST/PUT/PATCH, and full for DELETE. Viewer actions require explicit high-risk approval; anonymous users cannot execute actions. DELETE always requires confirmation.
|
||||
|
||||
## Parameter substitution
|
||||
|
||||
Path placeholders use `{name}` and are URL-encoded by the server. Runtime parameters must exactly match all declared names and types. JSON bodies substitute `{ "$param": "name" }` recursively and preserve string, number, or boolean types.
|
||||
|
||||
## Network boundary
|
||||
|
||||
Named paths stay on the base URL origin. HTTP and HTTPS are accepted; URL credentials and fragments are rejected. DNS answers are classified and pinned. Public, private, and loopback scopes progressively allow destinations, but link-local, metadata, unspecified, multicast, documentation, carrier-grade NAT, and IPv4-mapped addresses are always blocked.
|
||||
|
||||
Queries may follow three revalidated same-origin redirects. Actions do not follow redirects. Credentials never cross origins. Reserved authentication, cookie, host, forwarding, proxy, content-length, security, and hop-by-hop headers cannot be declared.
|
||||
|
||||
## Limits
|
||||
|
||||
- Request body: 10 KiB UTF-8.
|
||||
- Decompressed response: 1 MiB, 32 JSON levels, 50,000 JSON nodes.
|
||||
- Per user/item: 60 queries, 10 actions, and 3 deletes per minute.
|
||||
- Concurrency: 4 per user/item and 8 per definition.
|
||||
|
||||
Successful GETs can use manifest cache TTLs and single-flight deduplication. Logs contain redacted origin/method/error metadata, never secrets, request bodies, or query values.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
sidebar_label: Troubleshooting
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## The preview says the template is required
|
||||
|
||||
New Custom JSX forms always include the starter fixture. If an imported definition has an empty template, reopen its import review; malformed imports must be corrected rather than previewed as JSX.
|
||||
|
||||
## A prop is ignored
|
||||
|
||||
The diagnostics name the component and prop. Check the generated component reference. Unsupported props are intentionally removed; use a supported layout primitive or wrapper prop instead of polymorphic, portal, ref, positioning, or raw handler props.
|
||||
|
||||
## A request does not run
|
||||
|
||||
Confirm that the component kind matches the manifest: `SubFetch` needs a query; action controls need an action. Parameter names and primitive types must match exactly. Base requests and queries require GET. Actions only run from a gesture, and edit mode disables controls.
|
||||
|
||||
## Network scope errors
|
||||
|
||||
Resolve the hostname from the Homarr server environment. Select private or loopback scope only when required and understood. Link-local and metadata destinations cannot be enabled by any scope. Redirects must remain on the base origin.
|
||||
|
||||
## Permission errors
|
||||
|
||||
The placed board item is the authorization anchor. A definition ID alone cannot authorize credential use. Verify board view/modify/full access, explicit viewer-action approval, and that the caller is not anonymous.
|
||||
|
||||
## Stale or failed preview data
|
||||
|
||||
Connection, authentication, body, method, secret, or manifest changes mark samples stale. Run the GET test again. Background query failures keep prior content visible and offer retry; actions roll toggles back and show error feedback.
|
||||
|
||||
## Import paste is ignored
|
||||
|
||||
Paste a complete v2/v3 JSON export or authoring bundle while the list or edit page is focused. Ordinary text and partial JSON are ignored so normal typing is not intercepted.
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user