diff --git a/docs/tutorials/index.mdx b/docs/tutorials/index.mdx
index c393f312..52b80919 100644
--- a/docs/tutorials/index.mdx
+++ b/docs/tutorials/index.mdx
@@ -4,6 +4,7 @@ copyright: This file is part of midnight-docs. Copyright (C) Midnight Foundation
title: Tutorials
description: Step-by-step tutorials for building DApps on Midnight Network, from your first Compact contract to a full-stack deployment with wallet integration.
sidebar_position: 4
+hide_table_of_contents: true
tags:
- tutorials
- dapps
@@ -13,6 +14,8 @@ tags:
---
import PersonaTiles from '@site/src/components/PersonaTiles';
+import CoverageMatrix from '@site/src/components/CoverageMatrix';
+import { columns as tutorialColumns, features as tutorialFeatures } from '@site/src/components/CoverageMatrix/tutorialsData';
# Tutorials
@@ -50,3 +53,30 @@ Step-by-step tutorials for building DApps on Midnight Network. Start with the en
cta: "Build ZK Loan",
},
]} />
+
+## What each tutorial covers
+
+Each tutorial teaches a different slice of the Midnight stack. Pick a feature or
+pattern from the dropdown to see which tutorials teach it, or expand a section
+to compare tutorials row by row. A filled dot means the tutorial teaches the
+feature in depth, an outlined dot means the feature appears only briefly, and an
+empty cell means the tutorial does not cover it. Every dot links to the section
+of the tutorial where the feature is covered.
+
+
+
+:::note
+
+This matrix covers the written tutorials. Coverage data lives in
+`src/components/CoverageMatrix/tutorialsData.jsx`. When a tutorial is added or
+substantially updated, update the matrix there too.
+
+:::
diff --git a/src/components/CoverageMatrix/index.jsx b/src/components/CoverageMatrix/index.jsx
index 7be6adfd..832e01fd 100644
--- a/src/components/CoverageMatrix/index.jsx
+++ b/src/components/CoverageMatrix/index.jsx
@@ -1,24 +1,43 @@
import React, { useMemo, useState } from "react";
import Link from "@docusaurus/Link";
-import { columns as allColumns, features as allFeatures } from "./data";
+import { columns as exampleColumns, features as exampleFeatures } from "./data";
import styles from "./styles.module.css";
-const GROUPS = ["All", "DApps", "Contracts"];
+const DEFAULT_GROUPS = ["All", "DApps", "Contracts"];
-const MARKS = {
- x: { cls: "markFull", label: "Demonstrated" },
- "?": { cls: "markPartial", label: "Partial or in progress" }
-};
+// A coverage cell is either a plain "x" / "?" string, or an object
+// { level: "x" | "?", href: "/path#anchor" } that also deep-links to the place
+// in the docs where the feature is covered.
+function cellOf(feature, columnId) {
+ const entry = feature.coverage[columnId];
+ if (!entry) return null;
+ return typeof entry === "string" ? { level: entry } : entry;
+}
-function CoverageMark({ value }) {
- const mark = MARKS[value] || { cls: "markNone", label: "Not covered" };
+function CoverageMark({ cell, featureName, columnName, fullLabel, partialLabel }) {
+ const mark =
+ cell?.level === "x"
+ ? { cls: "markFull", label: fullLabel }
+ : cell?.level === "?"
+ ? { cls: "markPartial", label: partialLabel }
+ : { cls: "markNone", label: "Not covered" };
+ const dot = ;
+ if (cell?.href) {
+ return (
+
+ {dot}
+
+ );
+ }
return (
-
+
+ {dot}
+
);
}
@@ -44,37 +63,76 @@ function ColumnHeader({ column }) {
);
}
-export default function CoverageMatrix() {
+export default function CoverageMatrix({
+ columns = exampleColumns,
+ features = exampleFeatures,
+ groups = DEFAULT_GROUPS,
+ itemNoun = "example",
+ fullLabel = "Demonstrated",
+ partialLabel = "Partial or in progress",
+ summaryLabel = "Demonstrated by",
+ stretch = false
+}) {
const [group, setGroup] = useState("All");
const [selectedFeature, setSelectedFeature] = useState("all");
+ const [expandedSections, setExpandedSections] = useState(() => new Set());
const visibleColumns = useMemo(
() =>
- group === "All"
- ? allColumns
- : allColumns.filter((c) => c.group === group),
- [group]
+ group === "All" ? columns : columns.filter((c) => c.group === group),
+ [group, columns]
);
- const visibleFeatures = useMemo(
- () =>
- selectedFeature === "all"
- ? allFeatures
- : allFeatures.filter((f) => f.name === selectedFeature),
- [selectedFeature]
- );
+ // Optional `section` on features groups the dropdown into optgroups and turns
+ // the table into collapsible sections. Datasets without sections render as a
+ // flat table, exactly as before.
+ const sections = useMemo(() => {
+ if (!features.some((f) => f.section)) return null;
+ const order = [];
+ const bySection = new Map();
+ features.forEach((f) => {
+ const key = f.section || "Other";
+ if (!bySection.has(key)) {
+ bySection.set(key, []);
+ order.push(key);
+ }
+ bySection.get(key).push(f);
+ });
+ return order.map((name) => ({ name, features: bySection.get(name) }));
+ }, [features]);
+
+ const allExpanded =
+ sections !== null && expandedSections.size === sections.length;
- // Examples that demonstrate the currently selected feature. Computed against
- // every column (not the group-filtered view) so the summary always reflects
+ const toggleSection = (name) => {
+ setExpandedSections((prev) => {
+ const next = new Set(prev);
+ if (next.has(name)) next.delete(name);
+ else next.add(name);
+ return next;
+ });
+ };
+
+ const toggleAllSections = () => {
+ setExpandedSections(
+ allExpanded ? new Set() : new Set(sections.map((s) => s.name))
+ );
+ };
+
+ // Columns that cover the currently selected feature. Computed against every
+ // column (not the group-filtered view) so the summary always reflects
// reality, then we note when the active filter hides some of them.
const matches = useMemo(() => {
if (selectedFeature === "all") return null;
- const feature = allFeatures.find((f) => f.name === selectedFeature);
+ const feature = features.find((f) => f.name === selectedFeature);
if (!feature) return [];
- return allColumns
- .filter((c) => feature.coverage[c.id])
- .map((c) => ({ ...c, level: feature.coverage[c.id] }));
- }, [selectedFeature]);
+ return columns
+ .map((c) => {
+ const cell = cellOf(feature, c.id);
+ return cell ? { ...c, level: cell.level, cellHref: cell.href } : null;
+ })
+ .filter(Boolean);
+ }, [selectedFeature, columns, features]);
// Of the matches, how many are hidden by the current group filter.
const hiddenByFilter = useMemo(() => {
@@ -82,8 +140,127 @@ export default function CoverageMatrix() {
return matches.filter((m) => m.group !== group).length;
}, [matches, group]);
+ const featureOption = (f) => (
+
+ );
+
+ const featureRow = (feature) => (
+
+ |
+ {feature.name}
+ |
+ {visibleColumns.map((c) => (
+
+
+ |
+ ))}
+
+ );
+
+ const sectionLabelRow = (name) => (
+
+ |
+ {name}
+ |
+
+ );
+
+ // Table body: three shapes.
+ // 1. A single feature is selected: show just that row, with a plain section
+ // label above it for context (collapse state is ignored).
+ // 2. Sectioned dataset, no selection: collapsible section rows.
+ // 3. Flat dataset: all rows.
+ let bodyRows;
+ if (selectedFeature !== "all") {
+ const feature = features.find((f) => f.name === selectedFeature);
+ bodyRows = feature
+ ? [
+ feature.section ? sectionLabelRow(feature.section) : null,
+ featureRow(feature)
+ ]
+ : [];
+ } else if (sections) {
+ bodyRows = sections.map((s) => {
+ const expanded = expandedSections.has(s.name);
+ return (
+
+ toggleSection(s.name)}
+ >
+ |
+
+ |
+ {visibleColumns.map((c) => {
+ const covered = s.features.filter((f) =>
+ cellOf(f, c.id)
+ ).length;
+ return (
+
+ {covered > 0 ? (
+
+ {covered}
+
+ ) : (
+
+ )}
+ |
+ );
+ })}
+
+ {expanded ? s.features.map(featureRow) : null}
+
+ );
+ });
+ } else {
+ bodyRows = features.map(featureRow);
+ }
+
return (
-
+
@@ -111,9 +290,9 @@ export default function CoverageMatrix() {
- {GROUPS.map((g) => (
+ {groups.map((g) => (
+
+ {sections && selectedFeature === "all" ? (
+
+ Sections
+
+
+ ) : null}
{matches ? (
{matches.length > 0 ? (
<>
- Demonstrated by
+ {summaryLabel}
- {matches.map((m) =>
- m.href ? (
-
+ {matches.map((m) => {
+ const href = m.cellHref || m.href;
+ return href ? (
+
{m.name}
{m.level === "?" ? partial : null}
@@ -147,8 +340,8 @@ export default function CoverageMatrix() {
{m.name}
{m.level === "?" ? partial : null}
- )
- )}
+ );
+ })}
{hiddenByFilter > 0 ? (
@@ -159,7 +352,7 @@ export default function CoverageMatrix() {
>
) : (
- No documented example covers this yet.
+ No documented {itemNoun} covers this yet.
)}
@@ -178,37 +371,17 @@ export default function CoverageMatrix() {
))}
-
- {visibleFeatures.map((feature) => (
-
- |
- {feature.name}
- |
- {visibleColumns.map((c) => (
-
-
- |
- ))}
-
- ))}
-
+
{bodyRows}
- Demonstrated
+ {fullLabel}
- Partial or in progress
+ {partialLabel}
Not covered
diff --git a/src/components/CoverageMatrix/styles.module.css b/src/components/CoverageMatrix/styles.module.css
index 1e0904ee..b8798d58 100644
--- a/src/components/CoverageMatrix/styles.module.css
+++ b/src/components/CoverageMatrix/styles.module.css
@@ -298,6 +298,200 @@
padding: 0.55rem 0.5rem;
}
+/* Section divider rows (used by datasets that group features into sections) */
+.sectionRow {
+ cursor: pointer;
+}
+
+.sectionRow .sectionHeader {
+ position: sticky;
+ left: 0;
+ z-index: 1;
+ min-width: 17rem;
+ text-align: left;
+ padding: 0;
+ font-size: 0.72rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--ifm-color-emphasis-700);
+ background-color: var(--ifm-color-emphasis-100);
+ border-right: 1px solid var(--cm-line);
+}
+
+.sectionCell {
+ text-align: center;
+ padding: 0.45rem 0.5rem;
+ background-color: var(--ifm-color-emphasis-100);
+}
+
+.sectionRow:hover .sectionHeader,
+.sectionRow:hover .sectionCell {
+ background-color: var(--ifm-color-emphasis-200);
+}
+
+.sectionRowOpen .sectionHeader {
+ box-shadow: inset 3px 0 0 var(--cm-dapp);
+ color: var(--ifm-font-color-base);
+}
+
+/* Plain (non-collapsible) section label, shown above a filtered single row */
+.sectionPlain {
+ padding: 0.55rem 0.85rem;
+}
+
+.sectionToggle {
+ display: flex;
+ align-items: center;
+ gap: 0.55rem;
+ width: 100%;
+ padding: 0.65rem 0.85rem;
+ border: 0;
+ background: transparent;
+ cursor: pointer;
+ font: inherit;
+ text-transform: inherit;
+ letter-spacing: inherit;
+ color: inherit;
+ text-align: left;
+}
+
+.sectionToggle:hover {
+ color: var(--ifm-font-color-base);
+}
+
+/* Per-tutorial coverage counts shown on collapsed section rows */
+.countPill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 1.7rem;
+ padding: 0.12rem 0.45rem;
+ border-radius: 999px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.2;
+}
+
+.countPill.isDapps {
+ color: var(--cm-dapp);
+ background-color: color-mix(in srgb, var(--cm-dapp) 13%, var(--cm-surface));
+ border: 1px solid color-mix(in srgb, var(--cm-dapp) 38%, transparent);
+}
+
+.countPill.isContracts {
+ color: var(--cm-contract);
+ background-color: color-mix(
+ in srgb,
+ var(--cm-contract) 13%,
+ var(--cm-surface)
+ );
+ border: 1px solid color-mix(in srgb, var(--cm-contract) 38%, transparent);
+}
+
+.chevron,
+.chevronOpen {
+ flex: 0 0 auto;
+ width: 0.45rem;
+ height: 0.45rem;
+ border-right: 2px solid currentColor;
+ border-bottom: 2px solid currentColor;
+ transform: rotate(-45deg);
+ transition: transform 0.15s ease;
+}
+
+.chevronOpen {
+ transform: rotate(45deg);
+}
+
+.sectionCount {
+ font-weight: 400;
+ color: var(--ifm-color-emphasis-600);
+}
+
+.expandBtn {
+ padding: 0.6rem 0.95rem;
+ font-size: 0.875rem;
+ font-family: inherit;
+ font-weight: 500;
+ color: var(--ifm-color-emphasis-700);
+ background-color: var(--cm-surface);
+ border: 1px solid var(--ifm-color-emphasis-300);
+ border-radius: 10px;
+ cursor: pointer;
+ transition:
+ border-color 0.15s ease,
+ color 0.15s ease;
+}
+
+.expandBtn:hover {
+ border-color: var(--ifm-color-emphasis-400);
+ color: var(--ifm-font-color-base);
+}
+
+:global([data-theme="dark"]) .sectionRow .sectionHeader,
+:global([data-theme="dark"]) .sectionCell {
+ background-color: var(--ifm-color-emphasis-200);
+}
+
+:global([data-theme="dark"]) .sectionRow:hover .sectionHeader,
+:global([data-theme="dark"]) .sectionRow:hover .sectionCell {
+ background-color: var(--ifm-color-emphasis-300);
+}
+
+:global([data-theme="dark"]) .countPill.isDapps {
+ color: color-mix(in srgb, var(--cm-dapp) 75%, #fff);
+ background-color: color-mix(in srgb, var(--cm-dapp) 22%, var(--cm-surface));
+ border-color: color-mix(in srgb, var(--cm-dapp) 55%, transparent);
+}
+
+:global([data-theme="dark"]) .countPill.isContracts {
+ color: color-mix(in srgb, var(--cm-contract) 75%, #fff);
+ background-color: color-mix(
+ in srgb,
+ var(--cm-contract) 22%,
+ var(--cm-surface)
+ );
+ border-color: color-mix(in srgb, var(--cm-contract) 55%, transparent);
+}
+
+/* Deep-linked coverage marks */
+.markLink {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.15rem;
+ border-radius: 50%;
+ line-height: 0;
+}
+
+.markLink:hover {
+ background-color: var(--ifm-hover-overlay);
+ box-shadow: 0 0 0 2px var(--ifm-color-emphasis-300);
+}
+
+/* Stretch mode: fill the content column (used on full-width pages).
+ min-width guards against any competing width rule; the first column gets
+ width 100% so it absorbs the surplus and the mark columns stay compact. */
+.stretch .tableCard,
+.stretch .tableScroll,
+.stretch .table {
+ width: 100%;
+ min-width: 100%;
+}
+
+.stretch .cornerHeader,
+.stretch .rowHeader,
+.stretch .sectionHeader {
+ width: 100%;
+}
+
+.stretch .colHeader,
+.stretch .cell,
+.stretch .sectionCell {
+ min-width: 7.5rem;
+}
+
/* Zebra + hover */
.table tbody tr:nth-child(even) .rowHeader,
.table tbody tr:nth-child(even) .cell {
diff --git a/src/components/CoverageMatrix/tutorialsData.jsx b/src/components/CoverageMatrix/tutorialsData.jsx
new file mode 100644
index 00000000..59309473
--- /dev/null
+++ b/src/components/CoverageMatrix/tutorialsData.jsx
@@ -0,0 +1,920 @@
+// Coverage matrix data: the written tutorials, mapped to the Compact / Midnight
+// features each one teaches.
+//
+// Scope: every column is a written tutorial under docs/tutorials/, and every
+// feature row is something at least one tutorial covers. Cells are sourced from
+// the tutorial pages themselves and deep-link to the section where the feature
+// is covered: level "x" means the tutorial teaches or demonstrates the feature
+// (code plus explanation), "?" means it appears only briefly or incidentally.
+//
+// To keep this current:
+// - Add or rename a column in `columns` (id must be unique, lowercase-dashed)
+// and point `href` at its docs page.
+// - Under each feature in `features`, add `"": x("/route#anchor")`
+// for a feature the tutorial teaches in depth, or `q("/route#anchor")` for a
+// brief touch. The href must point at the page section covering the feature;
+// the site build validates the anchors.
+// - `section` groups rows into collapsible table sections and dropdown groups.
+//
+// `group` buckets a column as a full "DApps" tutorial or a contract-focused
+// "Contracts" tutorial.
+
+const x = (href) => ({ level: "x", href });
+const q = (href) => ({ level: "?", href });
+
+// Tutorial page routes
+const BB_SC = "/tutorials/bboard/smart-contract";
+const BB_CLI = "/tutorials/bboard/bboard-cli";
+const BB_API = "/tutorials/bboard/bboard-api-implementation";
+const BB_IMPL = "/tutorials/bboard/bboard-cli-implementation";
+const PP_SC = "/tutorials/private-party/smart-contract";
+const BS_SC = "/tutorials/bship/smart-contract";
+const BS_TEST = "/tutorials/bship/test-suite";
+const LB_OV = "/tutorials/leaderboard/overview";
+const LB_SC = "/tutorials/leaderboard/smart-contract";
+const LB_API = "/tutorials/leaderboard/api-layer";
+const LB_UI = "/tutorials/leaderboard/browser-dapp";
+const ZK_SC = "/tutorials/zk-loan/smart-contract";
+const ZK_API = "/tutorials/zk-loan/attestation-api";
+const ZK_CLI = "/tutorials/zk-loan/cli";
+
+export const columns = [
+ {
+ id: "bboard",
+ name: "Bulletin board",
+ group: "DApps",
+ href: "/tutorials/bboard"
+ },
+ {
+ id: "private-party",
+ name: "Private party",
+ group: "Contracts",
+ href: "/tutorials/private-party"
+ },
+ {
+ id: "bship",
+ name: "Battleship",
+ group: "Contracts",
+ href: "/tutorials/bship"
+ },
+ {
+ id: "leaderboard",
+ name: "Leaderboard",
+ group: "DApps",
+ href: "/tutorials/leaderboard"
+ },
+ {
+ id: "zk-loan",
+ name: "ZK Loan",
+ group: "DApps",
+ href: "/tutorials/zk-loan"
+ }
+];
+
+const LANG = "Compact language";
+const CIRCUITS = "Circuits";
+const PRIVACY = "Privacy patterns";
+const TOKENS = "Tokens";
+const WITNESS = "Witness patterns";
+const CONNECTOR = "DApp Connector";
+const MJS = "MidnightJS";
+const WALLET = "Wallet SDK";
+const DEVNET = "Local devnet and testing";
+const ADVANCED = "Advanced cryptography and services";
+
+export const features = [
+ // ---------- Compact language ----------
+ {
+ name: "Field",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#create-the-post-circuit`),
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Uint",
+ section: LANG,
+ coverage: {
+ "private-party": q(`${PP_SC}#setup`),
+ bship: x(`${BS_SC}#setup`),
+ leaderboard: q(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Bytes",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-ledger-state`),
+ "private-party": q(`${PP_SC}#setup`),
+ bship: x(`${BS_SC}#setup`),
+ leaderboard: q(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Boolean",
+ section: LANG,
+ coverage: {
+ leaderboard: q(`${LB_SC}#submitscore`),
+ "zk-loan": q(`${ZK_SC}#admin-circuits`)
+ }
+ },
+ {
+ name: "Opaque types",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-ledger-state`)
+ }
+ },
+ {
+ name: "Tuples and destructuring",
+ section: LANG,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+ {
+ name: "Structs",
+ section: LANG,
+ coverage: {
+ "private-party": q(`${PP_SC}#compact-tutorial`),
+ leaderboard: x(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Enums",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-board-state-enum`),
+ "private-party": x(`${PP_SC}#setup`),
+ bship: x(`${BS_SC}#state-machines`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Vector",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#create-the-publickey-helper-circuit`),
+ "private-party": q(`${PP_SC}#access-control`),
+ bship: q(`${BS_SC}#hashing-circuits`),
+ leaderboard: q(`${LB_SC}#ownercommitment`),
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Maybe / Either",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-ledger-state`),
+ "private-party": q(`${PP_SC}#compact-tutorial`),
+ bship: q(`${BS_SC}#shoot-circuits`)
+ }
+ },
+ {
+ name: "if / else",
+ section: LANG,
+ coverage: {
+ "private-party": x(`${PP_SC}#compact-tutorial`),
+ bship: x(`${BS_SC}#check-boards-locally`),
+ leaderboard: x(`${LB_SC}#submitscore`),
+ "zk-loan": x(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+ {
+ name: "Type casting",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-post-circuit`),
+ "private-party": q(`${PP_SC}#compact-tutorial`),
+ bship: q(`${BS_SC}#shoot-circuits`),
+ leaderboard: q(`${LB_SC}#submitscore`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Importing modules (import, include)",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#import-the-standard-library`),
+ "private-party": q(`${PP_SC}#setup`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Declaring modules (module {})",
+ section: LANG,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Nominal type aliases (new type)",
+ section: LANG,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Bounded compile-time for loops",
+ section: LANG,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#pin-migration-and-schnorr-re-export`)
+ }
+ },
+ {
+ name: "default initialization",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#create-the-constructor`),
+ "zk-loan": q(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+ {
+ name: "Ternary / branchless selection",
+ section: LANG,
+ coverage: {
+ bship: q(`${BS_SC}#check-boards-locally`),
+ "zk-loan": x(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+ {
+ name: "Ledger Counter",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-ledger-state`),
+ bship: x(`${BS_SC}#setup`),
+ leaderboard: x(`${LB_SC}#write-the-contract`)
+ }
+ },
+ {
+ name: "Ledger Cell",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#define-the-ledger-state`),
+ "private-party": q(`${PP_SC}#setup`),
+ bship: q(`${BS_SC}#setup`),
+ "zk-loan": q(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Ledger Map",
+ section: LANG,
+ coverage: {
+ leaderboard: x(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Ledger Set",
+ section: LANG,
+ coverage: {
+ "private-party": x(`${PP_SC}#setup`),
+ bship: x(`${BS_SC}#setup`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Ledger List",
+ section: LANG,
+ coverage: {
+ bship: x(`${BS_SC}#shoot-circuits`)
+ }
+ },
+ {
+ name: "Nested ADTs",
+ section: LANG,
+ coverage: {
+ bboard: q(`${BB_SC}#define-the-ledger-state`),
+ leaderboard: q(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Sealed ledger fields",
+ section: LANG,
+ coverage: {
+ "private-party": x(`${PP_SC}#setup`)
+ }
+ },
+ {
+ name: "export modifier",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#define-the-board-state-enum`),
+ "private-party": q(`${PP_SC}#data-private-by-default`),
+ bship: q(`${BS_SC}#compact-tutorial`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Constructor",
+ section: LANG,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-constructor`),
+ "private-party": x(`${PP_SC}#constructor`),
+ bship: x(`${BS_SC}#constructor`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+
+ // ---------- Circuits ----------
+ {
+ name: "Public (exported) circuits",
+ section: CIRCUITS,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-post-circuit`),
+ "private-party": x(`${PP_SC}#access-control`),
+ bship: x(`${BS_SC}#shoot-circuits`),
+ leaderboard: x(`${LB_SC}#write-the-contract`),
+ "zk-loan": x(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+ {
+ name: "Pure circuits",
+ section: CIRCUITS,
+ coverage: {
+ bboard: x(`${BB_SC}#circuit-types`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Witnesses",
+ section: CIRCUITS,
+ coverage: {
+ bboard: x(`${BB_SC}#declare-the-witness-function`),
+ "private-party": q(`${PP_SC}#witnesses`),
+ bship: x(`${BS_SC}#witnesses`),
+ leaderboard: x(`${LB_API}#witness-provider`),
+ "zk-loan": x(`${ZK_SC}#create-the-witness-function-private-data-provider`)
+ }
+ },
+ {
+ name: "disclose()",
+ section: CIRCUITS,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-post-circuit`),
+ "private-party": x(`${PP_SC}#data-private-by-default`),
+ bship: x(`${BS_SC}#shoot-circuits`),
+ leaderboard: x(`${LB_SC}#submitscore`),
+ "zk-loan": x(`${ZK_SC}#core-loan-circuits`)
+ }
+ },
+
+ // ---------- Privacy patterns ----------
+ {
+ name: "persistentCommit",
+ section: PRIVACY,
+ coverage: {
+ "private-party": x(`${PP_SC}#compact-tutorial`),
+ bship: q(`${BS_SC}#hashing-circuits`)
+ }
+ },
+ {
+ name: "persistentHash",
+ section: PRIVACY,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-publickey-helper-circuit`),
+ "private-party": x(`${PP_SC}#access-control`),
+ bship: x(`${BS_SC}#hashing-circuits`),
+ leaderboard: x(`${LB_SC}#ownercommitment`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "transientHash",
+ section: PRIVACY,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Commit-reveal",
+ section: PRIVACY,
+ coverage: {
+ "private-party": x(`${PP_SC}#operational-steps`),
+ bship: x(`${BS_SC}#cheating-assertions`)
+ }
+ },
+ {
+ name: "Unlinkable actions (rotating keys)",
+ section: PRIVACY,
+ coverage: {
+ bboard: q(`${BB_SC}#create-the-post-circuit`),
+ "zk-loan": x(`${ZK_SC}#pin-migration-and-schnorr-re-export`)
+ }
+ },
+ {
+ name: "Selective disclosure",
+ section: PRIVACY,
+ coverage: {
+ bship: x(`${BS_SC}#data-public-vs-private`),
+ leaderboard: x(`${LB_SC}#submitscore`),
+ "zk-loan": x(`${ZK_SC}#what-you-built-in-part-1`)
+ }
+ },
+ {
+ name: "Domain separation",
+ section: PRIVACY,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-publickey-helper-circuit`),
+ "private-party": x(`${PP_SC}#access-control`),
+ bship: x(`${BS_SC}#hashing-circuits`),
+ leaderboard: x(`${LB_SC}#ownercommitment`),
+ "zk-loan": x(`${ZK_SC}#the-header-types-ledger-state-and-constructor`)
+ }
+ },
+ {
+ name: "Secret/preimage-based authorization",
+ section: PRIVACY,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-takedown-circuit`),
+ "private-party": x(`${PP_SC}#access-control`),
+ bship: x(`${BS_SC}#hashing-circuits`),
+ leaderboard: x(`${LB_SC}#verifyownership`),
+ "zk-loan": x(`${ZK_SC}#admin-circuits`)
+ }
+ },
+ {
+ name: "Witness-derived caller identity (not ownPublicKey())",
+ section: PRIVACY,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-publickey-helper-circuit`),
+ "private-party": q(`${PP_SC}#witnesses`),
+ bship: x(`${BS_SC}#hashing-circuits`),
+ leaderboard: x(`${LB_SC}#ownercommitment`),
+ "zk-loan": x(`${ZK_SC}#create-the-witness-function-private-data-provider`)
+ }
+ },
+
+ // ---------- Tokens ----------
+ {
+ name: "Shielded tokens",
+ section: TOKENS,
+ coverage: {
+ bboard: q(`${BB_IMPL}#implement-wallet-utilities`),
+ "private-party": q(`${PP_SC}#conclusion`),
+ "zk-loan": q(`${ZK_CLI}#wallet-initialization`)
+ }
+ },
+ {
+ name: "Unshielded tokens",
+ section: TOKENS,
+ coverage: {
+ bboard: x(`${BB_IMPL}#wait-for-unshielded-funds`),
+ "private-party": x(`${PP_SC}#data-private-by-default`),
+ "zk-loan": q(`${ZK_CLI}#1-fund-the-wallet`)
+ }
+ },
+ {
+ name: "Native token (NIGHT)",
+ section: TOKENS,
+ coverage: {
+ bboard: x(`${BB_IMPL}#wait-for-unshielded-funds`),
+ "private-party": x(`${PP_SC}#compact-tutorial`),
+ leaderboard: q(`${LB_OV}#wallet-setup`),
+ "zk-loan": x(`${ZK_CLI}#1-fund-the-wallet`)
+ }
+ },
+ {
+ name: "Token colors",
+ section: TOKENS,
+ coverage: {
+ bboard: q(`${BB_IMPL}#implement-wallet-utilities`),
+ "private-party": q(`${PP_SC}#compact-tutorial`)
+ }
+ },
+ {
+ name: "sendUnshielded",
+ section: TOKENS,
+ coverage: {
+ "private-party": x(`${PP_SC}#compact-tutorial`)
+ }
+ },
+
+ // ---------- Witness patterns ----------
+ {
+ name: "Witness setters",
+ section: WITNESS,
+ coverage: {
+ bboard: q(`${BB_SC}#create-the-witnesses-file`),
+ bship: x(`${BS_SC}#witnesses`)
+ }
+ },
+ {
+ name: "Witness getters",
+ section: WITNESS,
+ coverage: {
+ bboard: x(`${BB_SC}#create-the-witnesses-file`),
+ bship: x(`${BS_SC}#witnesses`),
+ leaderboard: x(`${LB_API}#witness-provider`),
+ "zk-loan": x(`${ZK_SC}#create-the-witness-function-private-data-provider`)
+ }
+ },
+ {
+ name: "Intermediate witnesses (modifiers)",
+ section: WITNESS,
+ coverage: {
+ bship: x(`${BS_SC}#check-boards-locally`)
+ }
+ },
+
+ // ---------- DApp Connector ----------
+ {
+ name: "Connect wallet",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+ {
+ name: "Authorization",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+ {
+ name: "Network ID handling",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+ {
+ name: "Reading wallet state",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+ {
+ name: "Submitting transactions",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+ {
+ name: "Error handling",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#application-component`)
+ }
+ },
+ {
+ name: "Transaction serialization and wallet handoff",
+ section: CONNECTOR,
+ coverage: {
+ leaderboard: x(`${LB_UI}#wallet-bridge`)
+ }
+ },
+
+ // ---------- MidnightJS ----------
+ {
+ name: "Providers",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-wallet-provider`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#midnight-setup`),
+ leaderboard: x(`${LB_UI}#wallet-bridge`),
+ "zk-loan": x(`${ZK_CLI}#provider-configuration-and-utilities`)
+ }
+ },
+ {
+ name: "deployContract",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_API}#implement-the-deploy-method`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#deploying-the-contract`),
+ leaderboard: x(`${LB_API}#leaderboardapi-class`),
+ "zk-loan": x(`${ZK_CLI}#compiled-smart-contract-and-deployjoin`)
+ }
+ },
+ {
+ name: "findDeployedContract",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_API}#implement-the-join-method`),
+ leaderboard: x(`${LB_API}#leaderboardapi-class`),
+ "zk-loan": x(`${ZK_CLI}#compiled-smart-contract-and-deployjoin`)
+ }
+ },
+ {
+ name: "Calling circuits from TypeScript",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_API}#implement-the-post-method`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#bob-accepts-the-game`),
+ leaderboard: x(`${LB_API}#leaderboardapi-class`),
+ "zk-loan": x(`${ZK_CLI}#circuit-call-wrappers-and-state-display`)
+ }
+ },
+ {
+ name: "Observing contract state",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_API}#implement-the-constructor-and-state-observable`),
+ bship: x(`${BS_TEST}#ledger-queries`),
+ leaderboard: x(`${LB_UI}#read-on-chain-state`),
+ "zk-loan": x(`${ZK_CLI}#wallet-context-and-ledger-state`)
+ }
+ },
+ {
+ name: "Private state storage",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_API}#implement-private-state-helper`),
+ "private-party": q(`${PP_SC}#witnesses`),
+ bship: x(`${BS_TEST}#midnight-setup`),
+ leaderboard: x(`${LB_UI}#in-memory-private-state-provider`),
+ "zk-loan": x(`${ZK_CLI}#provider-configuration-and-utilities`)
+ }
+ },
+ {
+ name: "Private state modification",
+ section: MJS,
+ coverage: {
+ bship: x(`${BS_TEST}#further-cheating-attempts`),
+ "zk-loan": x(`${ZK_CLI}#attestation-and-loan-request-logic`)
+ }
+ },
+ {
+ name: "Malicious private state",
+ section: MJS,
+ coverage: {
+ bship: x(`${BS_TEST}#further-cheating-attempts`),
+ "zk-loan": q(`${ZK_SC}#create-the-witness-function-private-data-provider`)
+ }
+ },
+ {
+ name: "Transaction lifecycle",
+ section: MJS,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-transaction-methods`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: q(`${BS_TEST}#wallet-preparation`),
+ leaderboard: x(`${LB_UI}#wallet-bridge`),
+ "zk-loan": x(`${ZK_CLI}#wallet-and-provider-infrastructure`)
+ }
+ },
+ {
+ name: "Error types",
+ section: MJS,
+ coverage: {
+ bboard: q(`${BB_IMPL}#implement-transaction-methods`)
+ }
+ },
+
+ // ---------- Wallet SDK ----------
+ {
+ name: "Wallet generation",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#wallet-setup-menu`),
+ bship: x(`${BS_TEST}#wallet-preparation`),
+ "zk-loan": x(`${ZK_CLI}#wallet-initialization`)
+ }
+ },
+ {
+ name: "Address encoding and decoding",
+ section: WALLET,
+ coverage: {
+ bboard: q(`${BB_IMPL}#implement-key-provider-methods`),
+ "zk-loan": q(`${ZK_CLI}#wallet-initialization`)
+ }
+ },
+ {
+ name: "Balance queries",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#get-initial-wallet-state`),
+ "private-party": q(`${PP_SC}#testing`),
+ "zk-loan": x(`${ZK_CLI}#wallet-context-and-ledger-state`)
+ }
+ },
+ {
+ name: "DUST registration",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-dust-generation`),
+ leaderboard: q(`${LB_OV}#wallet-setup`),
+ "zk-loan": x(`${ZK_CLI}#wallet-and-provider-infrastructure`)
+ }
+ },
+ {
+ name: "Sync state and wallet readiness",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#sync-wallet`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#wallet-preparation`),
+ "zk-loan": x(`${ZK_CLI}#wallet-and-provider-infrastructure`)
+ }
+ },
+ {
+ name: "Wallet to DApp signing flow",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-wallet-provider`),
+ bship: q(`${BS_TEST}#wallet-preparation`),
+ leaderboard: x(`${LB_UI}#wallet-bridge`),
+ "zk-loan": q(`${ZK_CLI}#wallet-and-provider-infrastructure`)
+ }
+ },
+ {
+ name: "Recipe API",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-transaction-methods`),
+ bship: q(`${BS_TEST}#wallet-preparation`),
+ "zk-loan": q(`${ZK_CLI}#wallet-and-provider-infrastructure`)
+ }
+ },
+ {
+ name: "NIGHT to DUST generation lifecycle",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-dust-generation`),
+ bship: q(`${BS_TEST}#packagejson`),
+ leaderboard: q(`${LB_OV}#wallet-setup`),
+ "zk-loan": x(`${ZK_CLI}#1-fund-the-wallet`)
+ }
+ },
+ {
+ name: "DUST as a fee resource",
+ section: WALLET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#implement-dust-generation`),
+ bship: q(`${BS_TEST}#wallet-preparation`),
+ leaderboard: q(`${LB_OV}#wallet-setup`),
+ "zk-loan": x(`${ZK_CLI}#1-fund-the-wallet`)
+ }
+ },
+
+ // ---------- Local devnet and testing ----------
+ {
+ name: "Devnet up and down",
+ section: DEVNET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#run-function`),
+ "private-party": x(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#composeyml`),
+ "zk-loan": x(`${ZK_CLI}#start-midnight-local-dev`)
+ }
+ },
+ {
+ name: "Proof server",
+ section: DEVNET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#configure-the-proof-server`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: q(`${BS_TEST}#composeyml`),
+ leaderboard: x(`${LB_OV}#prerequisites`),
+ "zk-loan": x(`${ZK_API}#set-up-docker-for-the-proof-server`)
+ }
+ },
+ {
+ name: "Test wallets (Alice, Bob, Charlie)",
+ section: DEVNET,
+ coverage: {
+ bboard: q(`${BB_IMPL}#run-function`),
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#wallet-preparation`)
+ }
+ },
+ {
+ name: "Funding test accounts",
+ section: DEVNET,
+ coverage: {
+ bboard: x(`${BB_IMPL}#wait-for-unshielded-funds`),
+ bship: q(`${BS_TEST}#wallet-preparation`),
+ "zk-loan": x(`${ZK_CLI}#1-fund-the-wallet`)
+ }
+ },
+ {
+ name: "Indexer subscriptions",
+ section: DEVNET,
+ coverage: {
+ bboard: x(`${BB_API}#implement-the-constructor-and-state-observable`),
+ leaderboard: x(`${LB_UI}#read-on-chain-state`),
+ "zk-loan": q(`${ZK_CLI}#imports-and-global-setup`)
+ }
+ },
+ {
+ name: "Vitest test suite",
+ section: DEVNET,
+ coverage: {
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#vitestconfigts`)
+ }
+ },
+ {
+ name: "Negative-path and adversarial tests",
+ section: DEVNET,
+ coverage: {
+ "private-party": q(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#further-cheating-attempts`),
+ "zk-loan": q(`${ZK_CLI}#mock-user-profiles`)
+ }
+ },
+ {
+ name: "Multi-network targeting",
+ section: DEVNET,
+ coverage: {
+ bboard: q(`${BB_IMPL}#network-configuration`),
+ bship: q(`${BS_TEST}#midnight-setup`),
+ leaderboard: q(`${LB_UI}#environment-variables`),
+ "zk-loan": q(`${ZK_CLI}#configuration`)
+ }
+ },
+ {
+ name: "Faucet funding flow",
+ section: DEVNET,
+ coverage: {
+ bboard: q(`${BB_IMPL}#implement-wallet-utilities`),
+ leaderboard: x(`${LB_OV}#wallet-setup`)
+ }
+ },
+ {
+ name: "Self-contained bundled devnet",
+ section: DEVNET,
+ coverage: {
+ "private-party": x(`${PP_SC}#testing`),
+ bship: x(`${BS_TEST}#composeyml`),
+ "zk-loan": q(`${ZK_CLI}#start-midnight-local-dev`)
+ }
+ },
+
+ // ---------- Advanced cryptography and services ----------
+ {
+ name: "In-circuit Schnorr signature verification (Jubjub)",
+ section: ADVANCED,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Elliptic curve stdlib operations",
+ section: ADVANCED,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "Witness-assisted range reduction",
+ section: ADVANCED,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#write-the-schnorr-signature-module`)
+ }
+ },
+ {
+ name: "PIN-rotatable identity with batched migration",
+ section: ADVANCED,
+ coverage: {
+ "zk-loan": x(`${ZK_SC}#pin-migration-and-schnorr-re-export`)
+ }
+ },
+ {
+ name: "Off-chain attestation signing service",
+ section: ADVANCED,
+ coverage: {
+ "zk-loan": x(`${ZK_API}#build-the-attestation-api`)
+ }
+ }
+];
+
+export default { columns, features };
+
+// Dev-only integrity check: warn about coverage keys that do not match a known
+// column id, duplicate column ids, or malformed cells. Stripped from
+// production builds.
+if (process.env.NODE_ENV !== "production") {
+ const ids = new Set();
+ columns.forEach((c) => {
+ if (ids.has(c.id)) {
+ console.warn(`[CoverageMatrix] duplicate column id: "${c.id}"`);
+ }
+ ids.add(c.id);
+ });
+ features.forEach((f) => {
+ Object.entries(f.coverage).forEach(([key, cell]) => {
+ if (!ids.has(key)) {
+ console.warn(
+ `[CoverageMatrix] feature "${f.name}" references unknown column id "${key}"`
+ );
+ }
+ if (cell.level !== "x" && cell.level !== "?") {
+ console.warn(
+ `[CoverageMatrix] feature "${f.name}" cell "${key}" has invalid level`
+ );
+ }
+ });
+ });
+}