diff --git a/.vale.ini b/.vale.ini index 777427cf..d0aebc70 100644 --- a/.vale.ini +++ b/.vale.ini @@ -4,8 +4,21 @@ StylesPath = .styles MinAlertLevel = warning Packages = Google, write-good +# Frcsoftware.Spelling (below) is our spellcheck rule; it replaces Vale's +# built-in Vale.Spelling so we control the dictionary in use. The 'Vale' +# style itself must stay in BasedOnStyles since it's what makes the Vocab +# below (and Vale.Terms/Vale.Avoid) take effect. +# +# To allow a word through spellcheck, or to enforce that a term always +# appears with a specific casing (e.g. "WPILib", not "wpilib"), add it to +# vale-accept-words.txt or src/data/glossary.ts (see setup-vale.ts). +Vocab = Frcsoftware + [*.mdx] -BasedOnStyles = Google, write-good +BasedOnStyles = Vale, Google, write-good, Frcsoftware + +Vale.Spelling = NO +Frcsoftware.Spelling = YES # Google style - selectively enable useful rules Google.GenderBias = error diff --git a/scripts/setup-vale.ts b/scripts/setup-vale.ts index f496e64f..312c681c 100644 --- a/scripts/setup-vale.ts +++ b/scripts/setup-vale.ts @@ -1,28 +1,28 @@ -import { createWriteStream, writeFileSync, existsSync, mkdirSync } from 'fs'; -import { resolve, dirname } from 'path'; +import { + createWriteStream, + writeFileSync, + readFileSync, + copyFileSync, + existsSync, + mkdirSync, +} from 'fs'; +import { resolve } from 'path'; import { fileURLToPath } from 'url'; import { glossaryTerms } from '../src/data/glossary'; import { pipeline } from 'stream/promises'; -// Update glossary terms const ROOT = fileURLToPath(new URL('..', import.meta.url)); -const OUTPUT = resolve(ROOT, '.styles/config/ignore/glossary.txt'); -const OUTPUT_DIR = dirname(OUTPUT); -if (!existsSync(OUTPUT_DIR)) { - mkdirSync(OUTPUT_DIR, { recursive: true }); -} - -const terms = [...new Set(glossaryTerms.map(({ term }) => term))].sort((a, b) => - a.toLowerCase().localeCompare(b.toLowerCase()), -); +const DICT_DIR = resolve(ROOT, '.styles/config/dictionaries'); +const DIC_PATH = resolve(DICT_DIR, 'en_US.dic'); +const AFF_PATH = resolve(DICT_DIR, 'en_US.aff'); -const content = terms.join('\n') + '\n'; -writeFileSync(OUTPUT, content); +const ACCEPT_WORDS_PATH = resolve(ROOT, 'vale-accept-words.txt'); +const SPELLING_RULE_PATH = resolve(ROOT, 'scripts/vale-spelling-rule.yml'); -console.log(`Wrote ${terms.length} glossary terms to ${OUTPUT}.`); +const VOCAB_DIR = resolve(ROOT, '.styles/config/vocabularies/Frcsoftware'); +const STYLE_DIR = resolve(ROOT, '.styles/Frcsoftware'); -// If not already present, download dictionary async function downloadFile(url: string, path: string) { const response = await fetch(url); if (!response.ok) @@ -38,20 +38,81 @@ async function downloadFile(url: string, path: string) { await pipeline(response.body, fileStream); } -const dictsToDownload = [ - { - path: resolve(ROOT, '.styles/config/dictionaries/en_US.dic'), - url: 'https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/libreoffice-26.2.5.1/en/en_US.dic', - }, - { - path: resolve(ROOT, '.styles/config/dictionaries/en_US.aff'), - url: 'https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/libreoffice-26.2.5.1/en/en_US.aff', - }, -]; - -dictsToDownload.forEach((dict) => { - if (!existsSync(dict.path)) { - mkdirSync(dirname(dict.path), { recursive: true }); - downloadFile(dict.url, dict.path); +// Vale's spelling check needs a Hunspell dictionary; we pin a specific +// LibreOffice release rather than relying on whatever Vale bundles. +async function ensureDictionaries() { + if (existsSync(DIC_PATH) && existsSync(AFF_PATH)) return; + + mkdirSync(DICT_DIR, { recursive: true }); + const version = 'libreoffice-26.2.5.1'; + await Promise.all([ + downloadFile( + `https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/${version}/en/en_US.dic`, + DIC_PATH, + ), + downloadFile( + `https://raw.githubusercontent.com/LibreOffice/dictionaries/refs/tags/${version}/en/en_US.aff`, + AFF_PATH, + ), + ]); +} + +// Hunspell .dic files are "\n/\n...". +function loadDictionaryWords(): Set { + const [, ...lines] = readFileSync(DIC_PATH, 'utf-8').split('\n'); + const words = new Set(); + for (const line of lines) { + const word = (line.split('/')[0] ?? '').trim(); + if (word) words.add(word.toLowerCase()); } -}); + return words; +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +// Builds Vale's Vocab accept list (`.styles/config/vocabularies/Frcsoftware/accept.txt`) +// from two committed sources: +// - vale-accept-words.txt: plain jargon words, always accepted regardless of casing. +// - src/data/glossary.ts: terms with tooltip definitions. A term marked +// `caseSensitive: true` is enforced with its exact casing (e.g. "WPILib"), +// UNLESS its lowercase form is itself a real English word (e.g. "CAN"), +// in which case enforcing casing would flag ordinary prose ("can you...") +// as an error, so it falls back to case-insensitive acceptance. +function buildAcceptEntries(dictionaryWords: Set): string[] { + const entries = new Set(); + + for (const { term, caseSensitive } of glossaryTerms) { + const enforceCase = + caseSensitive && !dictionaryWords.has(term.toLowerCase()); + entries.add( + enforceCase ? escapeRegExp(term) : `(?i)${escapeRegExp(term)}`, + ); + } + + const customWords = readFileSync(ACCEPT_WORDS_PATH, 'utf-8') + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith('#')); + for (const word of customWords) { + entries.add(`(?i)${escapeRegExp(word)}`); + } + + return [...entries].sort((a, b) => + a.toLowerCase().localeCompare(b.toLowerCase()), + ); +} + +await ensureDictionaries(); + +const acceptEntries = buildAcceptEntries(loadDictionaryWords()); +mkdirSync(VOCAB_DIR, { recursive: true }); +writeFileSync( + resolve(VOCAB_DIR, 'accept.txt'), + acceptEntries.join('\n') + '\n', +); +console.log(`Wrote ${acceptEntries.length} accepted terms to Vale vocabulary.`); + +mkdirSync(STYLE_DIR, { recursive: true }); +copyFileSync(SPELLING_RULE_PATH, resolve(STYLE_DIR, 'Spelling.yml')); diff --git a/scripts/vale-spelling-rule.yml b/scripts/vale-spelling-rule.yml new file mode 100644 index 00000000..5a28cfd9 --- /dev/null +++ b/scripts/vale-spelling-rule.yml @@ -0,0 +1,5 @@ +extends: spelling +message: "Did you really mean '%s'?" +level: warning +dictionaries: + - en_US diff --git a/src/content/docs/best-practices/git-usage.mdx b/src/content/docs/best-practices/git-usage.mdx index ccb17e53..6379a9a9 100644 --- a/src/content/docs/best-practices/git-usage.mdx +++ b/src/content/docs/best-practices/git-usage.mdx @@ -35,7 +35,7 @@ For advanced users, the command line offers more capabilities. The `main` branch is where the working, tested version of the code lives during the build season. When multiple programmers are working on different changes, creating separate development branches can help prevent merge conflicts. -For example, creating a seprate branch for vision code ensures that the code on the drivetrain branch isn't affected. +For example, creating a separate branch for vision code ensures that the code on the drivetrain branch isn't affected. Making branches for each competition helps isolate fixes and ensures that code is still reviewed before merging to `main`. To maintain a branch, you must stay up to date with `main`. diff --git a/src/content/docs/contribution/methodsOfContributing.mdx b/src/content/docs/contribution/methodsOfContributing.mdx index 83b13f0c..a422eebc 100644 --- a/src/content/docs/contribution/methodsOfContributing.mdx +++ b/src/content/docs/contribution/methodsOfContributing.mdx @@ -89,7 +89,7 @@ You can get a local hosted version of the website to have a live preview of the 1. Open the repository in VS Code (doesn't matter which branch) 2. Toggle the bottom panel on if there isn't one with the shortcut `Ctrl + J` -3. Click the dropdown next to the + on the top righthand side of the bottom panel and click "Terminal" +3. Click the dropdown next to the + on the top right-hand side of the bottom panel and click "Terminal" 4. Run the command `pnpm install` to install all needed packages (FIRST TIME) 5. Run the command `pnpm dev` to start the development server 6. If everything went smoothly it should say its serving on something like `http://localhost:4321` diff --git a/src/content/docs/learning-course/getting-started/required-tools.mdx b/src/content/docs/learning-course/getting-started/required-tools.mdx index 9197edd8..4a8161c7 100644 --- a/src/content/docs/learning-course/getting-started/required-tools.mdx +++ b/src/content/docs/learning-course/getting-started/required-tools.mdx @@ -22,7 +22,7 @@ You can find the download link [here](https://github.com/wpilibsuite/allwpilib/r tools and not 2026 WPILib tools. -Once you have downloaded the 2027 WPILib tools, you can follow the instructions on how to set up the WPILib tools here [here](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html). +Once you have downloaded the 2027 WPILib tools, you can follow the instructions on how to set up the WPILib tools [here](https://docs.wpilib.org/en/stable/docs/zero-to-robot/step-2/wpilib-setup.html). The WPILib tool package also includes different programs that are useful for data logging, simulation, dashboards, and more. We will use some of these tools in later stages. diff --git a/src/content/docs/learning-course/getting-started/vscode-overview.mdx b/src/content/docs/learning-course/getting-started/vscode-overview.mdx index 595db3b3..730bf703 100644 --- a/src/content/docs/learning-course/getting-started/vscode-overview.mdx +++ b/src/content/docs/learning-course/getting-started/vscode-overview.mdx @@ -39,7 +39,7 @@ VS Code's layout is categorized into a few key regions: The editor is star of the show and it is where you can view and edit files. - Next, the **Panel** is at the bottom of the screen. It has four main views, which are the Terminal, Problems, Output and Debug Console. -- Finally, the **Command Palette** is accesed through `Ctrl+Shift+P` / `Cmd+Shift+P` and it allows for you to search and run any command in VS Code. +- Finally, the **Command Palette** is accessed through `Ctrl+Shift+P` / `Cmd+Shift+P` and it allows for you to search and run any command in VS Code. In the human case, your arms and legs would be mechanisms, but your ears diff --git a/src/content/docs/resources/glossary.mdx b/src/content/docs/resources/glossary.mdx index b0be56dd..5becc05b 100644 --- a/src/content/docs/resources/glossary.mdx +++ b/src/content/docs/resources/glossary.mdx @@ -4,9 +4,13 @@ title: Glossary {/* This file is auto-generated by scripts/generate-glossary.ts. Do not edit directly. */} +**AprilTag** + +A fiducial marker (similar to a QR code) that WPILib and vendor vision tools can detect to determine the position and orientation of a camera relative to the tag + **CAN** -Controller Area Network: typically yellow and green cable used to communicate with motor controllers and sensors, can be run in various topographies instead of each cable needing to connect to SystemCore +Controller Area Network: typically yellow and green cable used to communicate with motor controllers and sensors, can be run in various topographies instead of each cable needing to connect to Systemcore **CI** @@ -36,7 +40,7 @@ Power Distribution Hub **PWM** -Pulse Width Modulation: A communication spec used to communicate with motor controllers and sensors, needs to connect back to SystemCore +Pulse Width Modulation: A communication spec used to communicate with motor controllers and sensors, needs to connect back to Systemcore **Repository** @@ -47,7 +51,7 @@ Repositories are just folders that contain files and subfolders, and they can be Motor controller for REV motors -**SystemCore** +**Systemcore** Main processor for robot code, contains various IO diff --git a/src/data/glossary.ts b/src/data/glossary.ts index 7f7f90b5..5d8efe69 100644 --- a/src/data/glossary.ts +++ b/src/data/glossary.ts @@ -14,7 +14,7 @@ export interface GlossaryTerm { /** * The word/abbreviation to match (case-insensitive) - * @example "SystemCore" + * @example "Systemcore" */ term: string; /** @@ -44,19 +44,20 @@ export const glossaryTerms: GlossaryTerm[] = [ definition: 'Motor controller for CTRE motors', }, { - term: 'SystemCore', + term: 'Systemcore', definition: 'Main processor for robot code, contains various IO', + caseSensitive: true, }, { term: 'CAN', definition: - 'Controller Area Network: typically yellow and green cable used to communicate with motor controllers and sensors, can be run in various topographies instead of each cable needing to connect to SystemCore', + 'Controller Area Network: typically yellow and green cable used to communicate with motor controllers and sensors, can be run in various topographies instead of each cable needing to connect to Systemcore', caseSensitive: true, }, { term: 'PWM', definition: - 'Pulse Width Modulation: A communication spec used to communicate with motor controllers and sensors, needs to connect back to SystemCore', + 'Pulse Width Modulation: A communication spec used to communicate with motor controllers and sensors, needs to connect back to Systemcore', }, { term: 'Main Breaker', @@ -93,6 +94,13 @@ export const glossaryTerms: GlossaryTerm[] = [ term: 'WPILib', definition: 'WPILib is the standard software library and toolsuite provided for teams to write, test, and debug code for their FIRST® Robotics Competition and FIRST® Tech Challenge robots', + caseSensitive: true, + }, + { + term: 'AprilTag', + definition: + 'A fiducial marker (similar to a QR code) that WPILib and vendor vision tools can detect to determine the position and orientation of a camera relative to the tag', + caseSensitive: true, }, { term: 'GitHub', diff --git a/src/plugins/mdast-hast-data.d.ts b/src/plugins/mdast-hast-data.d.ts new file mode 100644 index 00000000..b10f020d --- /dev/null +++ b/src/plugins/mdast-hast-data.d.ts @@ -0,0 +1,10 @@ +import type {} from 'mdast'; + +// mdast-util-to-hast reads these off node.data to control the hast output, +// but doesn't ship a `Data` augmentation that our remark plugins pick up. +declare module 'mdast' { + interface Data { + hName?: string; + hProperties?: Record; + } +} diff --git a/vale-accept-words.txt b/vale-accept-words.txt new file mode 100644 index 00000000..d3474603 --- /dev/null +++ b/vale-accept-words.txt @@ -0,0 +1,53 @@ +# Words accepted by the Vale spellchecker, on top of the en_US dictionary. +# +# For plain software/technical jargon that the dictionary doesn't know about, +# add it here (one per line, case-insensitive, sorted alphabetically). +# +# For a term that should always appear with a specific casing (e.g. +# "WPILib", not "wpilib" or "Wpilib"), add it to `glossaryTerms` in +# src/data/glossary.ts instead, with `caseSensitive: true` — that also +# gives it a tooltip definition on the docs site. +# +# Lines starting with # are ignored. Run `pnpm vale:setup` after editing. + +Assignees +CANBus +ESLint +FRCSoftware +Futureproofing +Gradle +Sith +Spotbugs +VSCode +checkboxes +codebase +drivetrain +dropdown +dropdowns +entrypoint +feedforward +fiducial +filepaths +formatter +formatters +frontmatter +gamepad +kitbot +linter +onTrue +opmode +opmodes +pnpm +rebase +roadmap +roadmaps +squoosh +subfolders +subteam +teleop +teleoperated +toolsuite +tradeoffs +typecheck +usecase +webm