From 62269df8bc387487e4b1cfa4f774224be9cd0e23 Mon Sep 17 00:00:00 2001 From: AJ-Koenig <39343698+AJ-Koenig@users.noreply.github.com> Date: Mon, 17 Aug 2026 21:39:51 +0700 Subject: [PATCH 1/2] Open developer docs contributions (#10) --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/api-docs.yml | 38 +++ .github/ISSUE_TEMPLATE/documentation.yml | 31 +++ .github/PULL_REQUEST_TEMPLATE.md | 11 + .github/workflows/sync-docs-dev-content.yml | 20 +- .github/workflows/validate-content.yml | 19 ++ CODE_OF_CONDUCT.md | 15 ++ CONTRIBUTING.md | 17 ++ README.md | 17 ++ SECURITY.md | 10 + content/docs/contributing.mdx | 40 ++- .../software/contributing/documentation.mdx | 251 +----------------- scripts/validate-content.mjs | 138 ++++++++++ 13 files changed, 351 insertions(+), 257 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/api-docs.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/validate-content.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 scripts/validate-content.mjs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5a41380 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @AJ-Koenig diff --git a/.github/ISSUE_TEMPLATE/api-docs.yml b/.github/ISSUE_TEMPLATE/api-docs.yml new file mode 100644 index 0000000..bc0a40c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/api-docs.yml @@ -0,0 +1,38 @@ +name: API documentation issue +description: Report a problem in a generated API operation page. +title: "API docs: " +labels: + - documentation +body: + - type: input + id: operation + attributes: + label: Operation + description: Include the method, path, or operation ID shown on the page. + validations: + required: true + - type: input + id: page + attributes: + label: Rendered page URL + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual documentation + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected documentation + validations: + required: true + - type: checkboxes + id: safety + attributes: + label: Public information + options: + - label: This report contains no credentials or private customer information. + required: true diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..d844726 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,31 @@ +name: Documentation correction +description: Report documentation that is unclear, incomplete, or incorrect. +title: "Docs: " +labels: + - documentation +body: + - type: input + id: page + attributes: + label: Page URL + placeholder: https://docs.example.com/page + validations: + required: true + - type: textarea + id: problem + attributes: + label: What is wrong? + description: Explain the inaccurate or unclear text. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Suggested correction + - type: checkboxes + id: safety + attributes: + label: Public information + options: + - label: This report contains no credentials or private customer information. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2f87107 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ +## What changed + +Describe the reader-facing correction or addition. + +## Verification + +- [ ] I targeted `main`. +- [ ] I checked the rendered or local preview. +- [ ] Images and local links resolve. +- [ ] I did not include credentials, private information, or generated output. +- [ ] I have the right to contribute this material under the repository's stated licenses. diff --git a/.github/workflows/sync-docs-dev-content.yml b/.github/workflows/sync-docs-dev-content.yml index 67ac1ef..06df560 100644 --- a/.github/workflows/sync-docs-dev-content.yml +++ b/.github/workflows/sync-docs-dev-content.yml @@ -28,24 +28,24 @@ concurrency: jobs: request-import: - if: ${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'Sync ') || !contains(github.event.head_commit.message, '[docs-sync]') }} + if: ${{ github.event_name == 'workflow_dispatch' || !contains(github.event.head_commit.message, '[docs-sync]') }} runs-on: ubuntu-latest timeout-minutes: 5 env: SYNC_BRANCH: ${{ github.event_name == 'push' && github.ref_name || inputs.branch }} steps: - - name: Verify sync credential - env: - DOCS_SYNC_TOKEN: ${{ secrets.DOCS_SYNC_TOKEN }} - run: | - if [ -z "$DOCS_SYNC_TOKEN" ]; then - echo "::error::DOCS_SYNC_TOKEN is required to request a RadApp import." - exit 1 - fi + - name: Create documentation sync token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.RAD_VERSION_CONTROL_APP_ID }} + private-key: ${{ secrets.RAD_VERSION_CONTROL_PRIVATE_KEY }} + owner: researchanddesire + repositories: rad-app - name: Request matching RadApp import env: - GH_TOKEN: ${{ secrets.DOCS_SYNC_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | gh workflow run import-docs-content.yml \ --repo researchanddesire/rad-app \ diff --git a/.github/workflows/validate-content.yml b/.github/workflows/validate-content.yml new file mode 100644 index 0000000..41210d2 --- /dev/null +++ b/.github/workflows/validate-content.yml @@ -0,0 +1,19 @@ +name: Validate Content + +on: + pull_request: + branches: [main, staging] + push: + branches: [main, staging] + +permissions: + contents: read + +jobs: + validate-content: + name: validate-content + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + - run: node scripts/validate-content.mjs diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ec64dce --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,15 @@ +# Code of Conduct + +We are committed to a welcoming, harassment-free community for everyone. + +Be respectful, assume good intent, keep feedback focused on the work, and avoid +publishing private or identifying information. Harassment, threats, +discrimination, sexualized abuse, and deliberate disruption are not accepted. + +Maintainers may edit or remove contributions and restrict participation when +needed to protect the community. Report conduct concerns privately to +support@researchanddesire.com. Reports will be handled as confidentially as +practical. + +This policy follows the principles of the Contributor Covenant 2.1: +https://www.contributor-covenant.org/version/2/1/code_of_conduct/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..12c1edc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + +Developer documentation lives under `content/docs/` as Markdown and MDX. Sidebar order and section titles are controlled by nearby `meta.json` files. + +The easiest path is the pencil beside an editable page title or heading on [dev.researchanddesire.com](https://dev.researchanddesire.com). It opens GitHub's editor on `main` at the exact source line and creates a fork automatically when needed. + +Before opening a pull request: + +- keep the change focused on supported APIs, firmware, protocols, hardware interfaces, or integrations +- name relevant product, hardware, firmware, and schema versions +- keep images beside the pages that use them +- do not add secrets, application code, build output, or private product information +- run `node scripts/validate-content.mjs` when working locally + +Target `main`. A maintainer review and the `validate-content` check are required. Approved content is synchronized into the private renderer automatically. + +Contributions use the repository's documented CC BY-SA and product-specific licensing boundaries. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9140344 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Research and Desire Developer Docs + +This repository is the public, editable content mirror for [RAD Developer Docs](https://dev.researchanddesire.com). Rendered page titles and section headings link to their exact source lines under `content/docs/`. + +## Contribute + +Select a pencil on the rendered site or edit a file under `content/docs/`, then open a focused pull request into `main`. The `validate-content` check verifies frontmatter, metadata, and local assets before maintainer review. + +Individual API operation pages are generated from the Dashboard OpenAPI document and cannot be edited here. Use their **Report an API docs issue** link instead. + +After an approved merge, the dedicated synchronization app mirrors the byte-identical content into the private application repository. The `[docs-sync]` marker prevents reciprocal loops. + +See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete workflow. + +## License + +Cross-product prose is available under CC BY-SA 4.0. Product developer content and source material retain the respective license identified by that product or file. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2456141 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Security Policy + +This repository contains public documentation only. + +Do not open a public issue for a vulnerability, exposed credential, private +customer information, or unsafe product behavior. Email +support@researchanddesire.com with the affected page, impact, and reproduction +details. Do not include secrets in the report. + +Ordinary documentation corrections should use the public pull request workflow. diff --git a/content/docs/contributing.mdx b/content/docs/contributing.mdx index c99a71a..7e5a917 100644 --- a/content/docs/contributing.mdx +++ b/content/docs/contributing.mdx @@ -1,10 +1,46 @@ --- title: Contributing -description: Review and contribute supported R+D technical documentation. +description: Edit R+D documentation on GitHub and submit a reviewed public contribution. --- # Contribute technical documentation -Documentation changes should name the supported product, hardware revision, firmware version, or API schema they describe. Link claims to the implementation or specification used to verify them. +Every editable page title and section heading has a subtle pencil. Selecting it opens the corresponding Markdown or MDX file at the exact line in GitHub's web editor on `main`. + +## Make a quick edit + +1. Select the pencil beside the page title or section you want to improve. +2. Sign in to GitHub if prompted. +3. Edit the highlighted source. If you do not have write access, GitHub creates a fork for you automatically. +4. Select **Propose changes**, describe the improvement, and open a pull request into `main`. +5. Let the public content validation finish and respond to maintainer feedback. + +After approval, automation synchronizes the byte-identical content into the private application repository and the rendered site is deployed through the normal release track. Commits marked `[docs-sync]` are ignored by the reciprocal workflow so the two-way mirror cannot loop. + +## Choose the correct repository + +| Rendered site | Public source | Editable content | +| --------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------- | +| [User Guides](https://docs.researchanddesire.com) | [researchanddesire/simple-docs](https://github.com/researchanddesire/simple-docs) | `content/` | +| [Developer Docs](https://dev.researchanddesire.com) | [researchanddesire/dev-docs](https://github.com/researchanddesire/dev-docs) | `content/docs/` | +| [R+D Assembly](https://ohai.researchanddesire.com) | [researchanddesire/assembly-docs](https://github.com/researchanddesire/assembly-docs) | `content/docs/` | + +The application, Fumadocs renderer, API implementation, deployment settings, and credentials remain in the private application repository. Individual API operation pages are generated from the Dashboard OpenAPI document, so they provide a **Report an API docs issue** link instead of a misleading source-line pencil. + +## Write a useful contribution + +- Name the supported product, hardware revision, firmware version, or API schema when it matters. +- Link technical claims to the implementation or specification used to verify them. +- Keep images beside the page that uses them and verify every relative path. +- Reuse the existing MDX callouts, cards, tabs, accordions, and steps. +- Keep each pull request focused and never include credentials, private product information, or generated build output. + +Small corrections can be completed entirely in GitHub. For larger changes, fork and clone the public repository, edit its `content/` tree, and use the Fumadocs Preview extension for VS Code before opening the pull request. + +## License + +R+D-owned documentation prose and media in these public repositories is available under CC BY-SA 4.0 unless a file or directory says otherwise. Code samples, product source, hardware material, and third-party assets retain their stated licenses. Physical build procedures belong in [R+D Assembly](https://ohai.researchanddesire.com/ossm). Consumer instructions belong in the [User Guides](https://docs.researchanddesire.com/). Keep Developer Docs focused on APIs, firmware, protocols, official hardware interfaces, and integrations. + +GitHub documents the complete [editing and fork-based pull request flow](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). diff --git a/content/docs/ossm/software/contributing/documentation.mdx b/content/docs/ossm/software/contributing/documentation.mdx index 74c4058..142c545 100644 --- a/content/docs/ossm/software/contributing/documentation.mdx +++ b/content/docs/ossm/software/contributing/documentation.mdx @@ -1,251 +1,12 @@ --- -title: "Contributing to Documentation" -description: "Learn how to contribute to the OSSM documentation using Mintlify, MDX, and React snippets" +title: Contributing to OSSM documentation +description: Improve OSSM developer documentation through the public documentation repository. --- -# Contributing to Documentation +# Contribute OSSM documentation -The OSSM documentation is built with [Mintlify](https://www.mintlify.com/docs), a modern documentation platform that makes it easy to create beautiful, interactive docs. +Use the pencil beside any editable page title or section heading to open its exact source line in `researchanddesire/dev-docs`. GitHub will guide you through proposing the change to `main` as a reviewed pull request. - -All changes to the `Documentation/ossm` directory are automatically deployed to [https://www.researchanddesire.com/ossm](https://www.researchanddesire.com/ossm) after merging to the main branch. - +Read the canonical [documentation contribution guide](/contributing) for repository mapping, optional local previews, validation, licensing, and the bidirectional synchronization process. -## Documentation Platform - -This documentation uses **Mintlify** for rendering and deployment. For detailed information about available components, formatting options, and best practices, visit the official Mintlify documentation: - - - Complete guide to Mintlify features, components, and configuration - - -## Local Development Setup - -To preview documentation changes locally before submitting a pull request: - - - -```bash -cd Documentation -``` - - - -```bash -pnpm i -``` - - - -```bash -pnpm mint -``` - -This starts a local development server with hot-reload enabled. - - - -Open your browser and navigate to: - -``` -http://localhost:3333 -``` - - -The preview updates automatically as you edit MDX files. - - - - - -The Mintlify web editor is **not available** for this project. All editing must be done locally using your preferred text editor. - - -### Local Preview - -Once you have the local server running, you'll see the full documentation site with navigation, search, and all Mintlify features: - -![Local documentation preview](./_images/local-preview.webp) - -The preview includes the complete navigation sidebar, showing all sections including the new "Contributing" section under the Software tab. - -## Writing Documentation - -### MDX Format - -All documentation pages use **MDX** (Markdown + JSX), which allows you to combine standard Markdown with React components for rich, interactive content. - -**Example MDX structure:** - -```mdx ---- -title: "Page Title" -description: "Brief description for SEO and previews" ---- - -# Main Heading - -Regular Markdown content works here. - - -You can use Mintlify components like this. - - -## Another Section - -More content here... -``` - -### Image Guidelines - -Images should be **co-located** with the documentation that uses them: - - -```plaintext Structure -ossm/Software/communication/ -├── ble.mdx -├── gpio.mdx -└── _images/ - ├── ble-diagram.png - └── gpio-pinout.jpg -``` - - - -Don't worry about image optimization—images are automatically optimized during the deployment process. - - -## React Snippets - -For interactive examples and demonstrations, you can embed React components using the snippet system. - -### Snippet Location - -All React snippets are stored in: - -``` -Documentation/snippets/ossm/ -``` - -### Snippet Requirements - - - -Snippets must use **pure React** with no external frameworks or build tools. The React version is provided by the Mintlify runtime. - -```jsx -// ✅ Good - Pure React -import { useState } from 'react'; - -export const Counter = () => { - const [count, setCount] = useState(0); - return ; -}; -``` - -```jsx -// ❌ Bad - External dependencies -import { useQuery } from 'react-query'; -import axios from 'axios'; -``` - - - -**TailwindCSS** is included and ready to use for styling. No additional CSS imports needed. - -```jsx -export const StyledComponent = () => { - return ( -
-

Hello

-

Styled with Tailwind

-
- ); -}; -``` -
- - -Each snippet file must have **exactly one** export or declared function. Multiple exports are not supported. - -```jsx -// ✅ Good - Single export -export const MyComponent = () => { - return
Content
; -}; -``` - -```jsx -// ❌ Bad - Multiple exports -export const ComponentA = () =>
A
; -export const ComponentB = () =>
B
; -``` -
-
- -### Example Snippet - -Here's a complete example showing the proper structure: - - -```jsx snippets/ossm/example-snippet.jsx -import { useState } from 'react'; - -export const ExampleSnippet = () => { - const [isActive, setIsActive] = useState(false); - - return ( -
-

- Interactive Example -

- - {isActive && ( -

- The component is now active! -

- )} -
- ); -}; -``` - -```mdx Usage in documentation - -``` -
- -## Development Tools - - - This project includes Cursor AI rules to help you follow documentation best practices automatically while editing. - - -## Next Steps - - - - Explore available components for creating rich documentation - - - - Learn the full contribution workflow for OSSM - - - - Example of well-structured technical documentation - - - - Set up your own copy of the repository to work on - - +OSSM consumer instructions belong in the [User Guides](https://docs.researchanddesire.com/ossm), while physical build procedures belong in [R+D Assembly](https://ohai.researchanddesire.com/ossm). Keep this section focused on supported firmware, protocols, software architecture, and hardware interfaces. diff --git a/scripts/validate-content.mjs b/scripts/validate-content.mjs new file mode 100644 index 0000000..c5eb5a7 --- /dev/null +++ b/scripts/validate-content.mjs @@ -0,0 +1,138 @@ +import { existsSync } from "node:fs"; +import { readdir, readFile } from "node:fs/promises"; +import path from "node:path"; + +const contentRoot = path.resolve(process.argv[2] ?? "content"); +const failures = []; + +async function walk(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const target = path.join(directory, entry.name); + if (entry.isDirectory()) files.push(...(await walk(target))); + else files.push(target); + } + return files; +} + +function fail(file, message) { + failures.push(`${path.relative(process.cwd(), file)}: ${message}`); +} + +function candidateExists(fromFile, rawTarget, allowExtensionless = false) { + const withoutSuffix = rawTarget.split(/[?#]/, 1)[0]; + if (!withoutSuffix) return true; + let decoded; + try { + decoded = decodeURIComponent(withoutSuffix); + } catch { + return false; + } + + // Extensionless links are application routes in Fumadocs and cannot be + // resolved reliably without the private renderer's route manifest. + if (!path.extname(decoded) && !allowExtensionless) return true; + + const target = path.resolve(path.dirname(fromFile), decoded); + const targetRelative = path.relative(contentRoot, target); + if (targetRelative.startsWith("..") || path.isAbsolute(targetRelative)) return false; + return [ + target, + `${target}.md`, + `${target}.mdx`, + path.join(target, "index.md"), + path.join(target, "index.mdx"), + ].some(existsSync); +} + +function validateFrontmatter(file, source) { + if (!source.startsWith("---\n") && !source.startsWith("---\r\n")) { + fail(file, "missing YAML frontmatter"); + return; + } + const lines = source.split(/\r?\n/); + const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---"); + if (closing < 0) { + fail(file, "frontmatter is not closed"); + return; + } + const title = lines + .slice(1, closing) + .map((line) => line.match(/^\s*title\s*:\s*(.*)$/)?.[1]?.trim()) + .find((value) => value !== undefined); + if (!title || /^(?:["']{2}|null|~)$/i.test(title)) { + fail(file, "frontmatter must include a non-empty title"); + } +} + +function validateLinks(file, source) { + const searchable = source.replace(/```[\s\S]*?```/g, ""); + const targets = []; + for (const match of searchable.matchAll(/!?\[[^\]]*\]\((<[^>]+>|[^\s)]+)(?:\s+["'][^"']*["'])?\)/g)) { + targets.push(match[1].replace(/^<|>$/g, "")); + } + for (const match of searchable.matchAll(/\b(?:href|src)=["']([^"']+)["']/g)) { + targets.push(match[1]); + } + for (const match of searchable.matchAll(/^\s*\[[^\]]+\]:\s*(?:<([^>]+)>|(\S+))/gm)) { + targets.push(match[1] ?? match[2]); + } + for (const match of searchable.matchAll(/\bfrom\s+["'](\.[^"']+)["']/g)) { + targets.push(match[1]); + } + + for (const target of targets) { + if ( + /^(?:[a-z][a-z+.-]*:|#|\/)/i.test(target) || + target.includes("{") || + target.includes("}") + ) { + continue; + } + if (!candidateExists(file, target)) fail(file, `missing local target "${target}"`); + } +} + +function validateMeta(file, source) { + let meta; + try { + meta = JSON.parse(source); + } catch (error) { + fail(file, `invalid JSON: ${error instanceof Error ? error.message : String(error)}`); + return; + } + + for (const page of Array.isArray(meta.pages) ? meta.pages : []) { + if ( + typeof page !== "string" || + /^(?:---|\.\.\.)/.test(page) || + page.includes("](") + ) { + continue; + } + if (!candidateExists(file, page, true)) fail(file, `missing page entry "${page}"`); + } +} + +if (!existsSync(contentRoot)) { + console.error(`Content root does not exist: ${contentRoot}`); + process.exit(1); +} + +for (const file of await walk(contentRoot)) { + const source = await readFile(file, "utf8"); + if (/\.mdx?$/.test(file)) { + validateFrontmatter(file, source); + validateLinks(file, source); + } else if (path.basename(file) === "meta.json") { + validateMeta(file, source); + } +} + +if (failures.length > 0) { + console.error(failures.join("\n")); + process.exit(1); +} + +console.log(`Validated documentation content under ${path.relative(process.cwd(), contentRoot)}.`); From 67d2a97d5d4f5568b904977792b43a63e5edcd99 Mon Sep 17 00:00:00 2001 From: "rad-version-control[bot]" Date: Mon, 17 Aug 2026 14:43:57 +0000 Subject: [PATCH 2/2] Sync developer docs from rad-app [docs-sync] Docs-Source-Repo: researchanddesire/rad-app Docs-Source-Branch: staging Docs-Source-SHA: 193e7f8bc121bffc5b9e1dc2f744485ff4ce29eb --- content/docs/contributing.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/docs/contributing.mdx b/content/docs/contributing.mdx index 7e5a917..f8c71fb 100644 --- a/content/docs/contributing.mdx +++ b/content/docs/contributing.mdx @@ -17,6 +17,8 @@ Every editable page title and section heading has a subtle pencil. Selecting it After approval, automation synchronizes the byte-identical content into the private application repository and the rendered site is deployed through the normal release track. Commits marked `[docs-sync]` are ignored by the reciprocal workflow so the two-way mirror cannot loop. +The synchronization jobs compare Git content-tree hashes after every transfer. This keeps the public `main` files byte-identical with the rendered source so an edit link cannot silently drift to the wrong line. + ## Choose the correct repository | Rendered site | Public source | Editable content |