diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md
index ca96de5..9d34cd4 100644
--- a/CHANGELOG.fr.md
+++ b/CHANGELOG.fr.md
@@ -3,6 +3,7 @@
## [Non publié]
### Ajouté
+- **Page Guide des catégories standard** (Paramètres → *Structure standard des catégories*, route `/settings/categories/standard`) : nouvelle page en lecture seule qui expose la taxonomie v1 IPC complète sous forme d'arbre navigable avec repli/expansion par racine, un compteur global en direct (racines · sous-catégories · feuilles · total), une recherche plein texte insensible aux accents sur les noms traduits, des info-bulles au survol affichant la clé `i18n_key`, le type et l'identifiant de chaque nœud, et un bouton *Exporter en PDF* qui ouvre la boîte d'impression du navigateur. Une règle `@media print` dédiée force l'affichage complet de toutes les branches à l'impression, peu importe l'état de repli à l'écran. Tous les libellés passent par `categoriesSeed.*` avec `name` en repli pour les futures lignes personnalisées. Aucune écriture en base, aucune action destructive (#117)
- **Seed de catégories IPC pour les nouveaux profils** : les nouveaux profils sont désormais créés avec la taxonomie v1 IPC (Indice des prix à la consommation) — une hiérarchie alignée sur les catégories de Statistique Canada. Les noms des catégories du seed sont traduits dynamiquement depuis la clé i18n `categoriesSeed.*` (FR/EN), donc affichés dans la langue de l'utilisateur. Les profils existants gardent l'ancien seed v2, marqués via une nouvelle préférence `categories_schema_version` (une page de migration ultérieure offrira le passage v2→v1). Côté interne : colonne `categories.i18n_key` (nullable) ajoutée par la migration v8 (strictement additive), `src/data/categoryTaxonomyV1.json` livré comme source de vérité côté TS, les renderers `CategoryTree` et `CategoryCombobox` utilisent `name` en repli quand aucune clé de traduction n'est présente (catégories créées par l'utilisateur) (#115)
## [0.8.3] - 2026-04-19
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9cd94c..0c7a4e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## [Unreleased]
### Added
+- **Standard categories guide page** (Settings → *Standard category structure*, route `/settings/categories/standard`): new read-only page that exposes the full v1 IPC taxonomy as a navigable tree with expand/collapse per root, a live category counter (roots · subcategories · leaves · total), accent-insensitive full-text search over translated names, hover tooltips showing the `i18n_key` / type / ID of each node, and a *Export as PDF* button that triggers the browser print dialog. A dedicated `@media print` rule forces every branch to render fully expanded regardless of the on-screen collapse state. All labels resolve via `categoriesSeed.*` with `name` as fallback for future custom rows. No database writes, no destructive actions (#117)
- **IPC-aligned categories seed for new profiles**: brand-new profiles are now seeded with the v1 IPC (Indice des prix à la consommation) taxonomy — a structured hierarchy aligned with Statistics Canada consumer price index categories. Category labels are now translated dynamically from the `categoriesSeed.*` i18n namespace (FR/EN), so seed categories display in the user's current language. Existing profiles remain on the legacy v2 seed, marked via a new `categories_schema_version` user preference (a later migration wizard will offer the v2→v1 transition). Internally: nullable `categories.i18n_key` column added in migration v8 (additive only), `src/data/categoryTaxonomyV1.json` bundled as the TS-side source of truth, `CategoryTree` and `CategoryCombobox` renderers fall back to the raw `name` when no translation key is present (user-created rows) (#115)
## [0.8.3] - 2026-04-19
diff --git a/src/App.tsx b/src/App.tsx
index 60e230c..6657d16 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,6 +16,7 @@ import ReportsComparePage from "./pages/ReportsComparePage";
import ReportsCategoryPage from "./pages/ReportsCategoryPage";
import ReportsCartesPage from "./pages/ReportsCartesPage";
import SettingsPage from "./pages/SettingsPage";
+import CategoriesStandardGuidePage from "./pages/CategoriesStandardGuidePage";
import DocsPage from "./pages/DocsPage";
import ChangelogPage from "./pages/ChangelogPage";
import ProfileSelectionPage from "./pages/ProfileSelectionPage";
@@ -112,6 +113,10 @@ export default function App() {
} />
} />
} />
+ }
+ />
} />
} />
diff --git a/src/components/categories/CategoryTaxonomyTree.tsx b/src/components/categories/CategoryTaxonomyTree.tsx
new file mode 100644
index 0000000..aba341e
--- /dev/null
+++ b/src/components/categories/CategoryTaxonomyTree.tsx
@@ -0,0 +1,214 @@
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { ChevronRight, ChevronDown } from "lucide-react";
+import type { TaxonomyNode } from "../../services/categoryTaxonomyService";
+
+interface CategoryTaxonomyTreeProps {
+ nodes: TaxonomyNode[];
+ expanded: Set;
+ onToggle: (id: number) => void;
+ searchQuery: string;
+}
+
+interface NodeRowProps {
+ node: TaxonomyNode;
+ depth: number;
+ expanded: Set;
+ onToggle: (id: number) => void;
+ visibleIds: Set | null;
+}
+
+function NodeRow({
+ node,
+ depth,
+ expanded,
+ onToggle,
+ visibleIds,
+}: NodeRowProps) {
+ const { t } = useTranslation();
+ const label = t(node.i18n_key, { defaultValue: node.name });
+ const hasChildren = node.children.length > 0;
+ const isExpanded = expanded.has(node.id);
+
+ // Filter children by visibility set (search mode) if provided.
+ const visibleChildren = useMemo(() => {
+ if (visibleIds === null) return node.children;
+ return node.children.filter((child) => visibleIds.has(child.id));
+ }, [node.children, visibleIds]);
+
+ const typeLabel =
+ node.type === "income"
+ ? t("categoriesSeed.guidePage.type.income")
+ : node.type === "transfer"
+ ? t("categoriesSeed.guidePage.type.transfer")
+ : t("categoriesSeed.guidePage.type.expense");
+
+ const tooltipText = [
+ `${t("categoriesSeed.guidePage.tooltip.key")}: ${node.i18n_key}`,
+ `${t("categoriesSeed.guidePage.tooltip.type")}: ${typeLabel}`,
+ `${t("categoriesSeed.guidePage.tooltip.id")}: ${node.id}`,
+ ].join("\n");
+
+ // On screen: show children only if expanded.
+ // In print: @media print in styles.css overrides display:none to show everything.
+ const childrenHidden = !isExpanded;
+
+ return (
+
+ );
+}
+
+/**
+ * Build the set of node IDs whose visible subtree matches the query.
+ * A node is kept if its translated name contains the query OR any of its descendants match.
+ */
+export function collectVisibleIds(
+ roots: TaxonomyNode[],
+ normalizedQuery: string,
+ translate: (key: string, fallback: string) => string,
+): Set {
+ const visible = new Set();
+ if (normalizedQuery.length === 0) {
+ const walk = (n: TaxonomyNode) => {
+ visible.add(n.id);
+ n.children.forEach(walk);
+ };
+ roots.forEach(walk);
+ return visible;
+ }
+
+ const walk = (node: TaxonomyNode): boolean => {
+ const label = translate(node.i18n_key, node.name);
+ const selfMatches = normalize(label).includes(normalizedQuery);
+ let anyChildMatches = false;
+ for (const child of node.children) {
+ if (walk(child)) anyChildMatches = true;
+ }
+ if (selfMatches || anyChildMatches) {
+ visible.add(node.id);
+ return true;
+ }
+ return false;
+ };
+
+ roots.forEach(walk);
+ return visible;
+}
+
+// Case-and-accent insensitive normalization for search.
+export function normalize(s: string): string {
+ return s
+ .toLowerCase()
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "");
+}
+
+export default function CategoryTaxonomyTree({
+ nodes,
+ expanded,
+ onToggle,
+ searchQuery,
+}: CategoryTaxonomyTreeProps) {
+ const { t } = useTranslation();
+ const normalizedQuery = normalize(searchQuery.trim());
+
+ const visibleIds = useMemo(() => {
+ if (normalizedQuery.length === 0) return null;
+ return collectVisibleIds(nodes, normalizedQuery, (key, fallback) =>
+ t(key, { defaultValue: fallback }),
+ );
+ }, [nodes, normalizedQuery, t]);
+
+ const visibleRoots =
+ visibleIds === null ? nodes : nodes.filter((r) => visibleIds.has(r.id));
+
+ if (visibleRoots.length === 0) {
+ return (
+
+ {t("categoriesSeed.guidePage.noResults")}
+
+ );
+ }
+
+ return (
+
+ {visibleRoots.map((root) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/settings/CategoriesCard.tsx b/src/components/settings/CategoriesCard.tsx
new file mode 100644
index 0000000..22b504f
--- /dev/null
+++ b/src/components/settings/CategoriesCard.tsx
@@ -0,0 +1,38 @@
+import { Link } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import { FolderTree, ChevronRight } from "lucide-react";
+
+/**
+ * Card that surfaces category-related entries in the Settings page.
+ * For now: a single link to the read-only "standard categories guide"
+ * (Livraison 1 of the IPC category refactor).
+ */
+export default function CategoriesCard() {
+ const { t } = useTranslation();
+ return (
+
+