Contributing Software Tool Documentation
This guide covers two things: setting up the repo locally and opening a pull request, and embedding a software tool repository as a Git submodule with optional auto-generated API docs.
Local development workflow
Prerequisites
- Node.js 20 or later — nodejs.org
- Git with submodule support
1. Clone and initialise
git clone https://github.com/robotiq/robotiq.github.io
cd robotiq.github.io
git submodule update --init
2. Install dependencies
npm install
3. Sync external docs
The sync script pulls content from submodules into docs/. It runs
automatically on npm start and npm run build, but you can trigger
it manually at any time to verify a new sync job:
node scripts/sync-external-docs.js
4. Start the dev server
npm start
Opens http://localhost:3000 in your browser. The page hot-reloads
when you edit files under docs/, src/, or sidebars.js.
5. Open a pull request
- Create a branch:
git checkout -b my-feature
- Commit your changes:
git add .git commit -m "Add FT300 Python tool page"
- Push and open a pull request from your branch to
main:git push -u origin my-feature
The CI pipeline builds the site and reports broken links or MDX errors before the PR is merged.
How it works
Each software tool (SDK, driver, ROS package, …) lives in its own GitHub repository. Embedding it here works in two steps:
-
Submodule — a Git submodule under
external/acts as a pinned import of the tool's repository. It does not copy files; it records the exact commit of the source repo that this site should use. Runninggit submodule update --initchecks out those commits locally so their files become available. -
Sync script —
scripts/sync-external-docs.jsreads the checked-out submodule files and copies the ones you declare intodocs/, rewriting relative links so they resolve correctly on this site.
Repository layout
robotiq.github.io/
├── external/ ← pinned imports of source repos
│ └── tactile_sensors/ ← TSF-85 SDK repo
├── docs/
│ ├── intro.mdx ← overview page, includes the
│ │ auto-generated software tools tables
│ └── drivers/
│ ├── TSF-85/
│ │ ├── index.mdx ← product landing page (in git)
│ │ ├── SDK/
│ │ │ ├── C++/
│ │ │ │ ├── index.mdx ← wrapper page (in git)
│ │ │ │ └── _readme.md ← synced from submodule
│ │ │ └── Python/
│ │ │ ├── index.mdx
│ │ │ └── _readme.md
│ │ ├── ROS/
│ │ │ └── ROS2/index.mdx
│ │ └── Physics Engine/
│ │ └── Isaac Sim/index.mdx
│ ├── FT300/
│ │ ├── index.mdx
│ │ ├── SDK/C/ …
│ │ ├── SDK/Python/ …
│ │ └── ROS/ROS2/ …
│ └── 2F hande/
│ ├── index.mdx
│ ├── SDK/C++/ …
│ ├── SDK/Python/ …
│ ├── ROS/ROS2/ …
│ ├── Physics Engine/Isaac Sim/ …
│ ├── Physics Engine/PyBullet/ …
│ └── GraspGen/ … ← "Other" category, no wrapper folder needed
├── draft/ ← content with no nav link yet (never
│ built into a page — outside docs/)
├── scripts/
│ ├── sync-external-docs.js
│ └── generate-tools-table.js
└── sidebars.js
Files prefixed with _ are Docusaurus partials — imported by a
wrapper .mdx but not standalone pages themselves.
The SDK/, ROS/, and Physics Engine/ folders are just organizational
containers — generate-tools-table.js doesn't care about folder names, only
about which index.mdx files carry a Category badge (see
step 3 below). GraspGen/ proves the point: it
sits directly under the product folder with no wrapper category folder, and
still lands in the "Other" table because of its badge, not its path.
Excluding content from a synced README
A tool repo's README often carries things that only make sense on GitHub — e.g. a "full documentation at robotiq.github.io/..." blurb, which would be redundant (or circular) once that same README is copied onto this site. Wrap that content in a pair of HTML comment markers:
<!-- docs-site:exclude -->
📖 Full documentation: https://robotiq.github.io/...
<!-- /docs-site:exclude -->
GitHub renders the text between the markers normally on the tool
repo's own README (HTML comments are invisible), but
sync-external-docs.js strips everything between
<!-- docs-site:exclude --> and <!-- /docs-site:exclude -->
before copying content into docs/, so it never reaches this site.
Step-by-step: adding a new software tool
The steps below add a Python tool for the 2F hande gripper family hosted
at https://github.com/robotiq/2f85-python-driver.
1. Add the submodule
This registers the tool's repository as a pinned import under
external/. Every contributor who clones this site will get the
same version of the source repo.
git submodule add \
https://github.com/robotiq/2f85-python-driver \
external/2f85_python
git submodule update --init
2. Declare the sync job
Open scripts/sync-external-docs.js
and add an entry to the JOBS array. Each job copies one file or
folder from the submodule into docs/:
{
submodule: '2f85_python',
repoUrl: 'https://github.com/robotiq/2f85-python-driver',
branch: 'main',
from: 'README.md',
to: 'drivers/2F hande/SDK/Python/_readme.md',
},
For a repo that contains multiple tools, add one job per tool:
// C++ tool
{
submodule: '2f85_drivers',
repoUrl: '...',
branch: 'main',
from: 'sdk_cpp/README.md',
to: 'drivers/2F hande/SDK/C++/_readme.md',
},
// Python tool
{
submodule: '2f85_drivers',
repoUrl: '...',
branch: 'main',
from: 'python/README.md',
to: 'drivers/2F hande/SDK/Python/_readme.md',
},
Test locally:
node scripts/sync-external-docs.js
3. Create the wrapper page
Create docs/drivers/2F hande/SDK/Python/index.mdx. This file
is tracked in git and controls the page title, sidebar label, and
any introductory text above the synced README:
---
title: Python
sidebar_label: Python
sidebar_position: 2
---


Brief description and link to the source repository.
## Intro
import Readme from './_readme.md';
<Readme />
The ## Intro heading and the import are optional — omit them if
there is no synced README or if the content should start directly.
Both badges are required, not decorative — generate-tools-table.js
walks the folder tree looking for the Category badge to decide which
table a page belongs to, and reads the Supported_by badge for the cell
shown in that table (see
Auto-generated software tools tables
below). Valid Category values: SDK, Physics_Engine, Other, ROS1,
ROS2 (ROS pages use a different badge block — see below). Use
Supported_by-Robotiq-blue for tools developed and maintained by Robotiq,
or Supported_by-Third_party-lightgrey for community-maintained tools.
ROS pages are different. Instead of one Supported_by badge, a ROS
tool page carries one Distro badge per supported ROS distribution, each
immediately followed by its own Supported_by badge — that's what drives
the ROS1/ROS2 distro-compatibility tables:
---
title: ROS
sidebar_label: ROS
sidebar_position: 3
---
<br />


For a tool supporting several distros, repeat the Distro/Supported_by
pair for each one (see docs/drivers/2F hande/ROS/ROS2/index.mdx for a
three-distro example). Use Category-ROS1 instead of Category-ROS2 for
a ROS 1 package.
No README or no software tool yet? Skip steps 1 and 2 entirely
and write the index.mdx manually. This is the right approach when
a tool is planned but not yet available, or when the source repo has
no suitable README to pull in. Write the page content directly with
no import:
---
title: Python
sidebar_label: Python
sidebar_position: 2
---


Coming soon. In the meantime, see
[org/repo](https://github.com/robotiq/...) on GitHub.
The page still appears in the sidebar and can be updated to import a synced README later, once the tool or its documentation exists.
4. Create the product landing page
If the product folder has no index.mdx yet, create
docs/drivers/2F hande/index.mdx:
---
title: 2F / Hand-E
sidebar_label: 2F / Hand-E
sidebar_position: 0
---
Short description of the product family.
## Available drivers
### SDK
{/* AUTO-GENERATED-PRODUCT-SDK-TABLE:START */}
{/* AUTO-GENERATED-PRODUCT-SDK-TABLE:END */}
### ROS
#### ROS2
{/* AUTO-GENERATED-PRODUCT-ROS2-TABLE:START */}
{/* AUTO-GENERATED-PRODUCT-ROS2-TABLE:END */}
#### ROS1
{/* AUTO-GENERATED-PRODUCT-ROS1-TABLE:START */}
{/* AUTO-GENERATED-PRODUCT-ROS1-TABLE:END */}
### Physics Engine
{/* AUTO-GENERATED-PRODUCT-PHYSICS_ENGINE-TABLE:START */}
{/* AUTO-GENERATED-PRODUCT-PHYSICS_ENGINE-TABLE:END */}
### Other
{/* AUTO-GENERATED-PRODUCT-OTHER-TABLE:START */}
{/* AUTO-GENERATED-PRODUCT-OTHER-TABLE:END */}
Don't write any of the five tables by hand — leave each marker pair next
to each other and scripts/generate-tools-table.js fills in a
single-row version of the matching category table (see
below) on the next build. The
title here is what shows up as the row label there. A category with no
tool yet renders a short "not documented yet" placeholder instead of an
empty table, so it's safe to keep all five headings even before every
category has content.
5. Register in sidebars.js
Add a category entry with a link pointing at the product landing
page, nesting the tool pages under an SDK / ROS / Physics Engine / Other
sub-category matching where they live on disk:
{
type: 'category',
label: '2F / Hand-E',
link: { type: 'doc', id: 'drivers/2F hande/index' },
items: [
{
type: 'category',
label: 'SDK',
items: [
'drivers/2F hande/SDK/C++/index',
'drivers/2F hande/SDK/Python/index',
],
},
{
type: 'category',
label: 'ROS',
items: [
'drivers/2F hande/ROS/ROS2/index',
],
},
{
type: 'category',
label: 'Physics Engine',
items: [
'drivers/2F hande/Physics Engine/Isaac Sim/index',
'drivers/2F hande/Physics Engine/PyBullet/index',
],
},
{
type: 'category',
label: 'Other',
items: [
'drivers/2F hande/GraspGen/index',
],
},
],
},
Only include the sub-categories that actually have pages — e.g. FT300-S has no Physics Engine or Other tools yet, so its sidebar entry skips those two groups entirely.
Auto-generated software tools tables
docs/intro.mdx has five "Software tools" tables, one per category —
SDK, ROS2, ROS1, Physics Engine, Other — each listing
every product that has a tool in that category, with each cell showing
that tool's support badge linked to its page. Each product's own landing
page (docs/drivers/<Product>/index.mdx) gets the same five tables,
single-row versions scoped to just that product. You never edit any of
them by hand — all ten (five global + five per-product) are regenerated by
scripts/generate-tools-table.js,
which runs automatically on npm start / npm run build (same as
sync-external-docs.js).
The script walks each docs/drivers/<Product>/ folder tree looking for
tool leaf pages — an index.mdx carrying a Category badge — regardless
of how deeply it's nested (SDK/, ROS/, Physics Engine/, or no
container folder at all, as with GraspGen/). For each leaf page found:
- Row — the product's own
titlefrontmatter (fromdocs/drivers/<Product>/index.mdx). - SDK / Physics Engine / Other pages — column = the tool's
titlefrontmatter; cell = itsSupported_by-<Label>-<color>badge (see step 3 above), shown compactly (no "Supported by" prefix — only the source page keeps the full badge). - ROS1 / ROS2 pages — columns = the distros named in that page's
Distro-<Name>badges; cell = theSupported_bybadge immediately following each one.
This means adding a new product or tool page, with a title and the right badges, is enough to make it appear in the matching tables on the next build — no manual edit needed. A category with no products yet renders a short placeholder line instead of an empty table.
A few things are still hand-maintained in the script itself, by design — they're presentation choices, not derivable from the file tree:
COLUMN_ORDER/DISTRO_ORDER— preferred left-to-right order for each category's columns / each ROS table's distro columns. Anything not listed still gets its own column, just sorted alphabetically after the known ones.COLUMN_DESCRIPTIONS/DISTRO_DESCRIPTIONS— the one-line legend shown under each table. A column or distro without an entry here still appears, just with no legend line, until one is added.
To test a change locally:
node scripts/generate-tools-table.js
It rewrites the content between each AUTO-GENERATED-<CATEGORY>-TABLE
marker pair (AUTO-GENERATED-SDK-TABLE, AUTO-GENERATED-ROS2-TABLE,
AUTO-GENERATED-ROS1-TABLE, AUTO-GENERATED-PHYSICS_ENGINE-TABLE,
AUTO-GENERATED-OTHER-TABLE) in docs/intro.mdx, and the matching
AUTO-GENERATED-PRODUCT-<CATEGORY>-TABLE marker pairs in each product's
own index.mdx — leaving the rest of each page untouched. The two sets
of markers use different link styles on purpose: docs/intro.mdx needs
root-relative links (drivers/<Product>/<Tool>), while a product page
needs page-relative links (<Tool>), since it already lives inside
docs/drivers/<Product>/. Reusing the root-relative form on a product
page silently doubles the path (e.g.
drivers/2F hande/drivers/2F hande/C++) — the script keeps these as
two separate href fields (href vs localHref) to avoid that.
Auto-generated API reference from docstrings
Software tool repos can expose a machine-generated API reference page — no manual writing required. The sync script runs the generation tool against the submodule during build.
Python — pydoc-markdown
In the tool repository, add docstrings following Google or NumPy
style, and commit a pydoc-markdown.yml config at the repo root:
# pydoc-markdown.yml (in the tool repo)
loaders:
- type: python
search_path: [.]
packages: [your_package_name]
renderer:
type: markdown
filename: docs/api.md
render_toc: true
In this site, install the tool once:
npm install --save-dev pydoc-markdown
Then add a generation step to scripts/sync-external-docs.js
before the JOBS loop:
const { execSync } = require('child_process');
execSync('pydoc-markdown', {
cwd: path.join(ROOT, 'external', '2f85_python'),
stdio: 'inherit',
});
This writes external/2f85_python/docs/api.md. Copy it with a
normal job:
{
submodule: '2f85_python',
repoUrl: 'https://github.com/robotiq/2f85-python-driver',
branch: 'main',
from: 'docs/api.md',
to: 'drivers/2F hande/SDK/Python/api.md',
},
Add 'drivers/2F hande/SDK/Python/api' to the sidebar items.
C++ — Doxygen + Doxybook2
In the tool repository, add Doxygen-style comments to headers
and commit a Doxyfile configured to output XML:
# Doxyfile (relevant lines)
GENERATE_XML = YES
XML_OUTPUT = doxygen-xml
GENERATE_HTML = NO
INPUT = .
RECURSIVE = YES
In this site, install the tools once:
# Doxygen: https://www.doxygen.nl/download.html (add to PATH)
npm install --save-dev @mikaelkristiansson87/doxybook2
Add generation steps to scripts/sync-external-docs.js:
const submoduleDir = path.join(ROOT, 'external', '2f85_cpp');
const xmlOut = path.join(submoduleDir, 'doxygen-xml');
const mdOut = path.join(submoduleDir, 'docs/api');
execSync('doxygen', { cwd: submoduleDir, stdio: 'inherit' });
execSync(
`doxybook2 --input ${xmlOut} --output ${mdOut}` +
` --config .doxybook/config.json`,
{ cwd: submoduleDir, stdio: 'inherit' }
);
Then copy the output with a folder job:
{
submodule: '2f85_cpp',
repoUrl: 'https://github.com/robotiq/2f85-cpp-driver',
branch: 'main',
from: 'docs/api',
to: 'drivers/2F hande/SDK/C++/api',
},
Add 'drivers/2F hande/SDK/C++/api' (or its sub-pages) to the
sidebar.
Quick reference
| What | Where |
|---|---|
| Submodules (pinned repo imports) | external/<submodule>/ |
| Sync + generation config | scripts/sync-external-docs.js → JOBS |
| Exclude README content from the synced page | <!-- docs-site:exclude --> ... <!-- /docs-site:exclude --> in the tool repo's README |
| Synced partials | docs/drivers/<Product>/<Category>/<Tool>/_readme.md |
| Wrapper pages | docs/drivers/<Product>/<Category>/<Tool>/index.mdx |
| Category badge (SDK / ROS1 / ROS2 / Physics_Engine / Other) |  at the top of each wrapper page |
| Support badge (non-ROS tables) | , right after the Category badge |
| Distro + support badges (ROS tables) | one Distro-<Name> badge per supported distro, each immediately followed by its own Supported_by-<Label>-<color> badge |
| Product landing pages | docs/drivers/<Product>/index.mdx |
| Generated API pages | docs/drivers/<Product>/<Category>/<Tool>/api.md |
| Draft / not-yet-linked content (never built into a page) | draft/ at the repo root, outside docs/ |
| Auto-generated tools tables script | scripts/generate-tools-table.js |
| Auto-generated overview tables output | docs/intro.mdx, between each AUTO-GENERATED-<CATEGORY>-TABLE marker pair |
| Auto-generated per-product tables output | docs/drivers/<Product>/index.mdx, between each AUTO-GENERATED-PRODUCT-<CATEGORY>-TABLE marker pair |
| Sidebar config | sidebars.js |
| Doc ID format | drivers/TSF-85/SDK/C++/index |