Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 3 additions & 61 deletions .claude/rules/doc-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,7 @@ paths:
- "**/*.mdx"
---

# Structuring and linking docs
# Doc structure

## Pick the right category (Diátaxis)

Every page does exactly one of these jobs. Mixing them inside one page is why docs end up
feeling unfocused:

- **Tutorial** — a guided first experience. Optimizes for the reader finishing
successfully, not for completeness.
- **How-to guide** — solves one specific, named problem for someone who already knows the
basics. Skips background explanation.
- **Reference** — describes the thing accurately and completely (an API, a config schema,
a component's props). No opinions, no narrative — facts, kept accurate as the thing
changes.
- **Explanation** — the why: design rationale, tradeoffs, how pieces fit together. The only
category where discussing alternatives and context belongs.

Not sure which one a page is? That's the sign it's trying to do two jobs — split it.

## Fully documenting a component: don't skip details

When writing reference or explanation content for a package or component, cover what a
careful reviewer would ask about, not just the happy path:

- What it's for, in one sentence a newcomer understands without prior context.
- Its public surface: what a consumer actually calls, imports, or configures.
- Invariants and constraints: what must be true before and after, what it assumes the
caller already did.
- Failure modes: what happens when it's misused, and what signals that (error, log,
silent no-op).
- Edge cases that aren't obvious from the signature — ordering, concurrency, lifecycle,
platform-specific behavior.
- Why it's built this way, when the reason isn't obvious. Link to an explanation page
rather than re-explaining inline.

Missing one of these isn't a style problem, it's a coverage gap — the kind of thing a
reader hits in production with nowhere to look it up.

## Sections and metadata

- One H1 per page, matching the frontmatter title.
- A one- or two-sentence summary right under the H1: what this page is and who it's for,
before anything else.
- Sentence-case headings throughout (see `writing-style.md`).

## Linking instead of duplicating

- Never restate content that already exists on another page. Link to the canonical page
instead — two copies of the same explanation will drift, a link can't.
- Link relatively within the same project folder (`../other-page`); link from the docs
root when crossing into another project's docs (`/docs/<other-project>/...`).
- When a decision has real tradeoffs and a future reader will reasonably ask "why was it
done this way", record it once as a short decision note (what, why, alternatives
considered) and link to it from everywhere it's relevant. Don't re-litigate it inline on
every page that touches it.

## Multi-project layout

Each project under the `tsvrc` org gets `docs/<project>/` in this repo (see the root
`CLAUDE.md` for why this is one repo, not one repo per project). Don't duplicate a concept
shared by two projects — document it once under whichever project owns it, and link from
the other.
See [STYLE_GUIDE.md](../../STYLE_GUIDE.md), the "Doc structure" section, canonical for
this repo. Written for any contributor, human or AI, not just for Claude specifically.
53 changes: 3 additions & 50 deletions .claude/rules/writing-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,7 @@ paths:
- "**/*.mdx"
---

# Writing style: sound human, say only what's needed
# Writing style

Docs written by an LLM have a recognizable smell, and readers trust content less once they
notice it. Avoid it.

## Cut, don't pad

- If a reader can see it by reading the code, don't restate it here. A doc earns its place
by adding what code can't show: why, tradeoffs, what breaks, what to do next.
- Before adding a paragraph, check what it adds beyond the paragraph before it or the code
itself. If nothing, delete it.
- One idea per section. If two sections could swap order without anything breaking, that's
a list wearing the structure of an argument — tighten it.

## Words and phrases to avoid

These are statistically over-represented in LLM output and read as filler to anyone who has
seen a lot of AI-generated text: delve, leverage (verb), robust, seamless, comprehensive,
cutting-edge, pivotal, meticulous, landscape / realm / tapestry (as metaphors), utilize,
"in order to", streamline, foster, elevate, unlock, harness, ecosystem (unless it's
literally an ecosystem), myriad, plethora, crucial, paramount, nuanced, "it's worth noting",
"it's important to note", "at its core", "best practices" (name the actual practice instead).

Hedging kills technical writing — cut "could potentially", "may eventually", "it's worth
mentioning". State the fact or the tradeoff directly.

## Sentence and paragraph shape

- Vary sentence length on purpose. A row of same-length sentences reads like a template.
- Don't open with "In today's...", "Let's explore...", or "In this doc, we will...". Start
with the actual point.
- Avoid the "It's not X, it's Y" construction. State the positive claim.
- Sentence case for headings ("Set up your environment"), not title case
("Set Up Your Environment").
- Skip chatbot artifacts entirely — "I hope this helps", "Great question", "Feel free to
reach out". This is documentation, not a chat reply.

## Punctuation and formatting

- Avoid em dashes for asides in prose; use a comma, a colon, or a full stop instead. (An em
dash inside a bolded-term list item is fine: `- **Term** — one-line description`.)
- Don't bold more than one phrase per section — bolding everything bolds nothing.
- Reach for prose over bullets when explaining an idea with real connections between its
parts. Bullets suit genuinely parallel, independent items — flags, a checklist, install
steps. A bulleted list of bare noun phrases ("Fast. Reliable. Secure.") explains nothing;
say what's actually fast, reliable, or secure, and why.

## Read it back before committing

Read the page out loud, or run it through text-to-speech mentally. Rewrite anything you
wouldn't actually say to a colleague. This one pass catches most of the above.
See [STYLE_GUIDE.md](../../STYLE_GUIDE.md), the "Writing style" section, canonical for
this repo. Written for any contributor, human or AI, not just for Claude specifically.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
- [ ] Commits are signed off (`git commit -s`) per the [DCO](https://developercertificate.org/) — this certifies you wrote the change or otherwise have the right to submit it under this project's license.
- [ ] If AI tooling helped write part of this PR, that's disclosed in this description (see `CONTRIBUTING.md`), and you've personally reviewed, understood, and checked it renders correctly.
- [ ] `npm run build` passes locally (`onBrokenLinks: 'throw'` catches broken links this way).
- [ ] The change follows `.claude/rules/doc-structure.md` and `.claude/rules/writing-style.md`.
- [ ] The change follows `STYLE_GUIDE.md`.
- [ ] If this documents `tsvrc-core` behavior, it matches what the code actually does.
19 changes: 14 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
## Project overview

Docusaurus site (TypeScript, Node 20+) documenting every project under the `tsvrc` org,
namespaced under `docs/<project>/` rather than one repo per project.
namespaced under `docs/<project>/` rather than one repo per project. See
[CONTRIBUTING.md](CONTRIBUTING.md) for the multi-project layout and the i18n workflow.

## Setup

Expand All @@ -18,10 +19,18 @@ namespaced under `docs/<project>/` rather than one repo per project.

## Code style

- Follow `.claude/rules/doc-structure.md` (Diátaxis categories, linking instead of
duplicating) and `.claude/rules/writing-style.md` (avoid AI-sounding prose) for any
`.md`/`.mdx` change, despite the `.claude/` path these apply to any contributor.
- See `CONTRIBUTING.md` for the multi-project layout and the i18n contribution path.
Follow [STYLE_GUIDE.md](STYLE_GUIDE.md) for any `.md`/`.mdx` change: Diátaxis categories,
linking instead of duplicating, avoiding AI-sounding prose.

## Gotchas

- `sidebars.ts` is hand-written per project, not a blanket autogenerated tree. A
`dirName` pointed straight at a leaf folder returns an unlabeled flat list, so a
category's `label` has to come from the wrapping `{type: 'category', ...}` node, not a
`_category_.json`.
- `tsvrc-core` (code) and this repo (docs) aren't wired together, no submodule, no
build-time fetch, and can drift. Changed public API or behavior in `tsvrc-core`, or
something under `docs/tsvrc/`? Check the other side still matches.

## Commit / PR rules

Expand Down
136 changes: 5 additions & 131 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,132 +1,6 @@
# TsVRC docs
<!-- Canonical agent instructions live in AGENTS.md, imported below, per Anthropic's
documented pattern for repos that already have an AGENTS.md (Claude Code reads
CLAUDE.md, not AGENTS.md, so this keeps both tools reading the same file instead of
two files drifting apart). See AGENTS.md and STYLE_GUIDE.md for the actual content. -->

This repo is the documentation site for the `tsvrc` GitHub org (Docusaurus, TypeScript).
It documents TsVRC and any other project under the org, namespaced by folder — not one
docs repo per project.

## Structure

- `docs/<project>/` — one folder per project (e.g. `docs/tsvrc/`). A new project gets a
new folder here, not a new repo.
- Content follows the Diátaxis framework (tutorial / how-to / reference / explanation).
Read `.claude/rules/doc-structure.md` before adding or restructuring a page.
- `sidebars.ts` is hand-written per project, not a single blanket
`{type: 'autogenerated', dirName: '.'}`. Each project gets one top-level category
(see the `Framework` category for `tsvrc`) built from `{type: 'autogenerated', dirName: '<project>/<folder>'}`
subtrees grouped as Getting started / How-to guides / Reference / Explanations. That
grouping is what keeps Diátaxis categories visually separated in the sidebar. A new
project needs its own top-level category added there the same way; it won't show up
from folder presence alone. Each folder-level category's `label` lives in `sidebars.ts`
itself, not in a `_category_.json`: `dirName` pointed straight at a leaf folder
returns its files as a flat, unlabeled list, so the label has to come from the
wrapping `{type: 'category', ...}` node instead. Ordering *within* an autogenerated
subtree still comes from each page's `sidebar_position` frontmatter.
- Writing style — grammar, tone, avoiding AI-sounding prose, avoiding noise — is defined in
`.claude/rules/writing-style.md`. Read it before writing or editing any `.md`/`.mdx` file.

## Internationalization (i18n)

This site is set up for `en` (default) and `es`, but only `en` has real content right
now — `es` currently covers only the two marketing pages (`src/pages/index.tsx`,
`src/pages/framework.tsx`) plus one worked-example doc page (see below), not the rest of
the docs.

- **Docs content** (`docs/<project>/`): stays English-only, unversioned, exactly where
it is. This is deliberate, not a gap to fill by default — per Docusaurus's own i18n
convention, the default locale's docs live directly under `docs/`, and a
translation is added later as a parallel tree at
`i18n/<locale>/docusaurus-plugin-content-docs/current/<project>/`, mirroring the
same relative paths and filenames. Adding a language never touches the English
files. Sidebar category labels live in `sidebars.ts` itself (see the Structure
section above), not in a translatable `_category_.json`, so they render in English
for every locale regardless of `i18n/<locale>/docusaurus-plugin-content-docs/current.json`.
That's deliberate for now, since a half-translated sidebar over English content would be
worse than an English one. Translating them means giving `sidebars.ts` locale-aware
labels (e.g. via `docusaurus-plugin-content-docs`'s own i18n message extraction) once
real doc translations exist to justify it.
- **The two marketing pages**: translated today via `@docusaurus/Translate`/
`translate()` in the component source, with per-locale strings in
`i18n/<locale>/code.json`. Adding a language means running
`docusaurus write-translations --locale <code>` and filling in that locale's
`code.json` — the component code itself doesn't change.
- **Theme chrome** (pagination, admonition labels, sidebar buttons, etc.): never
override these in this site's own `code.json`. Docusaurus ships its own
professionally translated defaults per locale; only `homepage.*`/`framework.*` keys
belong in this site's `code.json`.
- Adding a third locale: add it to `i18n.locales` (and `localeConfigs`) in
`docusaurus.config.ts`, run `write-translations`, translate `code.json` plus
`docusaurus-theme-classic/{navbar,footer}.json`. Docs stay English until someone
actually translates them into the new `i18n/<locale>/docusaurus-plugin-content-docs/current/`
tree.
- **Translation quality: never translate literally.** When writing or updating any
`code.json` (or any other translated string) for `es` or any future locale, use the
term a professional in that field, in that language, actually uses — not a word-for-word
rendering of the English. Before translating a technical or domain term (Unity/UdonSharp/
VRChat/GitHub concepts especially), check how it's rendered in that language's official or
most-trusted sources (e.g. Unity's own localized manual at `docs.unity3d.com/es/...`, VRChat's
Spanish-speaking creator community, GitHub's own glossary/docs) rather than guessing or
machine-translating. Two patterns come up constantly and cut opposite ways, so check per
term instead of assuming:
- **Many technical/proper nouns are conventionally left in English**, even in otherwise
fully translated prose — e.g. in Spanish, Unity's own docs never translate `GameObject`,
`Inspector`, `Prefab`, `Script`, or `Play Mode`; GitHub's Spanish glossary keeps `issue`
and `pull request` as loanwords (GitHub's own UI ships English-only, so the loanword is
literally what a user clicks). This repo's own code identifiers (`_ts.Memory`, `TsStart`,
`TsBehaviour`, class/property names inside example code or referring to it) are never
translated either — including inside descriptive text, e.g. `_ts.Name` must stay
`_ts.Name`, not become `_ts.Nombre`.
- **Other English phrasing has a real idiomatic equivalent that isn't the literal
word-for-word choice** — e.g. "extends"/inheritance reads more naturally in Spanish as
"hereda de" than a calque like "extiende"; Unity's Spanish manual calls `Instantiate()`
"instanciar", not a generic "generar/crear". Prefer the term the target community
actually uses in casual technical speech, not a dictionary's first hit.
- Also double-check direction/meaning survived translation, not just word choice — a
literal-sounding translation can silently invert an "A extends B" relationship if you're
not reading the translated sentence back for what it actually asserts.
- A word can be the "correct" dictionary or OS-level technical term and still not be what
this content's actual audience says day to day — e.g. "arranque" is standard Spanish for
booting an OS, but reads stiffer than "inicio"/"iniciar" for a script or behaviour starting
up in casual creator-facing prose. When a general web search and a native speaker's ear
disagree, defer to the native speaker: ask or take their correction over the search result.
- **Worked example**: `docs/tsvrc/intro.md` has a real Spanish translation at
`i18n/es/docusaurus-plugin-content-docs/current/tsvrc/intro.md`: same filename,
same relative path, translated content. Copy that pattern for any other doc page.
Verified in-browser: the translated page renders correctly, and links from it to
still-untranslated pages fall back to English cleanly under the same `/es/` prefix.
- **Headings that other pages link to by anchor** need an explicit, language-stable id,
or translating the heading text breaks the anchor. Docusaurus's MDX v3 syntax for this
is a comment, not `{#id}` (that's the old, now-invalid MDX v1/v2 form and fails the
build): `## Play Mode tests: TsPlayModeTestBase {/* #play-mode-tests-tsplaymodetestbase */}`
(see `docs/tsvrc/testing/testing-your-world.md`). Keep the same id across every
language's version of that heading.

## Commands

- `npm start` — dev server at http://localhost:3000, hot-reloads content but not
`docusaurus.config.ts` (restart the server after config changes).
- `npm run build` — production build. Run this before calling a doc change done;
`onBrokenLinks: 'throw'` means a broken link fails the build.

## Cross-repo: tsvrc/tsvrc-core and tsvrc/docs

This repo documents code that lives in the separate `tsvrc/tsvrc-core` repo. The two are not
wired together technically — no submodule, no build-time fetch (see below for why). Nothing
keeps them in sync automatically:

- Changed public API, behavior, or the package description in `tsvrc/tsvrc-core`? Check whether
`docs/tsvrc/` needs a matching update.
- Changed something in `docs/tsvrc/`? Check it still matches what `tsvrc/tsvrc-core` actually
does before publishing.

A global hook flags this when either repo's docs-relevant files change. It's a nudge to go
check, not proof the two are actually in sync.

## Why there's no cross-repo build wiring

We evaluated pulling `tsvrc/tsvrc-core`'s docs into this site via a git submodule at build time
and rejected it. Surveying large community projects — Kubernetes, React, Vue, Python,
Node.js, Docker — shows the dominant patterns are docs-in-the-code-repo (single product) or
a fully self-contained docs repo, and for multi-product orgs, one consolidated repo
namespaced by project (Docker moved to this after abandoning per-product federation). None
of them merge separate repos into one build. Keep this repo self-contained unless a project
genuinely outgrows it.
@AGENTS.md
Loading