Thank you for considering contributing to CannaGuide 2025! We welcome contributions from the community — whether it's fixing bugs, adding features, improving translations, or enhancing documentation.
- Code of Conduct
- Getting Started
- Development Setup
- Branch Strategy
- Commit Convention
- Code Style
- Testing
- Internationalization (i18n)
- Cursor MDC Governance
- Pull Request Process
- Deprecation Strategy
- Reporting Issues
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior by opening an issue.
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/<your-username>/CannaGuide-2025.git cd CannaGuide-2025
- Install dependencies:
pnpm install
- Start the development server:
pnpm run dev
- Node.js >= 24
- pnpm >= 10 (via Corepack:
corepack enable)
| Category | Technology |
|---|---|
| Frontend | React 19 + TypeScript |
| State | Redux Toolkit + RTK Query |
| AI | Google Gemini API (@google/genai) |
| Build | Vite 7 + vite-plugin-pwa |
| Styling | Tailwind CSS |
| Testing | Vitest + Playwright |
| i18n | i18next + react-i18next |
No environment variables are required for local development. AI features require a Gemini API key configured in Settings → General & UI → AI Security within the running app.
main— Production-ready code. All PRs target this branch.feature/<name>— New features (e.g.,feature/nutrient-scheduler).fix/<name>— Bug fixes (e.g.,fix/vpd-gauge-overflow).docs/<name>— Documentation changes.refactor/<name>— Code refactoring without behavior changes.
git checkout -b feature/my-new-featureDirect pushes to main are blocked (enforce_admins: true). All changes go through Pull Requests:
# 1. Commit your changes on main (signed)
git add -A && git commit -S -m "feat(scope): description"
# 2. Push via automated PR workflow
pnpm run pr:push # auto-generated branch name
pnpm run pr:push -- "feat/my-feature" # explicit branch nameThe pr:push script (scripts/github/pr-push.mjs) automates: branch creation → push → PR → auto-merge (squash) → CI wait → cleanup. PRs are merge-gated by CI checks (quality + ci-status) and signed commits.
This repository uses a modular Cursor rule architecture under .cursor/rules/*.mdc.
- Global manifest:
.cursor/index.mdc - Detailed governance:
docs/cursor-mdc-governance.md - Session memory stream:
.notes/meeting_notes.md(local only, ignored by Git)
When adding/updating rules, follow the naming ranges and frontmatter constraints documented in the governance file.
- MCP (Graphify + GitKraken):
.cursor/mcp.json— Graphify vianode scripts/graphify-mcp-launcher.mjs(requiresuv), GitKraken viagk mcp(requiresgk auth login). Diagnose:pnpm run mcp:doctor. Legacy launchers:scripts/graphify-mcp-stdio.sh,scripts/graphify-mcp-stdio-windows.cmd. - Codespaces / Dev Container:
.devcontainer/Dockerfileinstallsuvinto the image (/usr/local/bin/uv) so Graphify-MCP works without extra setup. - Team reference:
cursor_settings.json(root) documents the same MCP block plus optionalprojectContextpaths.
Maintainers: prefer pnpm run pr:push for substantive merges so CI gates apply; reserve direct git push origin main for emergencies or documented exceptions.
We follow Conventional Commits:
<type>(<scope>): <description>
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
refactor |
Code change that neither fixes a bug nor adds a feature |
test |
Adding or updating tests |
perf |
Performance improvement |
chore |
Build process, tooling, or dependency updates |
a11y |
Accessibility improvements |
i18n |
Internationalization changes |
Common scopes: ai, plants, strains, equipment, knowledge, settings, help, genealogy, pwa, ci, security, ui.
feat(strains): add THC/CBD ratio filter
fix(plants): prevent VPD gauge overflow on mobile
i18n(help): add French translations for FAQ
test(ai): add rate limiter unit tests
- TypeScript strict mode — no
anytypes, no@ts-expect-error. - ESLint 9 flat config — run
pnpm run lintbefore committing. - Tailwind CSS — use utility classes, avoid custom CSS where possible.
- Functional components with hooks — no class components.
- Named exports — prefer named over default exports.
- Memoization — use
React.memo()for list items and expensive components withdisplayName.
# Check for lint errors
pnpm run lint
# Type-check without emitting
pnpm exec tsc --noEmit- All
dangerouslySetInnerHTMLcontent must be sanitized with DOMPurify. - All external links must use
rel="noopener noreferrer". - All AI API calls must go through
geminiService.ts(or the provider abstraction). - No
console.login production code. Useconsole.debug(stripped in builds) orconsole.warn/console.errorfor legitimate diagnostics.
- All third-party GitHub Actions must be pinned to a full 40-character commit SHA. No mutable tags (
@v4,@latest). - All Dockerfile
FROMdirectives must include an@sha256:digest. - New third-party actions require adding the owner to the repository's Actions allowlist (Settings > Actions > General).
- Compromised tools are removed immediately and replaced with vetted alternatives (e.g., Trivy -> Grype).
- See
SECURITY.mdfor the full policy and rationale (including transitive pnpm overrides for Dependabot-driven fixes).
CannaGuide builds a Tauri v2 Desktop app alongside the PWA, sharing 99% of the codebase.
Desktop features require explicit capability permissions in apps/desktop/src-tauri/capabilities/. Each capability file grants specific permissions:
| File | Purpose |
|---|---|
core.json |
Window/event management |
desktop.json |
Tray, shell, process |
fs.json |
File system with scopes |
dialog.json |
Native file dialogs |
notification.json |
Native notifications |
tray.json |
System tray menu |
shortcut.json |
Global keyboard shortcuts |
updater.json |
Auto-updates |
window-state.json |
Window persistence |
store.json |
Key-value settings |
- Never add wildcard permissions (
*:all-*). Use explicit allows. - Always define FS scopes when accessing files. Restrict to
$APPDATA/cannaguide/**. - IPC commands must be registered in
lib.rsand exposed viainvoke_handler. - Platform detection uses
platformService.ts(isTauri,isPwa,isBrowser). - Native APIs are lazy-loaded to avoid bundling Tauri in web builds.
- Identify required plugin (e.g.,
tauri-plugin-clipboard-manager). - Add to
Cargo.tomlandpackage.json. - Initialize plugin in
lib.rsvia.plugin(). - Create capability file in
capabilities/with minimal permissions. - Add capability to
tauri.conf.jsoncapabilitiesarray. - Create TypeScript service wrapper with platform detection.
- Document in ADR-0012.
See ADR-0012 for the full architecture decision.
We use Vitest for unit/integration tests and Playwright for E2E tests.
# Run all unit/integration tests
pnpm test
# Run tests in watch mode
pnpm exec vitest --watch
# Run E2E tests (requires build)
pnpm run build && pnpm exec playwright test- New features must include tests.
- Bug fixes should include a regression test.
- Test files live next to their source:
MyComponent.test.tsxor intests/. - Use existing mocks from
tests/mocks/for Gemini, IndexedDB, etc. - Current baseline: 2140 tests, 0 failures (185 test files).
All user-facing strings must be localized. We support English (EN), German (DE), Spanish (ES), French (FR), and Dutch (NL).
- Add your key to the appropriate namespace in
locales/en/<namespace>.ts. - Add the translations in
locales/de/,locales/es/,locales/fr/, andlocales/nl/. - Use the key in your component:
const { t } = useTranslation('plants') return <p>{t('myNewKey')}</p>
common, plants, knowledge, strains, equipment, settings, help, commandPalette, onboarding, seedbanks, strainsData, legal.
Use getT() from i18n.ts for services and middleware:
import { getT } from '../i18n'
const t = getT()
console.debug(t('common:error.generic'))- Open an issue first to discuss significant changes.
- Ensure your branch is up to date with
main. - Run the full check suite:
pnpm exec tsc --noEmit && pnpm run lint && pnpm test
- Write a clear PR description explaining what changed and why.
- Link the related issue (e.g.,
Closes #42). - Wait for CI checks to pass.
- A maintainer will review your PR. Be open to feedback!
- TypeScript compiles with 0 errors (
pnpm exec tsc --noEmit) - ESLint passes with 0 warnings (
pnpm run lint) - All existing tests pass (
pnpm test) - New features include tests
- Translations added for all 5 languages (EN/DE/ES/FR/NL)
- No
console.logstatements - No
anytypes - DOMPurify used for any HTML rendering
Use the GitHub Issues tab. We provide templates for:
- 🐛 Bug Reports — Describe the problem, steps to reproduce, expected vs. actual behavior.
- ✨ Feature Requests — Describe the feature, use case, and proposed solution.
- 🔒 Security Reports — Follow
SECURITY.mdfor responsible disclosure. - 📄 Documentation — Report inaccurate or missing documentation.
- 🌍 Translation — Suggest translation improvements or new language support.
- ♿ Accessibility — Report accessibility barriers or WCAG compliance issues.
| Label | Description |
|---|---|
bug |
Something isn't working |
enhancement |
New feature or request |
security |
Security vulnerability |
documentation |
Documentation improvements |
a11y |
Accessibility improvements |
i18n |
Translation & localization |
good first issue |
Good for newcomers |
help wanted |
Extra attention needed |
performance |
Performance improvements |
ai |
AI/Gemini integration |
pwa |
PWA & offline behavior |
When proposing significant changes, please follow these guidelines:
- State management: Domain/persistent data in Redux, ephemeral UI state is transient.
- AI calls: All AI API calls go through the provider abstraction in
services/. - Security: All user-facing HTML must use DOMPurify. All external links need
rel="noopener noreferrer". - Error tracking: Runtime errors are captured by Sentry. Use
Sentry.captureException()for explicit error reporting. - Testing: Component tests use Playwright (
tests/ct/), unit tests use Vitest, E2E tests use Playwright (tests/e2e/).
When removing or replacing a public API, component, or feature, follow this process to give dependent code a migration window:
- Add a
@deprecatedJSDoc tag with a migration hint:/** * @deprecated Use `aiFacade.aiService.getPlantAdvice()` instead. * Scheduled for removal in v1.6. */ export function legacyGetAdvice(plantId: string): Promise<string> { ... }
- Add a runtime
console.warnon first invocation so developers notice during testing:let warned = false export function legacyGetAdvice(plantId: string): Promise<string> { if (!warned) { console.warn('[Deprecated] legacyGetAdvice -- use aiFacade.aiService.getPlantAdvice()') warned = true } // ... }
- Add a
deprecatedlabel to the corresponding GitHub Issue (if any).
| Phase | Duration | Action |
|---|---|---|
| Announce | Current release | Add @deprecated tag + runtime warning |
| Grace period | 1 minor release cycle | Keep functional, emit warning |
| Removal | Next minor release | Delete code, update CHANGELOG |
- Never remove a public export without a prior deprecation phase.
- Deprecated code must still pass typecheck and tests until removal.
- Document the replacement in the
@deprecatedtag body (not just "deprecated"). - Update
CHANGELOG.mdwhen deprecating (under### Deprecated) and when removing (under### Removed).
- Maintainer bumps version in
package.json. - Generate CHANGELOG:
pnpm run changelog:latest(orpnpm run changelogfor full rebuild). - Create a tag:
git tag v1.x.0 && git push --tags. - GitHub Actions automatically:
- Deploys to GitHub Pages, Vercel, and Cloudflare Pages (Netlify paused).
- Create a GitHub Release with the CHANGELOG excerpt.
Looking for a place to start? Here are some beginner-friendly task areas:
| Area | Description |
|---|---|
| Translations | Add or improve ES/FR/NL translations in apps/web/locales/ (EN/DE are the reference) |
| Component Tests | Add Playwright component tests in tests/ct/ -- only Button, Card, Input covered so far |
| Strain Data | Enrich strain entries with missing terpene or flavonoid data in apps/web/data/strains/ |
| Accessibility | Improve ARIA labels, keyboard nav, or screen reader support in UI components |
| Documentation | Fix typos, improve README sections, or add JSDoc to public service functions |
| Theme Variants | Create new cannabis theme CSS in packages/ui/src/tokens.css (9 themes exist) |
Look for issues labeled good first issue or help wanted on the Issues page.
Every contribution — big or small — makes CannaGuide better for the community. 🌿