diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a192ed8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,14 @@
+# Changelog
+
+Notable feature and design changes to the Idea Board, newest first. This is a running log of *what shipped and why*, not a design-spec archive — see `docs/design/specs/` for specs still under active discussion.
+
+## 2026-06-11 — Maturity label
+
+Scientists were reluctant to share early-stage ideas for fear of being held accountable if they turned out wrong. Added a maturity label so authors can flag how evidenced an idea is, framing uncertainty as a positive signal rather than a warning.
+
+- Four fixed levels: **Speculative → Exploratory → Supported → Validated**, each with a tooltip explaining what the level means.
+- New required `maturity` select field in the CMS (`static/admin/config.yml`); existing ideas without the field default to `speculative` via a Gatsby resolver.
+- `MaturityBadge` component renders an ALLEN_BLUE opacity ramp (faint at Speculative, full-strength at Validated), with two variants:
+ - `badge` (pill) — shown inline with the title in the idea list
+ - `inline` (dotted-underline text) — shown in the idea detail metadata strip
+- Out of scope (not done): bulk-updating maturity on existing ideas, filtering/sorting the index by maturity, automated maturity progression.
diff --git a/CLAUDE.md b/CLAUDE.md
index ddfa374..271be40 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -73,3 +73,4 @@ These run automatically — do not skip with `--no-verify`:
| `tdd` | Red-green-refactor loop for new features |
| `code-review` | Review changes for quality and team standards |
| `grill-me` | Stress-test a plan before implementation |
+| `pre-pr` | Retire shipped plans/specs into `CHANGELOG.md` before opening a PR |
diff --git a/gatsby/resolvers/resolvers.js b/gatsby/resolvers/resolvers.js
index 86c818a..0e34dc5 100644
--- a/gatsby/resolvers/resolvers.js
+++ b/gatsby/resolvers/resolvers.js
@@ -90,6 +90,9 @@ const createIdeaPostResolver = (reporter) => ({
};
},
},
+ maturity: {
+ resolve: (source) => source.maturity ?? "speculative",
+ },
});
module.exports = { createIdeaPostResolver };
diff --git a/gatsby/resolvers/test/resolvers.test.js b/gatsby/resolvers/test/resolvers.test.js
new file mode 100644
index 0000000..5bc5e91
--- /dev/null
+++ b/gatsby/resolvers/test/resolvers.test.js
@@ -0,0 +1,34 @@
+import { describe, expect, it } from "vitest";
+
+import { createIdeaPostResolver } from "../resolvers";
+
+const mockReporter = { error: () => {} };
+
+describe("createIdeaPostResolver - maturity", () => {
+ const resolver = createIdeaPostResolver(mockReporter);
+
+ it("returns the maturity value when present", () => {
+ expect(resolver.maturity.resolve({ maturity: "speculative" })).toBe(
+ "speculative",
+ );
+ expect(resolver.maturity.resolve({ maturity: "exploratory" })).toBe(
+ "exploratory",
+ );
+ expect(resolver.maturity.resolve({ maturity: "supported" })).toBe(
+ "supported",
+ );
+ expect(resolver.maturity.resolve({ maturity: "validated" })).toBe(
+ "validated",
+ );
+ });
+
+ it("returns 'speculative' when maturity is absent", () => {
+ expect(resolver.maturity.resolve({})).toBe("speculative");
+ expect(resolver.maturity.resolve({ maturity: null })).toBe(
+ "speculative",
+ );
+ expect(resolver.maturity.resolve({ maturity: undefined })).toBe(
+ "speculative",
+ );
+ });
+});
diff --git a/gatsby/schema/base.gql b/gatsby/schema/base.gql
index a90cd86..401c19c 100644
--- a/gatsby/schema/base.gql
+++ b/gatsby/schema/base.gql
@@ -57,6 +57,7 @@ type IdeaPost implements Node {
description: String
draft: Boolean
introduction: String
+ maturity: String
nextSteps: String
preliminaryFindings: PreliminaryFindings
primaryContact: Allenite
diff --git a/src/components/IdeaRoll.tsx b/src/components/IdeaRoll.tsx
index 324a6e9..476fa54 100644
--- a/src/components/IdeaRoll.tsx
+++ b/src/components/IdeaRoll.tsx
@@ -3,6 +3,7 @@ import React from "react";
import { Link, graphql, useStaticQuery } from "gatsby";
import FigureThumbnail from "./FigureThumbnail";
+import { MaturityBadge } from "./MaturityBadge";
import { TagPopover } from "./TagPopover";
const {
@@ -15,6 +16,7 @@ const {
textBlock,
thumbnail,
title,
+ titleRow,
} = require("../style/idea-roll.module.css");
type IdeaNode = Queries.IdeaRollQuery["allIdeaPost"]["nodes"][number];
@@ -38,6 +40,7 @@ const IdeaRoll = ({ count }: IdeaRollProps) => {
slug
title
tags
+ maturity
authors {
name
}
@@ -104,9 +107,16 @@ const IdeaRoll = ({ count }: IdeaRollProps) => {
))}
)}
-
- {item.title}
-
+