Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d7ace51
docs: add the markdown transforms the Node-RED library needs to becom…
dimitrieh Sep 7, 2026
c34ba12
docs: replace the '100+ Tutorials (2026)' page with a Node-RED sectio…
dimitrieh Sep 7, 2026
e41f037
docs: move the Node-RED library into /docs and off Eleventy
dimitrieh Sep 7, 2026
574dd71
docs: fetch core node help per build, and fail the build when it is m…
dimitrieh Sep 7, 2026
d989be2
docs: point the rest of the site at the library's new /docs URLs
dimitrieh Sep 7, 2026
7ccace0
docs: fix the anchors that Nuxt slugs differently from Eleventy
dimitrieh Sep 7, 2026
b46fa5d
docs: fix four defects the code review found in the migration
dimitrieh Sep 7, 2026
35d1a80
docs: keep the nav labels and the browser titles the move was meant t…
dimitrieh Sep 8, 2026
88b3b98
docs: put the core nodes back under their palette categories
dimitrieh Sep 8, 2026
11cde53
docs: stop a use-case include adding a second H1 to a core node page
dimitrieh Sep 8, 2026
1b95eac
docs: fix the links the category nesting broke, and the comments it i…
dimitrieh Sep 8, 2026
480216b
docs: take the reviewer's metaTitle wording for the Node-RED section …
dimitrieh Sep 8, 2026
e583b3d
docs: fix six defects a review round found, all of them silent
dimitrieh Sep 8, 2026
ef6c711
docs: make the move's invariants checks instead of things I verified …
dimitrieh Sep 8, 2026
9264378
docs: make the core node pages content instead of generator output
dimitrieh Sep 8, 2026
6093ccd
docs: fix four losses an adversarial re-check of the core node pages …
dimitrieh Sep 8, 2026
2a11826
docs: close the MDC blocks the CTA conversion left open
dimitrieh Sep 8, 2026
5fc9bbf
Merge branch 'main' into docs/node-red-library-into-docs
dimitrieh Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 29 additions & 3 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,38 @@ Collection config: `nuxt/content.config.ts` (defines the `handbook` collection)

### Product docs

**Source:** `flowfuse/flowfuse` repo, `docs/` directory — **do not edit in this repo**; cloned automatically at build time by `nuxt/modules/docs-source.ts`.
`/docs` is assembled from two sources. Which one owns a page decides where to edit it,
and one of the two is not editable here at all.

| Source | Owns | Edit where |
|---|---|---|
| `flowfuse/flowfuse` repo, `docs/` | the product documentation, versioned with the code | that repo, **not here** |
| `nuxt/content-guides/` | this repo's own guides: the Application Guide, the Node-RED guide, and the Node-RED library under `/docs/node-red/` and `/docs/flowfuse-nodes/` | **here**, in place |

The 38 core-node pages under `/docs/node-red/core-nodes/**` are ordinary guide files in
that second source, not generator output. Each ends with a
`::node-red-help{category=... file=... node=... name=...}` directive; the component
(`nuxt/components/content/NodeRedHelp.vue`) asks `nuxt/server/api/node-red-help.get.ts` for
that node's built-in help, which fetches the Node-RED project's locale file, selects the
matching `<script data-help-name=...>` block and sanitises it. The selection rules and the
sanitiser allowlist both live in `nuxt/lib/node-red-help.mjs` so `npm test` can check them
without a network. Note what happens when a reference is wrong: the route answers an error,
the component renders its "could not be read" notice instead of the help, and the page
prerenders successfully anyway - Nitro's prerender has no `failOnError`. So a broken
reference costs a page its whole Node help section with nothing failing, which is why
`nuxt/lib/node-red-help.test.mjs` validates every reference in the tree offline.

**URL:** `/docs/{section}/{slug}/`
**Rendered by:** Nuxt — `nuxt/pages/docs/[...slug].vue` + `DocsLeftNav` component
**Local content:** `nuxt/content/docs/` (gitignored, build-generated)
**Local content:** `nuxt/content/docs/` (gitignored, build-generated — never edit, it is wiped every build). Both sources are copied into it: `nuxt/lib/docs-sync.mjs` brings in the flowfuse tree and `nuxt/lib/guides-sync.mjs` overlays `nuxt/content-guides/` on top (stamping each guide with an `editUrl`), so `@nuxt/content` sees one `docs` collection. A guide edit therefore only reaches a running dev server once that overlay re-runs: `npm run dev:docs` is the watcher that does it, and without it an edit under `nuxt/content-guides/` shows up on the page only after a restart.
**Local assets:** `nuxt/public/docs/` (images, etc.)

A page's browser title is `metaTitle || navTitle || title` (`nuxt/lib/docs-page-title.mjs`).
`navTitle` is the sidebar label and is often much shorter, so a page whose full title
matters for search needs `metaTitle` set. **Any frontmatter key not declared in the `docs`
collection schema in `nuxt/content.config.ts` is stripped before a page ever sees it**, which
makes a missing declaration look like a missing value rather than an error.

```yaml
---
navTitle: "Page title for sidebar"
Expand All @@ -200,7 +226,7 @@ layout: redirect
---
```

**Nav groups** (in order): FlowFuse User Manuals · Device Agent · FlowFuse Cloud · FlowFuse Self-Hosted · Support · Contributing
**Nav groups** (in order): Application Guide · FlowFuse User Manuals · Device Agent · FlowFuse Cloud · FlowFuse Self-Hosted · Support · Contributing · Node-RED
**Nav composable:** `nuxt/composables/useDocsNav.ts`
**Collection config:** `nuxt/content.config.ts` (defines the `docs` collection)

Expand Down
13 changes: 0 additions & 13 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const pluginTOC = require('eleventy-plugin-toc');
const { decodeHTML } = require('entities');
const imageHandler = require('./lib/image-handler.js')
const site = require("./src/_data/site");
const coreNodeDoc = require("./lib/core-node-docs.js");
const { isSearchPage, isSearchUrl, extractHeadingRecords } = require("./lib/search-index.js");
const yaml = require("js-yaml");
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
Expand Down Expand Up @@ -215,18 +214,6 @@ module.exports = function(eleventyConfig) {
new FlowRenderer().renderFlows(JSON.parse(flow${flowId}.replace(/&gt;/g,'>').replace(/&lt;/g,'<').replace(/&amp;/g,'&')), { container: document.getElementById('nr-flow-${flowId}') })</script>`
});

eleventyConfig.addGlobalData("coreNodesArray", () => {
// Read the JSON file with core nodes
const coreNodes = JSON.parse(fs.readFileSync(path.join(__dirname, 'src', '_data', 'coreNodes.json'), 'utf-8'));

// Transform coreNodes object into an array
return Object.entries(coreNodes).map(([key, nodes]) => ({ key, nodes }));
})

eleventyConfig.addAsyncShortcode("coreNodeDoc", async function (category, node) {
return await coreNodeDoc(category, node)
});

eleventyConfig.addFilter("filterNodeCategory", function(nodes, category) {
if (category === "all") {
return nodes;
Expand Down
27 changes: 0 additions & 27 deletions lib/core-node-docs.js

This file was deleted.

13 changes: 12 additions & 1 deletion nuxt/components/content/CtaImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ const props = defineProps<{
src: string
alt: string
cta: 'sign-up' | 'demo' | 'contact' | 'pricing'
/**
* What the event's `reference` should say, for a page that is not a blog post.
*
* Blog posts leave this unset and get `Blog: <post title>` from the injected title
* below. The Node-RED guides under /docs have no injected title and no post, and
* carried their own reference string in a hand-written `onclick="capture(...)"` while
* they were Eleventy pages - MDC strips `onclick`, so that tracking went silent the
* moment they became content files. This prop is how those pages keep the exact
* reference they were already reporting, rather than a new dimension.
*/
reference?: string
}>()

const POSITION = 'inline-image'
Expand All @@ -33,7 +44,7 @@ const postTitle = inject<Ref<string> | undefined>('blogPostTitle', undefined)

function onClick () {
capture(EVENT, {
reference: `Blog: ${postTitle?.value || ''}`,
reference: props.reference || `Blog: ${postTitle?.value || ''}`,
position: POSITION,
cta_type: props.cta,
})
Expand Down
61 changes: 61 additions & 0 deletions nuxt/components/content/NodeRedHelp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
// MDC: ::node-red-help{category="network" file="10-mqtt" node="mqtt out" name="MQTT Out"}
//
// Renders one core node's built-in help, mirrored from the Node-RED project. The fetch and
// the sanitising happen in server/api/node-red-help.get.ts, which runs during prerender,
// so the help is part of the static HTML rather than something the browser waits for.
//
// This is what makes the core-node pages ordinary content: the page carries its own
// frontmatter and prose and names the help it wants, instead of being assembled by a
// build-time generator.
const props = defineProps<{
/** Locale directory upstream, e.g. `network`. */
category: string
/** Locale file upstream, without the extension, e.g. `10-mqtt`. */
file: string
/** The `data-help-name` to select. May name a family, e.g. `link`. */
node: string
/** Display name, for the callout. Falls back to `node`. */
name?: string
}>()

const label = computed(() => props.name || props.node)

const { data, error } = await useFetch<{ html: string }>('/api/node-red-help', {
query: { category: props.category, file: props.file, node: props.node },
key: `node-red-help-${props.category}-${props.file}-${props.node}`,
})
</script>

<template>
<section class="node-red-help">
<h2 id="node-help">Node help</h2>

<div v-if="error" class="node-red-help__error">
<p>
This node's help could not be read from the Node-RED project.
See <a href="https://nodered.org/docs/user-guide/editor/workspace/palette">the Node-RED documentation</a>.
</p>
</div>

<template v-else>
<div class="ff-callout ff-callout--info">
<p>
This is the {{ label }} node's built-in help, mirrored from the Node-RED project.
It is the same text the editor shows in its Info sidebar.
</p>
</div>

<!-- Third-party HTML. Sanitised server-side against an allowlist before it gets here. -->
<div v-html="data?.html" />
</template>
</section>
</template>

<style scoped>
.node-red-help__error {
border-left: 3px solid #d1d5db;
padding-left: 1rem;
color: #4b5563;
}
</style>
4 changes: 2 additions & 2 deletions nuxt/components/integrations/InstallBox.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
const installVideoUrl = '/node-red/getting-started/library/images/installing-node-red-node.webm'
const installVideoUrl = '/docs/node-red/getting-started/library/images/installing-node-red-node.webm'
const installPngUrl = '/images/integrations/palette-manager-install.png'
</script>

Expand All @@ -21,7 +21,7 @@ const installPngUrl = '/images/integrations/palette-manager-install.png'
<p class="text-sm text-gray-600 mb-3">
Install in Node-RED via the
<a
href="/node-red/getting-started/library/#using-the-palette-manager"
href="/docs/node-red/getting-started/library/#using-the-palette-manager"
class="text-indigo-600 hover:text-indigo-800 font-semibold"
target="_blank"
rel="noopener noreferrer"
Expand Down
25 changes: 25 additions & 0 deletions nuxt/content-guides/flowfuse-nodes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: FlowFuse Nodes
metaTitle: "FlowFuse Nodes"
navTitle: FlowFuse Node-RED nodes
navOrder: 4
navGroup: FlowFuse User Manuals
navGroupOrder: 1
meta:
description: An overview of the Node-RED nodes provided by FlowFuse, explaining their purpose, usage, and benefits for building integrations and automations.
---

# FlowFuse Nodes

In Node-RED on the FlowFuse platform, you have access to additional Node-RED nodes that are provided by FlowFuse. These are documented in the below sections as a reference you can use when building integrations and automations.

## Nodes

The following documents provide details about the FlowFuse nodes:

- [FlowFuse AI Nodes](/docs/flowfuse-nodes/ai/): A set of Node-RED nodes for AI and machine learning, including ONNX model inference and LLM nodes for OpenAI, Anthropic, Google Gemini, and Ollama.
- [MCP Nodes](/docs/flowfuse-nodes/mcp/): A set of nodes that enable the creation of MCP (Model Context Protocol) servers in your Node-RED flows for AI-integration.
- [FlowFuse Tables](/docs/flowfuse-nodes/flowfuse-tables/): FlowFuse Tables provides managed databases for Node-RED users, offering built-in nodes to query, insert, and manage data easily within FlowFuse flows.
- [Edge Certified Nodes](/docs/flowfuse-nodes/edge/): Documentation for FlowFuse Edge Certified Nodes, including nodes for connecting FlowFuse to industrial protocols, PLCs, SCADA systems, and factory-floor equipment.
- [Hub Certified Nodes](/docs/flowfuse-nodes/hub/): Documentation for FlowFuse Hub Certified Nodes, including nodes for connecting FlowFuse to IT systems, databases, cloud services, APIs, and enterprise applications.
- [MQTT Nodes](/docs/flowfuse-nodes/mqtt/): MQTT In and Out nodes designed for FlowFuse users with automatic configuration.
20 changes: 20 additions & 0 deletions nuxt/content-guides/flowfuse-nodes/ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: FlowFuse AI Nodes
metaTitle: "FlowFuse AI Nodes"
navTitle: AI
navOrder: 1
meta:
description: A set of Node-RED nodes for AI and machine learning, including ONNX model inference and LLM nodes for OpenAI, Anthropic, Google Gemini, and Ollama.
---

# FlowFuse AI Nodes

The **FlowFuse AI** Nodes package adds AI capabilities to Node-RED. It includes nodes for running local ONNX models for image classification, object detection, depth estimation, and custom inference, as well as LLM nodes for sending text prompts to hosted and local large language models from OpenAI, Anthropic, Google Gemini, and Ollama.

## Nodes

- [LLM Nodes](/docs/flowfuse-nodes/ai/llm-nodes/): Send text prompts to hosted and local large language models from OpenAI, Anthropic, Google Gemini, and Ollama directly within Node-RED flows.
- [Depth Estimation](/docs/flowfuse-nodes/ai/depth-estimation/): The Depth Estimation node estimates the distance of objects in an image and creates a depth map using an ONNX model.
- [Image Classification](/docs/flowfuse-nodes/ai/image-classification/): Classify images using ONNX models directly in Node-RED. Supports pre-trained and custom models for tasks like labeling, content moderation, and object recognition.
- [Object Detection](/docs/flowfuse-nodes/ai/object-detection/): The Object Detection node identifies and locates objects within images using ONNX models such as YOLO and DETR, enabling real-time computer vision directly in Node-RED without external AI services.
- [ONXX](/docs/flowfuse-nodes/ai/onxx/): The ONNX node allows you to perform AI inference directly in Node-RED using ONNX models, supporting image, object, and numeric predictions without external AI services.
Loading
Loading