Skip to content

Commit c20883e

Browse files
feat: add use-cases page with 25 composed workflows
Generate site/use-cases.html from a new forgesworn-use-cases.json source: 25 end-to-end workflows across payments, identity, trust, coordination, and protocol/tooling. Each has a persona, a step-by-step flow, and the building blocks it uses, with stack chips linking back to repos via the catalogue. Adds tested builder functions and buildUseCasesPage() to build-site.mjs (both pages build from the CLI), a self-contained use-cases template reusing the home design system with a category filter, and a Use Cases nav/hero link. Generated HTML is gitignored and rebuilt in CI.
1 parent 12e209d commit c20883e

8 files changed

Lines changed: 1580 additions & 2 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
node_modules/
22
site/index.html
3+
site/use-cases.html
34
forgesworn-repos.json
45
.DS_Store
6+
.playwright-mcp/

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,24 @@ Current focus areas:
2626
- Protocol / Standards
2727
- Tooling / Demos
2828

29+
## Use Cases
30+
31+
The use-cases page (`/use-cases.html`) is generated from
32+
[forgesworn-use-cases.json](forgesworn-use-cases.json) — 25 end-to-end
33+
workflows that compose the catalogue libraries, each with a persona, the
34+
step-by-step flow, and the building blocks it uses. Stack links are resolved
35+
against `forgesworn-repos.json` at build time, so each block links back to its
36+
repo. Add or edit workflows in the JSON; no template changes needed.
37+
2938
## Development
3039

3140
```sh
32-
npm run build
41+
npm run build # generates site/index.html and site/use-cases.html
3342
npm test
3443
```
3544

45+
Both pages are generated output (gitignored) and are rebuilt in CI on deploy.
46+
3647
## Licence
3748

3849
MIT. See [LICENCE](LICENCE).

forgesworn-use-cases.json

Lines changed: 577 additions & 0 deletions
Large diffs are not rendered by default.

scripts/build-site.mjs

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,136 @@ export function buildFlowChain(category) {
278278
return chain.length >= 2 ? chain : null;
279279
}
280280

281+
// ─── Use-cases page ──────────────────────────────────────────────────────────
282+
283+
const USE_CASE_FALLBACK_COLOUR = '#888';
284+
285+
/**
286+
* Build a lookup of repo name -> { github, npm } from the catalogue data so the
287+
* use-cases page can link each stack item back to its repo without duplicating URLs.
288+
*/
289+
export function buildRepoIndex(catalogueData) {
290+
const index = {};
291+
for (const cat of catalogueData.categories) {
292+
for (const repo of cat.repos) {
293+
index[repo.name] = { github: repo.github, npm: repo.npm };
294+
}
295+
}
296+
return index;
297+
}
298+
299+
/**
300+
* Returns { useCases, categories, repos } counts for the use-cases hero.
301+
*/
302+
export function computeUseCaseStats(useCasesData) {
303+
const repos = new Set();
304+
for (const uc of useCasesData.useCases) {
305+
for (const item of uc.stack) repos.add(item.repo);
306+
}
307+
return {
308+
useCases: useCasesData.useCases.length,
309+
categories: useCasesData.categories.length,
310+
repos: repos.size,
311+
};
312+
}
313+
314+
/**
315+
* Returns HTML for <!-- USE_CASES_HERO_STATS -->
316+
*/
317+
export function buildUseCasesHeroStatsHtml(useCasesData) {
318+
const { useCases, categories, repos } = computeUseCaseStats(useCasesData);
319+
const stat = (value, label) =>
320+
`<div class="stat"><span class="stat-value">${value}</span><span class="stat-label">${label}</span></div>`;
321+
return [
322+
stat(useCases, 'end-to-end workflows'),
323+
stat(categories, 'problem domains'),
324+
stat(repos, 'building blocks used'),
325+
].join('\n');
326+
}
327+
328+
/**
329+
* Returns HTML for <!-- USE_CASE_FILTERS -->
330+
*/
331+
export function buildUseCaseFiltersHtml(useCasesData) {
332+
const counts = {};
333+
for (const uc of useCasesData.useCases) {
334+
counts[uc.category] = (counts[uc.category] || 0) + 1;
335+
}
336+
const pill = (slug, label, count, colour) =>
337+
`<button class="uc-filter" data-filter="${escHtml(slug)}"${colour ? ` style="--uc-colour: ${colour}"` : ''} type="button">${escHtml(label)} <span class="uc-filter-count">${count}</span></button>`;
338+
const all = `<button class="uc-filter is-active" data-filter="all" type="button">All <span class="uc-filter-count">${useCasesData.useCases.length}</span></button>`;
339+
const cats = useCasesData.categories
340+
.filter(c => counts[c.slug])
341+
.map(c => pill(c.slug, c.name, counts[c.slug], c.colour));
342+
return [all, ...cats].join('\n');
343+
}
344+
345+
/**
346+
* Build one use-case card.
347+
*/
348+
function buildUseCaseCard(uc, colour, repoIndex) {
349+
const steps = uc.steps.map(s => ` <li>${escHtml(s)}</li>`).join('\n');
350+
351+
const stack = uc.stack
352+
.map(item => {
353+
const meta = repoIndex[item.repo];
354+
const inner = `<span class="uc-chip-name">${escHtml(item.repo)}</span><span class="uc-chip-role">${escHtml(item.role)}</span>`;
355+
return meta && meta.github
356+
? ` <a class="uc-chip" href="${escHtml(meta.github)}" target="_blank" rel="noopener noreferrer">${inner}</a>`
357+
: ` <span class="uc-chip">${inner}</span>`;
358+
})
359+
.join('\n');
360+
361+
const protos = (uc.protocols || [])
362+
.map(p => ` <span class="uc-proto">${escHtml(p)}</span>`)
363+
.join('\n');
364+
365+
return `<article class="uc-card" data-category="${escHtml(uc.category)}" id="${escHtml(uc.id)}" style="--uc-colour: ${colour}">
366+
<div class="uc-card-accent"></div>
367+
<span class="uc-persona">${escHtml(uc.persona)}</span>
368+
<h3 class="uc-title">${escHtml(uc.title)}</h3>
369+
<p class="uc-problem">${escHtml(uc.problem)}</p>
370+
<p class="uc-outcome">${escHtml(uc.outcome)}</p>
371+
<div class="uc-label">Workflow</div>
372+
<ol class="uc-steps">
373+
${steps}
374+
</ol>
375+
<div class="uc-label">Built with</div>
376+
<div class="uc-stack">
377+
${stack}
378+
</div>
379+
<div class="uc-protos">
380+
${protos}
381+
</div>
382+
</article>`;
383+
}
384+
385+
/**
386+
* Returns HTML for <!-- USE_CASE_SECTIONS -->
387+
*/
388+
export function buildUseCaseSectionsHtml(useCasesData, repoIndex = {}) {
389+
const byCat = {};
390+
for (const uc of useCasesData.useCases) {
391+
(byCat[uc.category] = byCat[uc.category] || []).push(uc);
392+
}
393+
return useCasesData.categories
394+
.filter(cat => byCat[cat.slug] && byCat[cat.slug].length)
395+
.map(cat => {
396+
const colour = cat.colour || USE_CASE_FALLBACK_COLOUR;
397+
const cards = byCat[cat.slug].map(uc => buildUseCaseCard(uc, colour, repoIndex)).join('\n');
398+
return `<section class="uc-group" data-category="${escHtml(cat.slug)}" style="--uc-colour: ${colour}">
399+
<div class="uc-group-head">
400+
<span class="section-label" style="color: ${colour}">${escHtml(cat.name)}</span>
401+
<p class="uc-group-desc">${escHtml(cat.description)}</p>
402+
</div>
403+
<div class="uc-grid">
404+
${cards}
405+
</div>
406+
</section>`;
407+
})
408+
.join('\n\n');
409+
}
410+
281411
// ─── Main build function ───────────────────────────────────────────────────────
282412

283413
export async function build(jsonPath) {
@@ -299,10 +429,34 @@ export async function build(jsonPath) {
299429
console.log(`Built site/index.html -- ${repos} repos, ${stacks} categories, ${npmPackages} npm packages`);
300430
}
301431

432+
/**
433+
* Build site/use-cases.html from the use-cases JSON + the catalogue (for repo links).
434+
*/
435+
export async function buildUseCasesPage(catalogueJsonPath, useCasesJsonPath) {
436+
const resolvedCatalogue = catalogueJsonPath || join(ROOT, 'forgesworn-repos.json');
437+
const resolvedUseCases = useCasesJsonPath || join(ROOT, 'forgesworn-use-cases.json');
438+
const templatePath = join(ROOT, 'site', 'use-cases-template.html');
439+
const outputPath = join(ROOT, 'site', 'use-cases.html');
440+
441+
const catalogueData = JSON.parse(readFileSync(resolvedCatalogue, 'utf8'));
442+
const useCasesData = JSON.parse(readFileSync(resolvedUseCases, 'utf8'));
443+
const repoIndex = buildRepoIndex(catalogueData);
444+
445+
let template = readFileSync(templatePath, 'utf8');
446+
template = template.replace('<!-- USE_CASES_HERO_STATS -->', buildUseCasesHeroStatsHtml(useCasesData));
447+
template = template.replace('<!-- USE_CASE_FILTERS -->', buildUseCaseFiltersHtml(useCasesData));
448+
template = template.replace('<!-- USE_CASE_SECTIONS -->', buildUseCaseSectionsHtml(useCasesData, repoIndex));
449+
450+
writeFileSync(outputPath, template, 'utf8');
451+
452+
const { useCases, categories } = computeUseCaseStats(useCasesData);
453+
console.log(`Built site/use-cases.html -- ${useCases} use cases, ${categories} categories`);
454+
}
455+
302456
// Only run when executed directly (not when imported by tests)
303457
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
304458
if (isMain) {
305-
build(process.argv[2]).catch(err => {
459+
Promise.all([build(process.argv[2]), buildUseCasesPage()]).catch(err => {
306460
console.error(err);
307461
process.exit(1);
308462
});

site/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@
948948
<button class="nav-toggle" id="navToggle" type="button" aria-label="Toggle navigation" aria-expanded="false">&#9776;</button>
949949
<ul class="nav-links" id="navLinks">
950950
<li><a href="#ecosystem">Ecosystem</a></li>
951+
<li><a href="/use-cases.html">Use Cases</a></li>
951952
<li><a href="/forge-realms/">Forge Realms</a></li>
952953
<li><a href="https://bray.forgesworn.dev">Bray</a></li>
953954
<li><a href="https://github.com/forgesworn" class="btn-nav">GitHub</a></li>
@@ -965,6 +966,7 @@ <h1>Open-source building blocks for <em>sovereign commerce, identity, and trust<
965966
</p>
966967
<div class="hero-actions">
967968
<a href="#ecosystem" class="btn-primary">Explore the ecosystem</a>
969+
<a href="/use-cases.html" class="btn-secondary">See 25 use cases</a>
968970
<a href="/forge-realms/" class="btn-secondary">Play Forge Realms</a>
969971
<a href="https://github.com/forgesworn" class="btn-secondary">
970972
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>

site/template.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@
948948
<button class="nav-toggle" id="navToggle" type="button" aria-label="Toggle navigation" aria-expanded="false">&#9776;</button>
949949
<ul class="nav-links" id="navLinks">
950950
<li><a href="#ecosystem">Ecosystem</a></li>
951+
<li><a href="/use-cases.html">Use Cases</a></li>
951952
<li><a href="/forge-realms/">Forge Realms</a></li>
952953
<li><a href="https://bray.forgesworn.dev">Bray</a></li>
953954
<li><a href="https://github.com/forgesworn" class="btn-nav">GitHub</a></li>
@@ -965,6 +966,7 @@ <h1>Open-source building blocks for <em>sovereign commerce, identity, and trust<
965966
</p>
966967
<div class="hero-actions">
967968
<a href="#ecosystem" class="btn-primary">Explore the ecosystem</a>
969+
<a href="/use-cases.html" class="btn-secondary">See 25 use cases</a>
968970
<a href="/forge-realms/" class="btn-secondary">Play Forge Realms</a>
969971
<a href="https://github.com/forgesworn" class="btn-secondary">
970972
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>

0 commit comments

Comments
 (0)