Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Bulk reformats. `git config blame.ignoreRevsFile .git-blame-ignore-revs`
59f014a8ac427bd3bfc62aa8fad2bc225b9390c4 # prettier, 9 files
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 5
Expand All @@ -20,7 +20,7 @@ updates:
update-types: [minor, patch]

- package-ecosystem: github-actions
directory: "/"
directory: '/'
schedule:
interval: weekly
open-pull-requests-limit: 3
31 changes: 31 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Build output and vendored trees — formatting these is noise.
node_modules
.next
dist
build
out
coverage
.turbo
.vercel
*.min.js
*.min.css

# Generated during a build, so it is absent locally and present in CI — which
# makes a clean local --check no evidence at all. Contentlayer's output also
# uses import assertions, which prettier's parser rejects outright.
.contentlayer
.astro
.svelte-kit
storybook-static
test-results
playwright-report

# Lockfiles are generated; prettier would rewrite them wholesale.
package-lock.json
pnpm-lock.yaml
yarn.lock

# Markdown is deliberately out of scope for now. Prettier rewraps prose, which
# is where it is most opinionated and least useful, and it would bury the real
# diff. Remove this line when you want docs formatted too.
*.md
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "all",
"arrowParens": "always",
"endOfLine": "lf"
}
9 changes: 7 additions & 2 deletions app/[[...path]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface RouteParams {
* adding one is a config entry, never a new route file.
*/
export function generateStaticParams(): Array<{ path?: string[] }> {
return sitePages().map(page => ({ path: page.path ? [page.path] : [] }));
return sitePages().map((page) => ({ path: page.path ? [page.path] : [] }));
}

export async function generateMetadata({ params }: RouteParams): Promise<Metadata> {
Expand All @@ -45,7 +45,12 @@ export default async function Page({ params }: RouteParams) {

return (
<div className="flex min-h-screen flex-col bg-surface-page">
<SiteMasthead chrome={chrome} navItems={siteNavItems(pages)} currentPath={currentPath} Link={Link} />
<SiteMasthead
chrome={chrome}
navItems={siteNavItems(pages)}
currentPath={currentPath}
Link={Link}
/>
<main className="flex-1">
<div className="mx-auto max-w-shell px-4 py-14 sm:px-6 sm:py-20 lg:px-8">
{!pageRendersOwnHeader(page) && (
Expand Down
58 changes: 30 additions & 28 deletions config/site-substrata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import type { SiteChrome, SitePage, SiteSection } from './site-content';
import { SITE } from '../lib/site';

const ROLE_LABEL: Record<string, string> = Object.fromEntries(
PRODUCER_ROLES.map(role => [role.id, role.label])
PRODUCER_ROLES.map((role) => [role.id, role.label]),
);

export function substrataSiteChrome(): SiteChrome {
Expand All @@ -77,8 +77,8 @@ export function substrataSiteChrome(): SiteChrome {

function homePage(): SitePage {
const progress = coverageProgress();
const activePhases = PHASES.filter(phase => phase.status === 'active');
const companies = new Set(COVERAGE.flatMap(entry => entry.producers.map(p => p.name))).size;
const activePhases = PHASES.filter((phase) => phase.status === 'active');
const companies = new Set(COVERAGE.flatMap((entry) => entry.producers.map((p) => p.name))).size;

return {
path: '',
Expand Down Expand Up @@ -136,7 +136,7 @@ function homePage(): SitePage {
'A node enters coverage, or the book, only if it passes both. Failing either is a ' +
'decline — and we decline every week.',
columns: 3,
cards: MANDATE_CURVES.map(curve => ({
cards: MANDATE_CURVES.map((curve) => ({
title: curve.label,
body: curve.detail,
meta: curve.test,
Expand All @@ -146,7 +146,7 @@ function homePage(): SitePage {
kind: 'definitions',
heading: 'The chokepoint screen',
blurb: EXCLUSION_RULE.rule,
items: CHOKEPOINT_TEST.map(factor => ({
items: CHOKEPOINT_TEST.map((factor) => ({
term: factor.question,
detail: factor.detail,
})),
Expand All @@ -157,7 +157,7 @@ function homePage(): SitePage {
blurb:
'Two phases at once. A desk is not one of them — see Disclosure for what this ' +
'firm does and does not do.',
items: activePhases.map(phase => ({ term: phase.label, detail: phase.detail })),
items: activePhases.map((phase) => ({ term: phase.label, detail: phase.detail })),
},
],
};
Expand All @@ -179,7 +179,7 @@ function mandatePage(): SitePage {
kind: 'cards',
heading: 'Three curves',
columns: 3,
cards: MANDATE_CURVES.map(curve => ({
cards: MANDATE_CURVES.map((curve) => ({
title: curve.label,
body: curve.detail,
meta: curve.test,
Expand All @@ -191,7 +191,7 @@ function mandatePage(): SitePage {
blurb:
'Being on a curve is not enough. Most of a supply chain is substitutable, and ' +
'therefore uninteresting. A node earns coverage when it gates a curve.',
items: CHOKEPOINT_TEST.map(factor => ({ term: factor.question, detail: factor.detail })),
items: CHOKEPOINT_TEST.map((factor) => ({ term: factor.question, detail: factor.detail })),
},
{
kind: 'cards',
Expand All @@ -201,14 +201,14 @@ function mandatePage(): SitePage {
'reaches robotics, AI hardware and additive manufacturing without becoming ' +
'“everything”: you arrive at them by tracing a chain you were already mapping.',
columns: 3,
cards: NODE_TYPES.map(node => ({ title: node.label, body: node.detail })),
cards: NODE_TYPES.map((node) => ({ title: node.label, body: node.detail })),
},
{
kind: 'definitions',
heading: 'Sequence',
blurb:
'Research first, because it costs nothing but attention and it is the sourcing work the desk needs anyway.',
items: PHASES.map(phase => ({
items: PHASES.map((phase) => ({
term: `${phase.label}${phase.status === 'active' ? ' — active' : ''}`,
detail: phase.detail,
})),
Expand All @@ -235,9 +235,9 @@ function mapPage(): SitePage {
// The material's own research — why it gates, and which grade actually ships
// — used to live on the desk page. With no desk there is no desk page, and
// that content belongs here anyway: it is research, not a product listing.
const materialById = new Map(MATERIALS.map(material => [material.title, material]));
const materialById = new Map(MATERIALS.map((material) => [material.title, material]));

const materialTables: SiteSection[] = COVERAGE.map(entry => {
const materialTables: SiteSection[] = COVERAGE.map((entry) => {
const material = materialById.get(entry.material);
return {
kind: 'table' as const,
Expand All @@ -251,7 +251,7 @@ function mapPage(): SitePage {
// across a row — mono keeps them aligned and comparable.
monoColumns: [1],
statusColumn: 3,
rows: entry.producers.map(producer => [
rows: entry.producers.map((producer) => [
producer.name,
producer.jurisdictions.join(' '),
ROLE_LABEL[producer.role] ?? producer.role,
Expand Down Expand Up @@ -294,7 +294,7 @@ function mapPage(): SitePage {
kind: 'index',
heading: 'Materials',
blurb: 'Fifteen chokepoints. The number beside each is how many producers are mapped.',
entries: COVERAGE.map(entry => ({
entries: COVERAGE.map((entry) => ({
label: entry.material,
meta: String(entry.producers.length),
anchor: materialAnchor(entry.material),
Expand All @@ -315,12 +315,12 @@ function chokepointsPage(): SitePage {
// Cards, not a table. A table of names and country codes scans nicely and
// says nothing — the claim IS the reason it gates, and a research page that
// hides its reasoning behind a tidy grid has published a list, not research.
const byCurve = MANDATE_CURVES.map(curve => ({
const byCurve = MANDATE_CURVES.map((curve) => ({
kind: 'cards' as const,
heading: curve.label,
blurb: curve.detail,
columns: 2 as const,
cards: CHOKEPOINTS.filter(point => point.curve === curve.id).map(point => ({
cards: CHOKEPOINTS.filter((point) => point.curve === curve.id).map((point) => ({
title: point.name,
body: point.why,
meta: [
Expand Down Expand Up @@ -362,7 +362,7 @@ function chokepointsPage(): SitePage {
{ label: 'Non-material chokepoints', value: String(progress.total) },
{
label: 'Kinds of node',
value: String(new Set(CHOKEPOINTS.map(point => point.type)).size),
value: String(new Set(CHOKEPOINTS.map((point) => point.type)).size),
note: 'Machines, processes, companies and people.',
},
],
Expand Down Expand Up @@ -404,7 +404,7 @@ function thesisPage(): SitePage {
'circumstances — see Acting on it for what that does and does not permit.',
],
},
...INVESTMENT_THESIS.map(claim => ({
...INVESTMENT_THESIS.map((claim) => ({
kind: 'definitions' as const,
heading: claim.claim,
blurb: claim.detail,
Expand All @@ -422,7 +422,7 @@ function participantsPage(): SitePage {
const progress = participantProgress();
const binding = bindingParticipants();

const layerTables: SiteSection[] = CHAIN_LAYERS.flatMap(layer => {
const layerTables: SiteSection[] = CHAIN_LAYERS.flatMap((layer) => {
const rows = participantsInLayer(layer.id);
if (rows.length === 0) {
return [];
Expand All @@ -436,7 +436,7 @@ function participantsPage(): SitePage {
columns: ['Participant', 'Role in the chain', 'Where', 'Scarcity'],
monoColumns: [2],
statusColumn: 3,
rows: rows.map(item => [
rows: rows.map((item) => [
item.name,
item.role,
item.jurisdictions.join(' '),
Expand Down Expand Up @@ -493,10 +493,12 @@ function participantsPage(): SitePage {
blurb:
'The mandate’s own screen — concentration, substitutability, lead time, demand ' +
'inelasticity — applied one participant at a time.',
items: (Object.keys(SCARCITY_DETAIL) as Array<keyof typeof SCARCITY_DETAIL>).map(grade => ({
term: SCARCITY_LABEL[grade],
detail: SCARCITY_DETAIL[grade],
})),
items: (Object.keys(SCARCITY_DETAIL) as Array<keyof typeof SCARCITY_DETAIL>).map(
(grade) => ({
term: SCARCITY_LABEL[grade],
detail: SCARCITY_DETAIL[grade],
}),
),
},
{
kind: 'cards',
Expand All @@ -505,7 +507,7 @@ function participantsPage(): SitePage {
`${binding.length} of ${progress.total} participants are graded a hard constraint. ` +
'These are the ones worth knowing by name.',
columns: 2,
cards: binding.map(item => ({
cards: binding.map((item) => ({
title: item.name,
body: item.why,
meta: `${item.role} · ${item.jurisdictions.join(' ')}`,
Expand All @@ -515,7 +517,7 @@ function participantsPage(): SitePage {
kind: 'index',
heading: 'The chain, layer by layer',
blurb: 'Ordered upstream to downstream. The number is how many participants are mapped.',
entries: CHAIN_LAYERS.map(layer => ({
entries: CHAIN_LAYERS.map((layer) => ({
label: layer.name,
meta: String(participantsInLayer(layer.id).length),
anchor: layer.id,
Expand Down Expand Up @@ -551,7 +553,7 @@ function actingPage(): SitePage {
detail: limit,
})),
},
...ACTION_ROUTES.map(route => ({
...ACTION_ROUTES.map((route) => ({
kind: 'definitions' as const,
heading: route.name,
blurb: route.gives,
Expand Down Expand Up @@ -593,7 +595,7 @@ function actingPage(): SitePage {
// the status belongs next to the requirement, not in a column of its own.
kind: 'definitions',
heading: 'The ledger',
items: READINESS.map(item => ({
items: READINESS.map((item) => ({
term: `${item.requirement} — ${READINESS_STATUS_LABEL[item.status]}`,
detail: item.detail,
})),
Expand Down
4 changes: 2 additions & 2 deletions config/substrata-acting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,8 @@ export interface ReadinessProgress {
export function readinessProgress(): ReadinessProgress {
return {
total: READINESS.length,
done: READINESS.filter(item => item.status === 'done').length,
inProgress: READINESS.filter(item => item.status === 'in-progress').length,
done: READINESS.filter((item) => item.status === 'done').length,
inProgress: READINESS.filter((item) => item.status === 'in-progress').length,
};
}

Expand Down
Loading
Loading