First off — thank you for being here! Contributions are very much encouraged and genuinely appreciated, whether that's a bug report, a fix, a new or improved Microsoft mapping, a translation tweak, or a documentation improvement. No contribution is too small.
This project is an independent, unofficial visualizer that maps the Flemish GRIP growth path (Groeipad informatieveiligheid en privacy voor het Vlaamse onderwijs) to the Microsoft A3/A5 stack. It is built with React + Vite, tested with Vitest, and deployed as a static site.
- 🐛 Report bugs or unexpected behaviour via issues.
- 🌍 Improve translations (Dutch / English / French).
- 🔗 Add or correct mappings between GRIP measures and Microsoft products/features.
- 📝 Improve docs (this file, the README, in-app copy).
- ✨ Propose features — open an issue first so we can discuss scope.
- UI —
src/components/,src/App.jsx - Mapping data —
src/data/grip.json - Translations / copy —
src/i18n/strings.js - Styles —
src/styles/index.css - Tests —
src/**/*.test.{js,jsx}
For the tech stack, project layout, data model and deployment, see ARCHITECTURE.md.
The quickest path is a Dev Container / GitHub Codespaces (config in .devcontainer/),
which gives you Node and all tooling preinstalled. Otherwise, with Node 24+ locally:
npm install
npm run dev # start the dev server (http://localhost:5173)
npm run build # production build into dist/
npm run preview # preview the production build
npm run lint # eslint
npm run format # prettier (JS/CSS) — markdown is formatted with dprint
npm test # vitestAll UI copy lives in src/i18n/strings.js, organised by language
under the ui object. The project ships three languages: Dutch (nl), English
(en) and French (fr).
- Dutch is the source of truth. The GRIP material is Dutch; non-Dutch strings are translations and should preserve the Dutch meaning. When in doubt, the original Dutch wins.
- The
t(lang, key)helper falls back to Dutch when a key is missing in another language, so always add new keys tonlfirst, thenenandfr. - Aim for clear, concise copy aligned with the Microsoft writing style (see #23).
- Add the language code to the
LANGSarray insrc/i18n/strings.js. - Add a matching block under
uiwith translations for every key (copy thenlblock as a starting point so no keys are missed). - Make sure the language toggle in the header renders and switches correctly.
Measures live in src/data/grip.json. The full schema (every field, the
a5Adds / addOn / standalone badge flags, standards and guidance) is documented in
ARCHITECTURE.md. A measure looks like:
Guidelines when editing mappings:
- Only map to a Microsoft product/feature where a defensible relationship exists — avoid forced or misleading mappings.
- Set
tierto the lowest licence that delivers the capability (A1/A3/A5). - Use
a5Adds: truefor capabilities unlocked specifically by A5 (these feed the A3 vs A5 view). - Use
addOn: truefor capabilities that need a separate paid add-on on top of the base licence (e.g. Microsoft Priva, Entra ID Governance). Addstandalone: truewhen the add-on has no Microsoft 365 base tier (a separate service such as Microsoft Sentinel or Entra Private Access). See ARCHITECTURE.md for how these render. - Set
tierOverride(e.g."A3") on a measure to force its card/chip badge when the core measure is achievable at a lower tier and the higher-tier mappings are only value-adds. - Keep
docsUrlpointing at current, official Microsoft documentation. - The Microsoft mapping is an informative starting point, not licensing advice.
Before opening a PR, please run:
npm run lint
npm run format
npm testThis repo uses lefthook to run the linters, formatters and tests
automatically. Inside the Dev Container / Codespaces the hooks are installed for you by
.devcontainer/post-create.sh. For a local setup:
npm install # installs ESLint, Prettier, Vitest
mise install # installs the pinned CI linters + lefthook (see .mise.toml)
mise exec -- lefthook install # wires up the git hooksWhat runs:
- pre-commit (on staged files) — ESLint
--fix, Prettier, dprint (Markdown), yamlfmt + yamllint, actionlint, zizmor and gitleaks. Auto-fixable changes are re-staged for you. - pre-push —
npm run lintandnpm testacross the whole project.
Run the full pre-commit suite on demand with mise exec -- lefthook run pre-commit. To bypass
hooks in an emergency, use git commit --no-verify (please don't make a habit of it).
- A broken-links checker validates documentation and
docsUrl/sourceUrllinks (see #24).
- Keep PRs small and focused — one logical change per PR.
- Update docs and tests alongside code changes.
- Link the related issue (e.g.
Closes #123). - Use Conventional Commits for commit messages.
- Make sure lint, build and tests pass.
Releases are automated with
release-please: it derives the next
semantic version, updates package.json and CHANGELOG.md, and
creates the Git tag and GitHub Release. This only works if commits follow the
Conventional Commits specification.
Each commit message should be of the form type(optional scope): description, e.g.
feat(i18n): add Spanish translations or fix: correct A5 tier on Conditional Access.
Common types and their effect on the version bump:
| Type | Use for | Version bump |
|---|---|---|
feat: |
A new feature | minor |
fix: |
A bug fix | patch |
docs: |
Documentation-only changes | none |
chore: |
Tooling, deps, housekeeping | none |
ci: |
CI / workflow changes | none |
refactor: |
Code change that neither fixes a bug nor adds a feature | none |
test: |
Adding or fixing tests | none |
perf: |
A performance improvement | patch |
For a breaking change, append ! after the type (e.g. feat!: drop A1 view) or add a
BREAKING CHANGE: footer. While the project is pre-1.0.0, breaking changes bump the
minor version.
- You merge Conventional Commits (e.g.
feat:/fix:) intomain. - release-please opens or updates a
chore(main): release vX.Y.ZPR with the version bump and changelog entries. - Once that PR is reviewed and merged, the tag and GitHub Release are created automatically.
Tip: when a change spans multiple commits, the PR title should also follow the Conventional Commits format, since PRs are typically squash-merged.
Please be kind and constructive in all interactions. By contributing, you agree that your contributions are licensed under the project's MIT License.
Thanks again for helping improve GRIP Visualizer! 💙
{ "code": "O7", // GRIP code (O = Organisatorisch, T = Technisch) "basis": 1, // Basis maturity level 1–6 "type": "O", "horizon": "short", // implementation effort: short | medium | long "title_nl": "...", "title_en": "...", "title_fr": "...", "summary_nl": "...", "summary_en": "...", "summary_fr": "...", "microsoft": [ { "name": "Conditional Access", "tier": "A3", "docsUrl": "https://...", "a5Adds": false } ] }