Contributor Handbook¶
Any community that collaborates for long enough accumulates unwritten rules: how to phrase a heading, how to name a file, what belongs in a pull request description, how issues get sorted, and the questions that come up in a contributor's first week. This handbook collects what would otherwise stay scattered across the README, issue comments, and Matrix conversations, so a new contributor can read it in one sitting and experienced members have something common to point at.
If this is your first time here, start with How to contribute to pick a direction, then come back for the specifics. Account requests and service entry points are on Community services.
Your first week¶
Sorted by what you want to do:
- Read first, decide later: pick anything from Concepts, then use the skill level self-assessment to gauge how familiar you are with Tor, Tails, and OONI
- Write or translate: request a Matrix account (see Community services), join the public Space, say what you would like to work on, and claim an issue
- Technical maintenance: request collaborator access to anoni-net/docs, then follow Development environment setup
- Event organizing: ask in the relevant Matrix room about what is coming up, and help with materials, on-site logistics, or registration
Every one of these starts with saying hello on Matrix. The community works asynchronously, so a reply landing a day or two later is the normal rhythm, not a snub.
Writing style¶
English and Chinese have separate rule sets¶
Traditional Chinese (docs/zh-TW) is the source of truth for the site, and its style rules cover Chinese punctuation, classifier repetition, register, and translated terminology. Those rules do not transfer, and several are actively wrong when applied to English. Em dashes, for example, are banned in Chinese body text and are ordinary English typography.
What follows is the English rule set. If you are writing or reviewing Chinese, use the Chinese contributor handbook (in Chinese) instead, which is the authority for zh-TW and zh-CN. Since 2026-08 the automated style linter in CI also covers docs/en, though only three of the English rules are mechanised so far (bold-lead-sentence, title-colon, machine-field). The rest of the English rules below rest on human review.
What these rules cover¶
The rules apply to the documentation under docs/ in all three locales, and to the repository's own explanatory files: README.md, CONTRIBUTING.md, AGENTS.md, CLAUDE.md, and NOTICE at the root, plus the README.md in each subdirectory. Readers meet the project through those files, so they follow the same standard as the site.
The docs-style-lint job only fires on Markdown changes under docs/zh-TW, docs/zh-CN, and docs/en. After editing an explanatory file, run the linter yourself:
python3 tools/docs_style_lint.py README.md CONTRIBUTING.md
NOTICE has no .md extension and the linter only accepts .md and .js, so that one needs a human read.
One group of rules is explicitly grandfathered for existing content; right now that is only title-colon from "Heading structure" below. CI passes --changed-since <base> so those rules are reported only on lines the pull request actually touched. Leave the flag off locally to see the whole file:
python3 tools/docs_style_lint.py --changed-since origin/main docs/en/tools/vpn-guide.md
Without it, changing a single image reference surfaces annotations for every old heading in the file, none of them related to the author's change, and the ones that do need fixing get lost among them.
Rule documents spell out every banned punctuation mark and sentence pattern, so the linter flags its own rule descriptions. This handbook and the workspace projection are exempted by filename through the linter's RULE_DOCS, and the rule table and known-limits section in tools/README.md are wrapped in <!-- docs-style-lint: disable --> and enable. Follow the same approach when writing rule documentation, and leave the quoted examples as they are.
Voice and positioning¶
The English site is written for international peers, researchers, journalists, and English-preferring readers across the Sinophone Asia-Pacific, by people working inside the region. The prose should sound like it.
- Write the name as
anoni.net, all lowercase, including at the start of a sentence (anoni.net Docs Project). NeverAnoni.net. URLs and email addresses keep their own form. - Refer to ourselves as "we, a community based in Taiwan". Avoid "In Taiwan, we...", which addresses the reader as though they were also in Taiwan.
- Where a passage is specific to Taiwan, add the regional comparison rather than leaving Taiwan as the implied default. Mainland China, Hong Kong and Macau, Singapore, Malaysia, and the diaspora each have their own picture.
- Do not translate Chinese conceptual shorthand literally. Phrases like 在地脈絡 or 公民團體 turn into stilted English when carried across word for word. Say what is actually meant.
Terminology¶
- Write regulatory short names out in full on first use: PDPA becomes "the Personal Data Protection Act of Taiwan", VASP becomes "the virtual asset service provider regime".
- Write institution names out in full: 金管會 becomes "the Financial Supervisory Commission (FSC)".
- Give technical names a short expansion on first use: Tor (onion routing network), Tails (amnesic live operating system), OONI (Open Observatory of Network Interference).
- Cite English-language primary sources in footnotes. Do not cite the Chinese translation of a piece that exists in English.
Headings¶
- Write headings as noun phrases, not as sentences. Recast a heading built around a verb into a noun structure; where a second layer of information is needed, continue with a comma or leave it to the intro and the
summary.-
What the docs site gained in the past two weeks -
Docs site update review, September 2026
-
- Do not use the "Topic: explanation" colon construction.
-
Brave and GPU fingerprinting: uniformity and randomization in one release -
Two opposite approaches to flattening GPU fingerprints in Brave
-
- Do not let a non-human subject perform an action in a heading; the test is the same one used for animacy in body text.
-
The sidebar groups the tools into five sets -
The five utility groups
-
- This applies to article titles and to section headings at every level.
- Keep an external source's original title as-is when the link text quotes it.
- Existing articles do not need retrofitting. Apply this to new articles and substantial rewrites.
Paragraph voice¶
- Write like a community member who knows the subject explaining it, not like an encyclopedia entry.
- Do not end every paragraph with a summarizing sentence. Let paragraphs stop when they are finished.
- Avoid openers like "It is worth noting that", "In conclusion", and "All in all".
- Avoid the over-symmetrical three-part structure that reads as machine-generated. Two-part parallel pairs count too: "For people outside... For people inside..." has the shape of an argument without the content. Write the actual scale, numbers, or who is affected.
- If a point reads clearly as a full sentence, do not break it into a bullet list.
- Define a concept by stating it completely. Constructions like "what this is about is" or "this refers to" push the definition out of focus without adding anything.
Narrative structure¶
- Keep dates, version numbers, PR numbers, and URLs out of the opening paragraph. Open with why the matter is important, and put the facts in the sections they belong to.
- Carry one argument through the piece and tie each section back to it, so the article does not read as a chronological event log.
- A timeline can list dates, but frame it: say why the work was worth doing before the list, and who benefits after it.
- In a call to action, say near the top who is invited, what they would do, and what to do instead if they cannot. Use an admonition (
!!! tip) when it needs to stand out.
No animacy for things that are not people¶
Non-human subjects do not take human actions. The common cases and their fixes:
| Case | ||
|---|---|---|
| Organizations speaking | Brave said it would follow up later |
Brave's announcement said it would follow up later |
| Documents speaking | The report points out the risk |
The risk is in the report's conclusion |
| Software perceiving | The site sees an unfamiliar string |
The string the site receives is not in its existing list |
| Abstractions having intent | The toggle's existence says the trade-off remains |
Keeping the toggle means the trade-off remains |
Two exceptions. An organization acting as an agent keeps the plain verb when the action is something it can actually do (Brave shipped the protection, the Tor Project released a new version, OONI collects measurements). Direct quotations keep their original wording.
Cutting the machine-written texture¶
The edits that come up most in review:
- Delete the throat-clearing opener.
Let us first lay out the basics of CryptPad. It is...becomesCryptPad is.... Start with the content instead of announcing what is coming. - Cut filler transitions: "essentially", "in other words", "to put it plainly". Delete rather than replace where possible.
- Replace an abstract placeholder with the actual content.
The next section explains why that conclusion does not holdbecomesThe usage figures in the next section contradict it. - Use a metaphor once in a while at most. Do not carry a whole piece on one, and do not stack the same metaphor twice in a sentence.
Each new relay in Taiwan puts another entrance on the mapbecomesEach new relay in Taiwan gives people nearby one more unblocked way into Tor. - Drop intensifiers and emotional colour.
battle-tested under real-world pressurebecomeshas a record of production use. Ordinary terms do not need quotation marks for emphasis. - Do not open a paragraph with a bolded complete sentence. Promote parallel items to headings, and write standalone paragraphs as ordinary prose.
**Location.** OONI records the country and ASN...becomes a### Locationheading followed by the text. Bold words as sentence elements or list labels are fine (the **control day** uses the same parameters,**Data source**: ...). The test is whether the bolded text is a complete sentence ending in a period.
Numbers and identifiers¶
Mark list numbers, IDs, and serial numbers as inline code (10006, 10298), so a reader can see at a glance that they are identifiers rather than quantities.
Writing about security and privacy¶
Anonymity and privacy are the subject of this site, and the writing has to hold the same line:
- Do not publish recipes that can be misused. Even where the data and APIs are public, we do not walk readers through full enumeration, bulk scraping, de-anonymization, or bypassing a security control. State the result instead:
we took a snapshot of the full list on a given day, rather than printing the command that iterates every identifier. - Do not expose individual operators' accounts or handles. Refer to someone's observations by region or role (
an observer in Thailand), and name people only when they are already public and naming them is necessary. - Material involving victims, unpublished research, or personal data goes through Sending us sensitive material.
Files and directories¶
Filenames¶
- All lowercase, hyphen-separated (
tor-browser-advanced.md,anonymity-vs-privacy.md) - Slugs in English
- Acronyms stay lowercase (
vasp-2026.md, notVASP-2026.md) - Numbers follow a hyphen directly (
roadmap-2026.md,updates-202506.md)
Directory structure¶
The structure stays flat. New articles go into an existing section:
| Section | Content |
|---|---|
basics/ |
Concepts. The thinking tools behind anonymity and privacy |
tools/ |
Specific tools, comparisons, and hardening guidance |
scenarios/ |
Situations and roles, and what they change |
regional/ |
Regional observation and local regulatory context across the Sinophone Asia-Pacific |
reports/ |
Curated external research, indexed with links to the originals |
community/ |
Governance, process, and entry points |
blog/ |
Posts and original commentary |
The English site uses regional/ where the Chinese site uses taiwan/. An English reader who sees taiwan/ assumes a site about Taiwan, while the content spans several jurisdictions with Taiwan as the anchor point.
If you are not sure where an article belongs, ask on Matrix before opening a PR, rather than moving it afterwards.
Moving, renaming, or deleting a page needs a redirect¶
When you move, rename, or delete a page that is already live, add the redirect in the same PR so the old URL does not turn into a 404. Old URLs live on in search engines, bookmarks, and other people's links.
- Redirects go in
plugins.redirects.redirect_mapsin the three mkdocs configs:mkdocs.ymlfor zh-TW (/docs/),mkdocs_en.ymlfor en, andmkdocs_cn.ymlfor zh-cn. - The format is
old path: new path, relative to each language's docs directory, without thedocs/<lang>/prefix. For example,'tools/what-is-ooni.md': 'tools/index.md'. - Where there is no one-to-one replacement, point at the section index (
community/index.md,tools/index.md). - Keep existing redirects. People keep arriving at old URLs. The one case for revisiting an entry is when its target page has itself been removed and the redirect now dead-ends.
- When you add a page at a path that an existing redirect points away from, remove that redirect entry in the same PR. Otherwise the redirect shadows the new page.
Splitting or moving content needs the inbound links checked¶
A redirect handles a URL that disappears. It does nothing for the case where a page stays put and the content moves out of it, which is what a page split produces. The old page still returns 200, so nothing reports an error, while every button and link pointing at it now promises material that has gone somewhere else.
When you split a page, or move a section from one page to another, search the site for links to the source page in the same PR and repoint the ones whose text refers to what moved. Two things to know about this check:
- Neither strict build nor the style linter catches it: Both target files exist and both links resolve, so the failure is in what the link means rather than whether it works. Only reading the link text against the destination finds it.
- Dated blog posts count: A post that was accurate when published keeps its text, and a button in it is a functional entry point rather than part of the record. Repointing the button does not alter what the post said at the time, and leaving it broken means a reader following it lands somewhere that no longer holds what they were promised.
This came up in August 2026: a May 2025 split moved the workshop recruitment content into its own page, and two earlier posts kept pointing at the original, where the material no longer was.
Images and assets¶
Screenshots and diagrams take different routes.
Screenshots (application windows, web pages) go in docs/<lang>/assets/images/:
- In markdown image syntax, the path is relative to the file:
../assets/images/filenamefrom a section directory such ascommunity/. - In raw HTML
<img src>and<a href>, the path resolves against the generated URL, not the source file. From a page at/docs/en/basics/internet-freedom/that means../../assets/images/filename. - Prefer webp or an optimized png. Do not commit unprocessed phone camera files.
- For a lightbox image, wrap
<img>in<figure>and<a href>, and keep both relative paths aligned. - The three language trees have independent copies of
assets/images/. Adding a file to one means adding it to the other two. A missing copy produces no build error, just a broken image on the page.python3 tools/check_image_refs.pyfinds them.
Diagrams (flowcharts, architecture diagrams, comparison matrices, timelines) keep their source in docs/diagrams/, are published to assets.anoni.net, and are referenced by all three languages through the same URL. See "Contributing technical diagrams" in the brand guide for how to make, name, and publish one.
Cross-file links¶
Internal links use relative paths, not absolute /docs/en/... paths:
- Same directory:
./other-file.md - Across directories:
../basics/anonymity-vs-privacy.md - Across depths:
../../blog/posts/2025to2026.md
Link text describes the destination. Do not paste a bare URL into the body or use the URL itself as the link text: write see the [community tools page](./tools.md).
External links get {target="_blank"} so they open in a new tab: [Freedom on the Net](https://freedomhouse.org/explore-the-map){target="_blank"}.
Linking to a page that exists only in Chinese is the one case where you write a full URL, because the language sites build separately and no relative path reaches across them. Use https://anoni.net/docs/community/privacy-guide/ and mark it (in Chinese) so the reader knows what they are clicking. The default language, zh-TW, carries no language segment in its URLs. zh-CN uses lowercase https://anoni.net/docs/zh-cn/... and English uses https://anoni.net/docs/en/..., while the source directories keep their original casing.
Ending an article with a short "Related" section linking two to four other pages helps. Sideways links between concepts, tools, scenarios, and regional material are worth more than one-directional references.
Page format¶
Front matter¶
Every page starts with front matter carrying at least three fields:
---
title: Threat modelling
description: One complete sentence on what the page covers and what the reader gets from it
icon: material/shield-account-outline
---
titletakes no question mark and no site name. The page title and the social card add the site name automatically.descriptionfeeds search-result snippets and social cards. Write it as one complete sentence about what the reader gets from the page, not a restatement of the title.iconis usually amaterial/icon, occasionallyfontawesome-solid-orfontawesome-brands-.- The H1 follows the front matter directly as
# :material-icon-name: Title, normally with the same icon as theiconfield. - Blog posts also need
date,slug,categories, andauthors. - To change a page's social card title, description, or background, see "Social cards" in Brand assets.
Footnotes¶
Cite research and reporting with Markdown footnotes, collected at the end of the article:
The Great Firewall[^1] has long filtered a large share of international sites.
[^1]: [Original title](https://example.org/article){target="_blank"} - Publication
Avoid paywalled material as the main source. If a paywalled version is all you can find, add an archive.org link as well.
Charts¶
The site supports Vega-Lite charts (mkdocs-charts-plugin) in code blocks tagged vegalite. Prefer the Pulse API (https://api.anoni.net/api/...) as the data source. See taiwan/tor-relay-watcher.md for an example.
Structured data¶
The site-wide Organization JSON-LD lives in docs/overrides/main.html. Do not add <script type="application/ld+json"> to individual articles.
Pull requests¶
Branch naming¶
blog/<short-slug>for blog posts (blog/throttle-drill-results)feat/<short-slug>for new features, new sections, writing rules, and substantial rewrites of existing pages (feat/title-colon-rule)fix/<short-slug>for bugs, styling, and small corrections (fix/table-width)
docs/ cannot be used as a prefix. docs is itself the build trigger branch, and git will not allow the same name to be both a ref and a directory of refs, so git switch -c docs/vasp-2026-rewrite fails with cannot lock ref.
Commit messages¶
Conventional commits:
<type>(<scope>): <subject>
<body>
Common types: docs, feat, fix, chore, refactor. The scope is a language or sub-project name (zh-TW, zh-CN, en, pulse, asn_coverage).
PR descriptions¶
A PR description covers at least:
- Why the change is being made, linking the issue or the community discussion
- What it touches: which files, which sections
- What it means for readers: whether links break, whether URLs change, whether other files need to change alongside it
Review¶
- Translation and copy editing: request at least one reviewer who is not the author
- Structural changes such as moves or nav edits: propose on Matrix first, then open the PR
- Images and assets: check alt text, filename, and licensing yourself
Maintainers merge. Contributors, including AI assistants working on a contributor's behalf, do not self-merge, and pages touching security-sensitive material always get a maintainer's technical review.
Issue labels¶
The label scheme, which is still settling:
type:docsdocumentationtype:bugincorrect behaviourtype:enhancementimprovement proposalstype:questiondiscussionarea:zh-TW/area:zh-CN/area:enby languagearea:tools/area:scenariosand the rest by sectiongood first issuefor newcomershelp wantedwhere more hands are needed
Search existing issues before opening a new one.
How translation works¶
zh-TW is the single source of truth. zh-CN and en are derived from it. The full process is in Localization and translation:
- New articles are written in zh-TW first
- zh-CN uses tool-assisted first drafts plus human adjustment for vocabulary differences
- en takes more human work, because the cultural context has to be re-framed rather than converted
- zh-CN and en do not have to ship together with zh-TW. They roll out as people are available
- When reviewing an English page that derives from a zh-TW original, the class of error to look for is named information being replaced by a category term. What goes missing when an English page derives from zh-TW has the test and how to run it
The English site is a rewrite, not a word-for-word translation. A page whose value is entirely in its Chinese-language context does not automatically get an English version, and an English page can carry regional comparisons its Chinese source does not have. Where an upstream English original already exists, as with translated Tor Project, OONI, Tails, and Signal blog posts, the English site links to the original instead of translating it back.
Working with AI tools¶
We do not restrict which AI service contributors use for writing, translation, or code. So that different people with different tools produce consistent work, the rules live in one place, this handbook, and every AI configuration file points back here.
Entry files¶
AGENTS.mdat the repository root covers the repository layout, development commands, and the places where things tend to go wrong. Most AI tools read it automatically.pulse/has its own.CLAUDE.mdimportsAGENTS.mdand adds notes on the subagents and skill under.claude/.- If your tool does not read either file on its own, give it
AGENTS.mdand the "Writing style" part of this handbook before you start.
Checking AI output¶
AI output goes through the same process as anything written by hand: run docs_style_lint.py, work through the pull request template, and go through review. The limits in "Writing about security and privacy" apply in the same way. Open and check every figure, quotation, and source link an AI gives you; the person who opens the pull request is responsible for its content.
Roles¶
Writing an article can be split across a few roles, each doing one job. Contributors using Claude Code can call the matching subagent under .claude/agents/; with other tools, give the instructions below. Every role assumes the same readers: journalists, civil society groups, and open-source communities, with specialists in Tor, OONI, and digital rights reading too.
Three roles before writing:
- Topic scout: scans a given period (the past two weeks by default) for new events in anonymity tools, digital identity and eID, surveillance and censorship legislation, censorship measurement, payment privacy, whistleblowing and leak platforms, and digital rights in Taiwan and the Asia-Pacific, and drops hype, plain product launches, and unrelated items. For each candidate it reports the event in one line, the date, a primary-source link, why the community should cover it, any Taiwan or Asia-Pacific angle, and how time-sensitive it is, ranked by how worth writing it is, six at most. It does not write the article or choose the angle.
- Angle adviser: proposes three or four angles on one topic, saying for each what it approaches from, what the community can add, and whether there is a Taiwan or Asia-Pacific hook, in a suggested order. It offers options only; it does not decide for the author or start writing.
- Research: once a topic is chosen, gathers primary sources (official announcements, original documents, authoritative reporting) with links and publication dates, scans
docs/zh-TW/blog/posts/for existing articles worth cross-linking with their relative paths, and marks the claims that will need a citation. If the site already has a very similar article, it says so. It does not write the article or choose the angle.
Four roles for review:
- Structure review: reads the whole piece, states its core argument and intended audience in one sentence, then checks whether each section serves that line, which should move, which can go, and where transitions are missing. It reports the reading of the argument, structural problems ordered by severity with their locations, and suggested changes. It does not edit sentences.
- Line editing: finds redundant words, vague phrasing, and logical jumps, checks that the tone stays consistent, and in bilingual documents confirms both versions say the same thing. For each problem it gives the location, the issue, and a rewrite, sentence by sentence, without rewriting whole paragraphs.
- Fact check: lists every checkable claim, including figures, dates, country cases, technical descriptions, and descriptions of projects or organisations, rates each as accurate, needs a source, possibly wrong, or overstated, and verifies the possibly wrong ones. For each problem it gives the location and claim, the rating, the evidence or source link, and a suggested fix (change the figure, add a source, soften the claim, or cut it). It does not polish prose or comment on style.
- Target reader: plays a reader who does not know the subject but will spend five minutes on the piece, and marks which sentences lose them, which arguments do not persuade, and what they remember and want to do afterwards. It gives reader reactions only, not editing advice.
Where to look before asking¶
| Question | Page |
|---|---|
| How do I pick something to work on? | How to contribute |
| How do I get a Matrix account? | Community services |
| What suits my level? | Skill level self-assessment |
| How do I set up the development environment? | Development environment setup |
| What are the translation rules? | Localization and translation |
If none of those answer it, ask on Matrix. Include what you are trying to do, what you have already tried, and where you are stuck.
Code of conduct, in brief¶
The community works on openness, mutual support, and staying within the law. This is the short version. The full text, including role definitions, decision-making, and dispute handling, is in the governance charter, which takes precedence where the two differ.
- Mutual respect: members get the same treatment regardless of background or familiarity with the subject
- Argue the issue, not the person
- Lawful purposes: all discussion and collaboration presumes lawful use. We do not assist money laundering, tax evasion, harassment, stalking, or unauthorized intrusion
- Disclosure: anything involving personal data or sensitive material goes through Sending us sensitive material
- Disputes: raise it on Matrix first. Without consensus there, it goes to the next community sync
Conduct that breaches these gets handled by core members under the governance charter.
This handbook is a living document¶
If you hit something this page does not cover, or find a process that turns out to be under-documented, propose a change. Editing the contributor handbook is itself a good first issue.