Skip to content

docs: source guides from a content-collection instead of copying them in - #5752

Open
dimitrieh wants to merge 34 commits into
mainfrom
docs/content-collections-instead-of-sync
Open

dimitrieh wants to merge 34 commits into
mainfrom
docs/content-collections-instead-of-sync

Conversation

@dimitrieh

@dimitrieh dimitrieh commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

A reviewer asked on #5495 (comment) why the docs pipeline uses a custom sync script instead of @nuxt/content's own collection primitives. This checks that against the installed version (3.15.2) and applies it where it genuinely helps.

The guides overlay (nuxt/content-guides/) is now a second source on the docs collection, read directly from disk instead of being copied into nuxt/content/docs first. The frontmatter it used to inject during that copy now happens in the existing content:file:beforeParse hook.

Source Change
Guides (nuxt/content-guides/) Native second collection source, no copy step
FlowFuse/flowfuse docs (docs-sync.mjs) Unchanged
Core nodes (core-nodes-sync.mjs) Unchanged

The other two sources stay as they are:

  • docs-sync.mjs needs the full git history of each file it clones (for the "updated" date). @nuxt/content's own git source only does a shallow clone, which would date every page the same.
  • core-nodes-sync.mjs generates pages from an HTTP fetch, not files on disk, so there is nothing to point a collection source at.

Also renamed the guides' README.md section-index files to index.md, since @nuxt/content only recognizes the latter.

What this does not solve: two of the three sync scripts are unchanged. scripts/sync_docs.mjs (used outside a Nuxt build) no longer copies guide pages into nuxt/content/docs, only their non-markdown assets.

Related Issue(s)

#5495 (comment)

Checklist

  • I have read the contribution guidelines
  • I have considered the performance impact of these changes
  • Suitable unit/system level tests have been added and they pass
  • Documentation has been updated
  • For blog PRs, an Art Request has been created (instructions)

@dimitrieh

Copy link
Copy Markdown
Contributor Author

has a lot of changes as this stacks on top of other PRs. If those get merged, this one can follow and it will have less changes. Until then this stays a draft PR

@netlify

netlify Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for flowfuse-website ready!

Name Link
🔨 Latest commit 5967a80
🔍 Latest deploy log https://app.netlify.com/projects/flowfuse-website/deploys/6ac3d30a42936a0008f3f40a
😎 Deploy Preview https://deploy-preview-5752--flowfuse-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 37 (🟢 up 7 from production)
Accessibility: 95 (no change from production)
Best Practices: 92 (no change from production)
SEO: 92 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@dimitrieh
dimitrieh changed the base branch from main to docs/node-red-library-into-docs September 8, 2026 08:47
@dimitrieh
dimitrieh force-pushed the docs/node-red-library-into-docs branch from 02d2826 to 35d1a80 Compare September 8, 2026 09:04
@dimitrieh
dimitrieh force-pushed the docs/content-collections-instead-of-sync branch 10 times, most recently from b17b8c5 to 68a3cb7 Compare September 8, 2026 11:41
Base automatically changed from docs/node-red-library-into-docs to main September 8, 2026 15:22
@dimitrieh
dimitrieh marked this pull request as ready for review September 8, 2026 15:23
@dimitrieh

Copy link
Copy Markdown
Contributor Author

@ZJvandeWeg can you have a look here? This essentially fixes your request at #5495 (comment)

@dimitrieh
dimitrieh force-pushed the docs/content-collections-instead-of-sync branch from 51dbb2e to 2063535 Compare September 15, 2026 11:38
@dimitrieh
dimitrieh force-pushed the docs/content-collections-instead-of-sync branch from 2063535 to fbd5d63 Compare September 25, 2026 19:56
@dimitrieh

Copy link
Copy Markdown
Contributor Author

FYI: the failing build here isn't caused by this PR. Tracked in https://github.com/FlowFuse/engineering/issues/460. It's resolved now, re-running the checks.

@dimitrieh
dimitrieh force-pushed the docs/content-collections-instead-of-sync branch from fbd5d63 to ddb9c47 Compare September 25, 2026 20:06
@dimitrieh

Copy link
Copy Markdown
Contributor Author

The group name rendered at caption size, the same weight as the pages under
it, so the grouping had to be read rather than seen. It now takes body size
and the links sit indented from it.

Shared component, so this applies to the handbook and the guides too.
…op the lone crumb

The sidebar started 1.5rem above the page body, so nothing lined up across
the top. Its items sit indented further from their group header.

A breadcrumb trail of one item names the page you are already on. It now
renders only when there is somewhere to go back to.
FfExpertAsk carries the markup of the old /ai/ page's chat interface and modal and
runs the same src/js/ai-expert-modal.js. The script now starts through a named init
that runs whether or not DOMContentLoaded has fired, can run again after client-side
navigation, and replaces its page-wide click listeners instead of stacking them.

Answers come from the Expert service for the hosted site; in a local preview the
request fails, as the script itself notes.
…home box

The Expert conversation modal now lives once in the layout for all docs pages. The
home page box carries the FlowFuse Expert branding above the text box, an Ask Docs
button and support questions instead of workflow ideas. Elsewhere, and on the home
page once that box scrolls away, an Ask FlowFuse Expert button at the top of the
right-hand column opens the same conversation.

ai-expert-modal.js: the entry button is handled by delegation so a box mounted after
start-up works, the morph from the box only runs when the box opened the modal, a page
can set the greeting, and window.ffExpertOpen() opens it from any button.
Below lg the docs sidebar and right-hand column are folded away, so docs pages get a
search button and the FlowFuse Expert button in the header, styled like the Expert
button in the FlowFuse app. To keep Book a demo on one line, the Expert button shows
the FlowFuse mark alone below 600px and on tablets, and the square logo holds until
520px on docs pages. Measured from 360 to 1000px: no overflow beyond what the header
already allows.
…r back on the button

The dialog header now reads like a plain search bar: the input sits flat above the
header's divider with no box or focus ring, the search icon is grey, and Cancel is an X
(still named Close for screen readers). The input's own clear button is hidden so the X
is the only one. The dialog carries an ff-search-dialog class, so the blog, changelog and
handbook phone dialogs are unchanged. The sidebar button lost its border to
algolia-theme.css, whose #algolia-search rule outranked the component's classes; the
component's rule now includes the id.
…ents

/docs now shows its single Docs crumb, so every docs page opens the same way. The
right-hand Expert button moves below the table of contents and reads Ask Expert with the
FlowFuse mark, like the compact header button, instead of the FlowFuse wordmark. On wide
screens the column is capped to the window and a long table of contents scrolls inside
itself, so the button under it stays in view.
The question box and the conversation modal now render FlowFuse Expert from one
DocsExpertBrand component at the modal's size; the box's was larger. When the box morphs
into the modal, its brand gets its own view transition and moves up into the modal
header while the rest of the box morphs into the input area as before, and back again on
close. Pages without a branded box (the Eleventy /ai/ page) are unaffected.
FlowFuse Expert now has one entry point on docs pages: a round Ask Docs button in the
bottom-right corner at every screen width, which steps aside while a page's own question
box is on screen. The Expert buttons in the right-hand column and in the phone and tablet
header are gone, so the header keeps only the docs search there. DocsExpertButton is
removed with them.
The right-hand column started 22px lower than the other two because the table of contents
pads its own container. It now starts where the sidebar's search field does and centres
its first line the same way, so the three columns share one top line. The column also no
longer reserves room for the Expert button that moved to Ask Docs.
A 1px gray-200 line separates the sticky header from the page on every page, Nuxt and
Eleventy alike, since both link this stylesheet. It is a shadow rather than a border, so
the header height that --ff-header-height tracks does not change.
The floating button is now white with the FlowFuse mark and a slowly turning brand-red to
indigo border, as the Expert button in the FlowFuse app is. Its box matches the header's
Book a demo button (40px tall, 16px text, the same padding), keeping the pill shape.
A docs page whose frontmatter sets landing: true gets the full page width: no sidebar, no
table of contents, no previous and next cards, and its body is not wrapped in .prose,
because every block on it styles itself. The breadcrumb stays. Three content components
build such a page from markdown, next to Nuxt UI's own cards, tabs and steps:

- docs-hero: heading and line, the docs search (the page's Cmd+K search, since there is
  no sidebar), a row of buttons from a markdown list, and an aside slot beside them.
- docs-section: eyebrow, heading and one line, then the section's cards or tiles.
- docs-tiles: a markdown list of links as a grid of tiles, each clickable across its
  whole area; the links stay plain markdown so the flowfuse link check still sees them.

AlgoliaSearch gains a taller lg size for the hero. The copy-command block no longer
picks up the site-wide code box inside itself on pages outside .nohero.
docs-choices and docs-choice list the ways into FlowFuse, one open at a time: each opens
to a line or two on what the option is, then its next step as a button and a guide as a
link. They route to the pages that already walk through setup (the Device Agent install,
the AI agent picker) rather than repeating them. Each choice is a native details element
sharing one name, so the browser keeps a single one open with no script.

docs-columns sets two blocks side by side, two thirds and one third, stacked on phones.
A card on a landing page that opens with a paragraph holding only a link takes that link
as its title, and the link covers the whole card with a hover state. This replaces Nuxt
UI's to attribute, whose URL the flowfuse docs link check misreads (it autolinks the text
and keeps the closing quote and brace), and which left internal card links unchecked.
A #title slot cannot stand in for it: inside these wrapper components MDC attaches a
nested card's #title to the wrapper and swallows the card's closing fence.
FfExpertAsk lays out its box and its prompts as a two-row grid, and the box fills the
height its row gives it. DocsColumns hands its first block both rows through subgrid, so
that block's box shares the top row with the second column and the two stretch to one
height, with the prompts below. The row gaps match; a subgrid with a different gap spreads
the difference into its rows and left the box 4px taller.
The trail on /docs would be the single word Docs, so the home page leaves it out; every
other docs page, landing or not, keeps its breadcrumb.
Autocomplete loads from a CDN after the page renders, so a detached search showed nothing
but its Cmd+K hint until then; on the docs home, where the field leads the hero, that read
as a missing search. A stand-in button of the same size now renders with the page and
gives way once autocomplete mounts, with no layout shift. A click or Cmd+K before then
opens the dialog as soon as it can; the shortcut listeners are set up right away.
FfExpertAsk drops its six prompt pills, so it is the question box alone and simply fills
the height it is given; DocsColumns no longer needs a subgrid to line it up with the card
beside it, plain grid stretching does. A card beside the Expert box now gets the same
compact padding and type as cards in a card-group, and cards that follow the two-column
block keep their distance. docs-choice states the rule for its links: one main call to
action and at most one secondary action.
Docs pages show Docs beside the logo, linking to the docs home while the logo still goes
to the site. On wide screens it pairs with the wordmark as one lockup: capitals as tall as
the wordmark's (28px) on the same baseline, lighter and grey so the brand leads. Below
1024px it pairs with the square mark. On tablets the row had no other room (the menus
cannot fold into More), so on docs pages Free Trial gives way there and Book a demo keeps
its margin; it stays from 1024px up and on every other page.
The gap between the sidebar search and the first group heading was Nuxt UI's label
padding, meant to separate a group from the one above, plus the nav's own top padding.
The first heading has no group above, so both go, and the space under the search is set
so the heading's capitals start on the same line as the page title's.
Docs beside the wordmark is now Heebo 700, whose stems (4px) match the wordmark's (3.9px),
at 29px, which splits Heebo's proportions against the wordmark's (capitals 0.6px taller,
lowercase 0.4px shorter), on the same baseline. On docs pages the wordmark now waits for
xl: between 1024 and 1280px it brought the menus within 16px of Docs, while the square mark
keeps at least 45px there, 70px or more from 1100px.
Between 1280 and 1536px the docs page and the header stop at the same max width but were
padded differently (the page on the left only), so the sidebar search sat 12px right of
the logo. The page now mirrors the header's padding from lg up. Group headings also drop
Nuxt UI's label inset, which put them 10px right of the search field: they are headings
over indented links, not link text.
Below xl the docs header no longer swaps in the separate square logo, which is drawn larger
and in a lighter red: it shows the wordmark itself cropped to its mark, so the logo keeps
one shape, colour and size and only its lettering comes and goes. Docs stays 29px at every
width, now weight 600: its stems sit just under the wordmark's, 700 read too heavy. On
phones below 390px the row cannot hold the mark, Docs, search, Book a demo and the menu,
so there Book a demo gives way; from 390px it fits.
Without the prompt pills the box sat in a flex row that sized it to its content, so it
shrank to a narrow strip and clipped its footnote. It now grows to the column's width as
well as its height.
/docs spans more than the FlowFuse/flowfuse docs: the guides and the Node-RED library come
from this repo. Its home therefore lives here, as nuxt/content-guides/README.md, which
guides-sync overlays as the docs index. docs-sync stops copying the README at the root of
the flowfuse docs tree, which stays that repository's own readme for its docs folder;
syncing it neither writes nor removes the home page. Tests cover both the dev watcher's
single-file sync and a full sync.

The page is the landing page built from the docs-* components, with links to other pages
of this site written relative so they resolve on any host, deploy previews included.
In the docs home's list of ways in, the second link of every option now leads to the page
that explains the option: about FlowFuse Cloud, the Device Agent, self-hosting, FlowFuse MCP
and FlowFuse applications. The first stays the step that starts it. The AI agent option,
which had only its first step, gains its about link too.
Opening another option no longer moves the home page heading or the sections below. The
hero aligns its columns to the top instead of centring the heading on the list, and the
list keeps the height of its tallest open option, measured once mounted and again when its
width changes (each option opened in turn with transitions off, in one synchronous pass).
Opening and closing an option now animate its height with a short fade, using
::details-content and interpolate-size; a browser without them opens the option
immediately, and reduced motion turns the animation off.
FlowFuse Expert on the docs (the Ask Docs button, the question box on the
docs home and the conversation modal) ran on src/js/ai-expert-modal.js,
which the Eleventy retirement removed from main, and the endpoint that
script calls no longer answers without sign-in. This PR keeps the docs
home, the header and the search dialog, and Ask Docs follows separately.

Removed: DocsAskFab, DocsExpertHost, DocsExpertBrand, FfExpertAsk,
useDocsExpert, and DocsColumns, which only held the question box. The
layout goes back to main's. The Help section on the docs home keeps its
wording without the Expert sentence, and the support card joins the
other three cards in a two-column group.
At the wordmark's own size (29px, weight 600) the label read as a second
logo on every docs page. It is now 13px, grey, set high beside the logo
with its capital level with the wordmark's, like a superscript: it still
says this is the docs, without competing with the brand.

The smaller label frees room on phones, so Book a demo now gives way only
below 360px instead of below 390px (at 360px the row has 20px to spare).
The mark-only logo below xl stays: with the whole wordmark the menus
would come within about 28px of "Docs" at 1100px.
A reviewer on #5495 asked why docs-sync/guides-sync materialize files into
nuxt/content/docs by hand instead of using @nuxt/content's own collection
primitives. Checked against the installed @nuxt/content (3.15.2, satisfies the
^3.13.0 this repo pins): a collection's `source` can be an array, each entry
with its own `cwd`, and each is globbed and parsed independently at build
time. That is enough for the guides authored in nuxt/content-guides/, since
they are already MDC and already local: the `docs` collection now reads them
straight from that directory as a second source (prefixed onto `docs/` so
they land at the same paths), and the `content:file:beforeParse` hook that
already existed for the blog collection now also stamps guide pages with
their editUrl/updated frontmatter, in place of a copy step that used to write
it into a duplicated file.

guides-sync.mjs is what is left after that: copying the guides' non-markdown
assets to public/docs (a content-collection source cannot do that), and a
proactive collision check against FlowFuse/flowfuse's pages (a real collision
now fails anyway, since the `docs` collection's `id` is a primary key, but as
a SQL error rather than a message naming the file). The section-index
README.md convention the guides borrowed from GitHub is renamed to index.md
throughout, since @nuxt/content only special-cases the latter and this tree
is ours to rename.

docs-sync.mjs (the FlowFuse/flowfuse clone) is unchanged: its full-history
clone can't become @nuxt/content's own git source, which only supports a
shallow clone and would flatten every page's `updated` date to the sync
commit - the reason the clone is not shallow already, predating this change.
core-nodes-sync.mjs is unchanged for the same reason it was never a
candidate: its input is an HTTP fetch, not files on disk, so it still writes
into nuxt/content/docs like a build artifact, because it is one.

Verified the source/prefix mechanics against @nuxt/content's own module code
(resolveSource, defineLocalSource, the collection build loop) and with a
standalone experiment reproducing the exact id/path computation for both
sources - not just theoretically, but with real @nuxt/content code executed
against toy directories, confirming the guides source resolves to the same
/docs/... paths the old copy step produced. nuxt/lib/guides-sync.test.mjs is
rewritten for the slimmed-down API.
Dropping the copy step took the guides out of the only path remark-docs-links
recognised. It keys off a `/docs/` segment in the file's path on disk, which held while
guides-sync materialized them into nuxt/content/docs, and stops holding now they are
read straight out of nuxt/content-guides/. The plugin returned early on every guide
page, so their relative image URLs reached the browser unresolved and the browser
resolved them against the page's own URL: ./images/x.png on
/docs/node-red/database/influxdb/ asked for
/docs/node-red/database/influxdb/images/x.png and 404'd. The asset was copied to the
right place throughout; only the URL was wrong. Three hundred-odd references across
33 pages, and nothing failed: the link checker sees the built HTML, where these are
valid relative URLs, and the images themselves are not fetched.

The path mapping moves to nuxt/lib so node --test can reach it, and it now understands
both of the collection's sources. The test asserts over the real tree that every guide
using a relative asset URL is one this can resolve, so the two cannot drift apart
again without failing.
With the guides read as a collection source rather than copied in, a
section index has to be named index.md, so the docs home added in #5866
follows the other guide indexes.
@dimitrieh
dimitrieh force-pushed the docs/content-collections-instead-of-sync branch from ddb9c47 to 5967a80 Compare October 5, 2026 16:40

This branch was successfully deployed

1 active (outdated) deployment
Preview — 51dbb2e4 Deployed Sep 8, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant