Skip to content

Commit a336539

Browse files
authored
Merge pull request #350 from MeshJS/claude/roadmap-page
feat(roadmap): public /roadmap page with a workstream timeline
2 parents 80c9335 + 319f0e0 commit a336539

81 files changed

Lines changed: 3475 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/__tests__/vault.test.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import {
2+
extractWikilinks,
3+
loadVaultGraph,
4+
splitFrontmatter,
5+
summarize,
6+
FEATURE_STATES,
7+
} from "@/lib/vault";
8+
9+
describe("splitFrontmatter", () => {
10+
it("parses scalars, inline arrays and block lists", () => {
11+
const { frontmatter, body } = splitFrontmatter(
12+
[
13+
"---",
14+
"type: feature",
15+
"state: delivered",
16+
"issues: [122, 213]",
17+
"code_paths:",
18+
" - src/lib/vault.ts",
19+
" - src/pages/roadmap",
20+
"empty: []",
21+
"---",
22+
"",
23+
"# Title",
24+
"",
25+
"Body text.",
26+
].join("\n"),
27+
);
28+
29+
expect(frontmatter.type).toBe("feature");
30+
expect(frontmatter.state).toBe("delivered");
31+
expect(frontmatter.issues).toEqual(["122", "213"]);
32+
expect(frontmatter.code_paths).toEqual([
33+
"src/lib/vault.ts",
34+
"src/pages/roadmap",
35+
]);
36+
expect(frontmatter.empty).toEqual([]);
37+
expect(body).toContain("# Title");
38+
});
39+
40+
it("strips matching quotes but leaves inner punctuation alone", () => {
41+
const { frontmatter } = splitFrontmatter(
42+
['---', 'owner: "Quirin & Andre"', "area: 'Platform & UX'", "---", ""].join(
43+
"\n",
44+
),
45+
);
46+
expect(frontmatter.owner).toBe("Quirin & Andre");
47+
expect(frontmatter.area).toBe("Platform & UX");
48+
});
49+
50+
it("returns an empty result for a note with no frontmatter", () => {
51+
const { frontmatter, body } = splitFrontmatter("# Just a heading\n\nText.");
52+
expect(frontmatter).toEqual({});
53+
expect(body).toBe("# Just a heading\n\nText.");
54+
});
55+
56+
it("does not treat an unterminated fence as frontmatter", () => {
57+
const { frontmatter } = splitFrontmatter("---\ntype: feature\nno end fence");
58+
expect(frontmatter).toEqual({});
59+
});
60+
61+
it("handles CRLF line endings", () => {
62+
const { frontmatter } = splitFrontmatter(
63+
"---\r\ntype: feature\r\nstate: blocked\r\n---\r\n\r\nBody.",
64+
);
65+
expect(frontmatter.state).toBe("blocked");
66+
});
67+
});
68+
69+
describe("extractWikilinks", () => {
70+
it("collects links, de-duplicates them and drops aliases and anchors", () => {
71+
expect(
72+
extractWikilinks(
73+
"See [[Alpha]] and [[Beta|the second]] and [[Alpha]] and [[Gamma#section]].",
74+
),
75+
).toEqual(["Alpha", "Beta", "Gamma"]);
76+
});
77+
78+
it("returns nothing when there are no links", () => {
79+
expect(extractWikilinks("Plain prose with [a link](https://x.dev).")).toEqual(
80+
[],
81+
);
82+
});
83+
});
84+
85+
describe("summarize", () => {
86+
it("uses the first paragraph, dropping the heading and link syntax", () => {
87+
expect(
88+
summarize("# Title\n\nFirst para with [[A Link]] and `code`.\n\nSecond."),
89+
).toBe("First para with A Link and code.");
90+
});
91+
});
92+
93+
describe("loadVaultGraph", () => {
94+
const graph = loadVaultGraph();
95+
96+
it("loads features, areas and states from the vault", () => {
97+
const kinds = graph.nodes.map((n) => n.kind);
98+
expect(kinds.filter((k) => k === "feature").length).toBeGreaterThan(0);
99+
expect(kinds.filter((k) => k === "area").length).toBeGreaterThan(0);
100+
expect(kinds.filter((k) => k === "state")).toHaveLength(
101+
FEATURE_STATES.length,
102+
);
103+
});
104+
105+
it("gives every feature exactly one area edge and one state edge", () => {
106+
const features = graph.nodes.filter((n) => n.kind === "feature");
107+
for (const feature of features) {
108+
const area = graph.edges.filter(
109+
(e) => e.source === feature.id && e.kind === "in-area",
110+
);
111+
const state = graph.edges.filter(
112+
(e) => e.source === feature.id && e.kind === "has-state",
113+
);
114+
expect(area).toHaveLength(1);
115+
expect(state).toHaveLength(1);
116+
}
117+
});
118+
119+
it("only emits edges between nodes that exist", () => {
120+
const ids = new Set(graph.nodes.map((n) => n.id));
121+
for (const edge of graph.edges) {
122+
expect(ids.has(edge.source)).toBe(true);
123+
expect(ids.has(edge.target)).toBe(true);
124+
}
125+
});
126+
127+
it("de-duplicates mutual references into a single relates-to edge", () => {
128+
const pairs = graph.edges
129+
.filter((e) => e.kind === "relates-to")
130+
.map((e) => [e.source, e.target].sort().join("|"));
131+
expect(new Set(pairs).size).toBe(pairs.length);
132+
});
133+
134+
it("never links a feature to itself", () => {
135+
for (const edge of graph.edges) {
136+
expect(edge.source).not.toBe(edge.target);
137+
}
138+
});
139+
140+
it("counts features by state consistently with the nodes", () => {
141+
for (const state of FEATURE_STATES) {
142+
const actual = graph.nodes.filter(
143+
(n) => n.kind === "feature" && n.state === state,
144+
).length;
145+
expect(graph.counts[state]).toBe(actual);
146+
}
147+
});
148+
149+
it("gives every feature a non-empty summary", () => {
150+
for (const feature of graph.nodes.filter((n) => n.kind === "feature")) {
151+
expect(feature.summary.length).toBeGreaterThan(0);
152+
}
153+
});
154+
155+
it("records degree matching the edges that touch each node", () => {
156+
for (const node of graph.nodes) {
157+
const touching = graph.edges.filter(
158+
(e) => e.source === node.id || e.target === node.id,
159+
).length;
160+
expect(node.degree).toBe(touching);
161+
}
162+
});
163+
});

src/components/common/overall-layout/site-footer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const productLinks = [
66
{ label: "Governance", href: "/governance" },
77
{ label: "DRep Explorer", href: "/governance/drep" },
88
{ label: "Import a wallet", href: "/wallets/import-wallet" },
9+
{ label: "Roadmap", href: "/roadmap" },
10+
{ label: "Feature graph", href: "/roadmap/graph" },
911
{ label: "Blog", href: "/blog" },
1012
];
1113

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"name":"generate-buildid","duration":181,"timestamp":291208474266,"id":4,"parentId":1,"tags":{},"startTime":1785140968470,"traceId":"08b1d61d3f5984a0"},{"name":"load-custom-routes","duration":1051,"timestamp":291208474506,"id":5,"parentId":1,"tags":{},"startTime":1785140968470,"traceId":"08b1d61d3f5984a0"},{"name":"create-dist-dir","duration":5435,"timestamp":291208475573,"id":6,"parentId":1,"tags":{},"startTime":1785140968471,"traceId":"08b1d61d3f5984a0"},{"name":"clean","duration":504,"timestamp":291208481602,"id":7,"parentId":1,"tags":{},"startTime":1785140968477,"traceId":"08b1d61d3f5984a0"},{"name":"next-build","duration":33296,"timestamp":291208448914,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"webpack","failed":true},"startTime":1785140968444,"traceId":"08b1d61d3f5984a0"}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"name":"next-build","duration":33296,"timestamp":291208448914,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"webpack","failed":true},"startTime":1785140968444,"traceId":"08b1d61d3f5984a0"}]

0 commit comments

Comments
 (0)