Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5d3b4fb
docs(contributing): correct two worked examples
XueBin-weston Sep 8, 2026
6593bc1
docs(wrl63): serve the manuals from the file store
XueBin-weston Sep 8, 2026
e0dc453
docs(wr65): serve the manuals from the file store
XueBin-weston Sep 8, 2026
98baa48
docs(ranger-mini-v3): serve the manual and CAD from the file store
XueBin-weston Sep 9, 2026
3b114a0
docs(piper): serve the manuals, CAN protocol and CAD from the store
XueBin-weston Sep 9, 2026
533af98
docs(kinova-gen3-lite): link to Kinova rather than rehost
XueBin-weston Sep 9, 2026
a9f1648
docs(ranger-mini-v2): drop the dead links from an unsupported model
XueBin-weston Sep 9, 2026
3942a4e
docs(unitree): serve our own documents from the file store
XueBin-weston Sep 10, 2026
3a31ad2
docs(unitree): link every model to Unitree's user manual
XueBin-weston Sep 10, 2026
8bc8492
docs(unitree): link every model to Unitree's user manual
XueBin-weston Sep 10, 2026
348049a
docs(unitree): unlist the As2 and H2 pages while they are incomplete
XueBin-weston Sep 10, 2026
c1f24a9
docs(nero): unlist the NERO page while it is incomplete
XueBin-weston Sep 11, 2026
531163f
docs(robot): drop the product cards for the unlisted pages
XueBin-weston Sep 11, 2026
bdb0b32
docs(z1): point at Unitree's current Z1 developer documentation
XueBin-weston Sep 11, 2026
c46254c
docs(todo): track the dead links inside the training decks
XueBin-weston Sep 11, 2026
110bd3d
docs(todo): anchor the manifold_pocket dead-link item to its table
XueBin-weston Sep 11, 2026
39add8d
Merge remote-tracking branch 'origin/main' into docs/dead-sharepoint-…
XueBin-weston Sep 24, 2026
e76ab3f
docs(todo): widen the dead-link item to every published document
XueBin-weston Sep 24, 2026
eae2690
docs(conventions): keep rationale out of docusaurus.config.ts
XueBin-weston Oct 5, 2026
5afb33f
fix(scripts): use guides instead of tutorial as a section
XueBin-weston Oct 7, 2026
155d4f5
docs(contributing): add zxx to the language rules
XueBin-weston Oct 7, 2026
a9c2de2
docs(conventions): shorten the config comment rule
XueBin-weston Oct 7, 2026
421f8cd
docs(piper): remove the windows app from the bring-up step
XueBin-weston Oct 7, 2026
f6573f9
docs(todo): mark finished link work and list lost documents
XueBin-weston Oct 7, 2026
8a37e9a
fix(manipulators): match wr65 and wrl63 specs to the published manuals
XueBin-weston Oct 8, 2026
a10f823
docs: update references to documents and pages this pr changed
XueBin-weston Oct 8, 2026
abc8be5
docs(contributing): describe publishing with downloads and add unlist…
XueBin-weston Oct 8, 2026
cc9f785
docs: replace leftover tutorial paths with guides
XueBin-weston Oct 8, 2026
7d3ad91
docs(file-hosting): update status now the store is loaded
XueBin-weston Oct 8, 2026
30f86e5
docs(conventions): move rationale out of sidebar and tag comments
XueBin-weston Oct 8, 2026
57ad362
fix(plugin): read zxx and subjects in the staged file preview
XueBin-weston Oct 8, 2026
9e0ff83
docs(todo): record the 2026-10-07 audit backlog
XueBin-weston Oct 8, 2026
cf96220
docs(todo): group the open pocket 1 items in one section
XueBin-weston Oct 8, 2026
55e27f9
docs(contributing): complete the unlisting steps
XueBin-weston Oct 8, 2026
79d6e6f
docs: describe publishing through the store and downloads
XueBin-weston Oct 8, 2026
8908a97
docs(design): correct records found in the cross-check
XueBin-weston Oct 8, 2026
0a31a70
docs(todo): fix stale references and merge duplicate entries
XueBin-weston Oct 8, 2026
88236ba
fix(manipulators): restore Realman's joint limits and weights for WR6…
timothy-weston Oct 15, 2026
e170ac0
docs(contributing): say what check:downloads does when the index is u…
timothy-weston Oct 15, 2026
d7fb1e6
docs(upload-file): move static/_upload aside instead of deleting it
timothy-weston Oct 15, 2026
dca9c2c
Merge pull request #48 from westonrobot/docs/pr44-review-fixes
timothy-weston Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 33 additions & 22 deletions .claude/skills/upload-file/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ this file** — a stale vocabulary here is worse than none, because it produces
confident wrong answers.

```bash
python3 -c "import sys;sys.path.insert(0,'scripts');import wrfiles as w;print(w.KINDS);print(w.LANGS);print(sorted(w.CONTENT_TYPES))"
python3 -c "import sys;sys.path.insert(0,'scripts');import wrfiles as w;print(w.SECTIONS);print(w.KINDS);print(w.LANGS);print(sorted(w.CONTENT_TYPES))"
```

## What you must establish
Expand All @@ -35,7 +35,7 @@ Ask for whatever is missing. Ask in one message, not four.
| **subject** | Optional, and the thing most often missed. Ask whether this is *the* CAD model / manual for the product or one of several — a wheel kit, a charging dock, a battery. If there is any chance of a second file of the same kind, include it: without a subject the second one overwrites the first and nothing reports it. |
| **lang** | One of `LANGS`. Look inside the document if unsure; do not assume English. |
| **version** | **From the document itself**, or the issue date when the manufacturer gives none — `v2020.10.29`, read from the file's own header. Never `v1` by default. — cover page, revision table, footer. Open it and check. Never invent one, never default to `v1`: it is permanent. If genuinely unversioned, say so and agree one with the user rather than guessing. |
| **section** | The top-level content directory the page lives in: `robot`, `solution`, `peripheral`, `system`, `tutorial`, `support`. |
| **section** | One of `SECTIONS`: the top-level content directory the page lives in. |

If the extension is not in `CONTENT_TYPES`, stop. Adding one is a deliberate
edit to `wrfiles.py` with the right MIME type, not something to do in passing.
Expand All @@ -50,7 +50,21 @@ Publishing needs the AWS CLI, boto3, credentials and `WR_FILES_DISTRIBUTION_ID`
`static/_upload/<section>/<product>/<product>-<kind>[-<subject>]-<lang>-v<version>.<ext>`.
Copying leaves the user's original where they left it.

2. **Dry run.** `python3 scripts/publish-files.py`
2. **Put it on the page,** if it is not already there. One component covers
every document for that product:

```jsx
<Downloads product="scout-mini" />
```

It belongs in *Related resources*, under **Documents we publish**, never in
the hand-maintained table beside it. Do not write a link to the file by hand.
If that table has a dead link to this document, delete the row.

3. **Preview.** `npm run start` and check the page. The staged file shows in
the table marked `staged`.

4. **Dry run.** `python3 scripts/publish-files.py`

`--list <product>` first is worth it when replacing something: it shows what
is already published for that product, so you can see whether this is a new
Expand All @@ -60,13 +74,13 @@ Publishing needs the AWS CLI, boto3, credentials and `WR_FILES_DISTRIBUTION_ID`
nothing has been uploaded — fix the name and run it again. Never work around
a refusal by editing the script.

3. **Publish.** `python3 scripts/publish-files.py --publish`
5. **Publish.** `python3 scripts/publish-files.py --publish`

Needs `WR_FILES_DISTRIBUTION_ID` set, or the CDN is not invalidated —
harmless for a new document, since published keys are immutable, but it
delays `index.json` by up to a minute.

4. **Verify it actually served.** Not optional, and not satisfied by the script
6. **Verify it actually served.** Not optional, and not satisfied by the script
exiting zero:

```bash
Expand All @@ -78,24 +92,21 @@ Publishing needs the AWS CLI, boto3, credentials and `WR_FILES_DISTRIBUTION_ID`
looks stale, that is the invalidation propagating — re-check after a minute
rather than republishing.

5. **Put it on the page,** if it is not already there. One component covers
every document for that product:

```jsx
<Downloads product="scout-mini" />
```

It belongs in *Related resources*, under **Documents we publish** — never in
the hand-maintained table beside it. If the page has a dead link to the
document you just published, delete that row.

6. **Rebuild and look.** `npm run build && npm run serve`, then the product page.
7. **Rebuild and look.** `npm run build && npm run serve`, then the product page.

A local build with the file still staged shows the **local** copy, badged
`staged`. To see what a customer gets, remove `static/_upload/` first and
rebuild — that is the build CI produces.
`staged`. To see what a customer gets — the build CI produces — move
`static/_upload/` outside the repository while you build, then move it back.
Do not delete it: it can hold staged documents that are not published yet.

```bash
mv static/_upload ../_upload.hold
npm run build && npm run serve
# after stopping the server:
mv ../_upload.hold static/_upload
```

7. **Clean up.** Delete the staged copy once published; it is scaffolding, and
8. **Clean up.** Delete the staged copy once published; it is scaffolding, and
the page now resolves from the store.

## Things that will bite
Expand All @@ -110,8 +121,8 @@ the correct one alongside; the wrong key simply stays.
Check the document, do not infer from a filename someone else chose.

**`npm run check:downloads` must pass** before committing. It fails if a page
still points into `static/_upload/`, which means the document was never
uploaded and the page would 404 for everyone but you.
still points into `static/_upload/`, which would 404 for everyone but you, or
if a `<Downloads>` query matches nothing in the store.

**Do not commit the file.** `static/_upload/` is gitignored deliberately; the
repository is already ~350 MB packed and git history is permanent.
48 changes: 31 additions & 17 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Everything here is a **task**. Where a task has reasoning behind it, this file l

## Who this is for

This repository is edited by people writing documentation. **Technicians publishing a manual do not need it** — they upload through the AWS console and never clone anything. That flow is [`docs/design/file-hosting.md` §3](docs/design/file-hosting.md).
This repository is edited by people writing documentation. A technician with a manual to publish can hand the file to someone in the `DocsDownloadPublishers` group, or use the upload-file skill (`.claude/skills/upload-file/SKILL.md`).

## Before you open a pull request

Expand Down Expand Up @@ -98,9 +98,10 @@ Manuals, SDK archives, firmware. These live in the file store, never in git —
1. Put the file in `static/_upload/<section>/<product>/`, **at the path it will occupy in the store**: `static/_upload/robot/wr65/wr65-user-manual-en-v2.3.pdf`. The directory is gitignored.

**The name matters** — it carries all the metadata and is checked rather than trusted. See [Naming a document](#naming-a-document) below before you copy anything in.
2. `npm run start` and check the page. The link works locally, so you are reviewing the real thing.
3. `python3 scripts/publish-files.py` shows what it would do. Add `--publish` to upload and rewrite the page's link to the published URL.
4. Rebuild and look again. The second review is against exactly what a customer gets.
2. Check the page has `<Downloads product="…" />`, with the same `<product>` as the directory, and add it if not. Do not write a link to the file by hand.
3. `npm run start` and check the page. The staged file shows in the table marked *staged*, so you are reviewing the real thing.
4. `python3 scripts/publish-files.py` shows what it would do. Add `--publish` to upload.
5. Rebuild and look again. The second review is against exactly what a customer gets.

### Has it actually gone up?

Expand All @@ -122,7 +123,7 @@ curl -s https://download.westonrobot.net/index.json | grep wr65

That is the same file the site build reads, so if a document is in there it is live.

`npm run check:downloads` fails if a page still points into `static/_upload/`. That is not a lint rule — CI has no copy of your local file, so such a page would 404 for everyone but you.
`npm run check:downloads` fails if a page still points into `static/_upload/`. That is not a lint rule — CI has no copy of your local file, so such a page would 404 for everyone but you. It also fails if a `<Downloads>` query matches nothing in the store's index. If the index can't be reached, it reports those queries as not checked and does not fail.

The path convention is [ADR 0001 D4](docs/adr/0001-host-downloadable-documents-on-s3.md): `/<section>/<product>/<file>`, and a published path is **never renamed or deleted**. A customer's bookmark, a QR code printed on a chassis and a support email from 2024 all depend on that.

Expand All @@ -137,14 +138,14 @@ static/_upload/<section>/<product>/<product>-<kind>[-<subject>]-<lang>-v<version

| Segment | Rules | Where it ends up |
| --- | --- | --- |
| `<section>` | one of `robot`, `solution`, `peripheral`, `system`, `tutorial`, `support` | first path segment of the URL |
| `<section>` | one of `robot`, `solution`, `peripheral`, `system`, `guides`, `support` | first path segment of the URL |
| `<product>` | lowercase and hyphens. **Must match the page's `<Downloads product="…" />`** or the document will not appear | second path segment — this is what groups every file for one robot |
| `<product>-` | the filename repeats the product slug | so the name still means something once someone has saved it to a desktop |
| `<kind>` | **one of the listed values below** — not free text | the **Document** column, tidied for display: `user-manual` → "User manual" |
| `<kind>` | **one of the listed values below** — not free text | the **File** column, tidied for display: `user-manual` → "User manual" |
| `<subject>` | **optional**, free text, lowercase and hyphens. Use it when a product has more than one of a kind — a CAD model of the body *and* of a wheel kit, a manual for the robot *and* for an accessory | appended to the **File** column: `CAD · Off road wheel`. Without it two such files share a key and the second silently overwrites the first |
| `<lang>` | **one of `en`, `zh`, `zh-hans`, `zh-hant`** | the **Language** column |
| `<lang>` | **one of `en`, `zh`, `zh-hans`, `zh-hant`, `zxx`**. `zxx` means no language: CAD models, firmware images, wiring diagrams | the **Language** column, shown as English, 中文, 简体中文, 繁體中文 or — |
| `v<version>` | `v` then digits and dots: `v2`, `v2.0`, `v2.0.1` | the **Version** column, sorted numerically so `v2.1` correctly beats `v2.0.9` |
| `<ext>` | must be in the publishable set — PDF, ZIP, tar.gz, MP4, XLSX and a few others | sets `Content-Type`; an unlisted extension is refused rather than served as a generic download |
| `<ext>` | must be in the publishable set — PDF, ZIP, tar.gz, MP4, XLSX and a few others | the **Type** column, and sets `Content-Type`; an unlisted extension is refused rather than served as a generic download |

**Use the version printed on the document itself.** It becomes part of a permanent URL that is never renamed (ADR 0001 D4), so a guessed version is wrong forever.

Expand All @@ -163,7 +164,7 @@ A hyphenated subject renders with spaces — `off-road-wheel` becomes "Off road

#### The `kind` vocabulary

Fixed on purpose, and covering what a hardware documentation site normally carries. Free text here is how a store ends up holding `cad`, `CAD`, `STP` and `STL` for the same thing — at which point `<Downloads kind="…" />` stops being usable and the Document column reads inconsistently.
Fixed on purpose, and covering what a hardware documentation site normally carries. Free text here is how a store ends up holding `cad`, `CAD`, `STP` and `STL` for the same thing — at which point `<Downloads kind="…" />` stops being usable and the File column reads inconsistently.

| Group | Values |
| --- | --- |
Expand All @@ -177,19 +178,19 @@ Three that catch people out:

- **`cad` covers STEP, STL and DXF.** The format is the extension; the kind is what the document *is*.
- **`manual` is not a value** — use `user-manual`, so it cannot drift apart from itself.
- **Chinese is `zh`, never `cn`.** Language is one of `en`, `zh`, `zh-hans`, `zh-hant`.
- **Chinese is `zh`, never `cn`.** Language is one of `en`, `zh`, `zh-hans`, `zh-hant`, `zxx`. A file with no language in it, such as a CAD model, firmware image or wiring diagram, is `zxx`, not `en`.

If something genuinely new comes along, add it to `KINDS` in `scripts/wrfiles.py`. The moment's thought about whether it duplicates a value already there is the entire point of the list.

#### Worked examples

| Filename | Renders as | URL becomes |
| --- | --- | --- |
| `scout-mini-user-manual-en-v2.0.1.pdf` | User manual · en · v2.0.1 | `download.westonrobot.net/robot/scout-mini/scout-mini-user-manual-en-v2.0.1.pdf` |
| `scout-mini-user-manual-zh-v2.0.1.pdf` | User manual · zh · v2.0.1 | `download.westonrobot.net/robot/scout-mini/scout-mini-user-manual-zh-v2.0.1.pdf` |
| `scout-mini-quick-start-en-v1.pdf` | Quick start · en · v1 | `download.westonrobot.net/robot/scout-mini/scout-mini-quick-start-en-v1.pdf` |
| `scout-mini-cad-en-v1.zip` | Cad · en · v1 | `download.westonrobot.net/robot/scout-mini/scout-mini-cad-en-v1.zip` |
| `wr65-wire-protocol-en-v3.2.pdf` | Wire protocol · en · v3.2 | `download.westonrobot.net/robot/wr65/wr65-wire-protocol-en-v3.2.pdf` |
| `scout-mini-user-manual-en-v2.0.1.pdf` | User manual · English · v2.0.1 | `download.westonrobot.net/robot/scout-mini/scout-mini-user-manual-en-v2.0.1.pdf` |
| `scout-mini-user-manual-zh-v2.0.1.pdf` | User manual · 中文 · v2.0.1 | `download.westonrobot.net/robot/scout-mini/scout-mini-user-manual-zh-v2.0.1.pdf` |
| `scout-mini-quick-start-en-v1.pdf` | Quick start · English · v1 | `download.westonrobot.net/robot/scout-mini/scout-mini-quick-start-en-v1.pdf` |
| `scout-mini-cad-zxx-v1.zip` | CAD · — · v1 | `download.westonrobot.net/robot/scout-mini/scout-mini-cad-zxx-v1.zip` |
| `wr65-api-reference-wire-protocol-en-v3.2.pdf` | API reference · Wire protocol · English · v3.2 | `download.westonrobot.net/robot/wr65/wr65-api-reference-wire-protocol-en-v3.2.pdf` |

The two Scout Mini rows differ only by language, and both appear in the same table — one row each, no page edit. That is the point of the convention: the store answers `product="scout-mini"` with everything it has.

Expand Down Expand Up @@ -235,12 +236,25 @@ Anything larger, or anything re-shot on a release cadence, belongs in the file s

Add a redirect in `docusaurus.config.ts`. Old URLs are in circulation with customers and pasted into support tickets, so a moved page without a redirect is a broken link for someone who has no idea the page moved.

Update `static/llms.txt` in the same change if it lists the page. The build does not check that file ([`docs/design/ia-proposal.md`](docs/design/ia-proposal.md) §10).

## Unlisting a page

1. Set `unlisted: true` in the page's front matter.
2. Add the route to `excludeRoutes` under `docusaurus-lunr-search` in `docusaurus.config.ts`. Search does not read the front matter. The entries are globs, so a page with its own directory needs both `**/x` and `**/x/**`.
3. Remove any `<ProductCard>` that links to the page, such as on `robot/intro.md`. Unlisting does not hide the card.
4. Check with `npm run build && npm run serve`, not `npm start`. The dev server shows unlisted pages.

See [`docs/LESSONS.md`](docs/LESSONS.md), *"A page hidden from the sidebar is still in site search"*.

## Recording a decision

- **Design decisions** → an ADR in `docs/adr/`, including the rejected alternatives and *why*. A decision without its discarded options cannot be re-evaluated later.
- **Findings and follow-ups** → [`TODO.md`](TODO.md), with the `file:line` and how it was verified.
- **Operational lessons** → [`docs/LESSONS.md`](docs/LESSONS.md), in Pattern / Correction / Context form.

**Not in a config file.** `docusaurus.config.ts`, the sidebars and CI workflows record *what* is set. A comment there may explain a mechanism a maintainer needs at that line. The reasoning behind a decision goes in an ADR, the design doc it amends, `TODO.md` or `docs/LESSONS.md`, with at most a one-line pointer left behind. If the comment would change when the decision is revisited, it moves. See [`docs/LESSONS.md`](docs/LESSONS.md), *"Rationale in a config file is filed where nobody looks for a decision"*.

If an accepted decision contradicts an existing document, fix that document in the same change. An ADR that disagrees with the design doc is worse than no ADR.

## Where things are
Expand All @@ -251,6 +265,6 @@ If an accepted decision contradicts an existing document, fix that document in t
| How a product page is laid out | `docs/design/product-page-template.md` |
| How the file store works | `docs/design/file-hosting.md` |
| Why the file store exists, and its decisions | `docs/adr/0001-host-downloadable-documents-on-s3.md` |
| Deploying the file store infrastructure | `infra/README.md` |
| Deploying the file store infrastructure | the private infrastructure repository |
| Mistakes already made once | `docs/LESSONS.md` |
| What is open | `TODO.md` |
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Documentation is split into six independent Docusaurus docs plugin instances, ea
| `/peripheral` | `peripheral/` | `sidebars-peripheral.ts` |
| `/system` | `system/` | `sidebars-system.ts` |
| `/solution` | `solution/` | `sidebars-solution.ts` |
| `/tutorial` | `tutorial/` | `sidebars-tutorial.ts` |
| `/guides` | `guides/` | `sidebars-guides.ts` |

The `general` and `software` sections were dissolved; `docusaurus.config.ts` keeps redirects for their old URLs, because support engineers paste them into tickets.

Expand Down
Loading
Loading