Static Eleventy site for roelvanbergen.nl.
Markdown is the source of truth:
- Articles:
src/blog/*.md - Talks:
content/talks/*.md - Speaking opportunities:
content/opportunities/*.md - Submissions:
content/submissions/*.md
Talks and opportunities live outside Eleventy's src/ directory so private working material cannot be copied into the public site by default.
npm run new:articleOr provide values non-interactively:
npm run new:article -- --title "A Practical Note on Calm Systems" --slug "a-practical-note-on-calm-systems" --description "How slower feedback loops help teams make better engineering decisions." --pillar engineering --tags ai,software-engineering --status draftNew articles use status: draft or status: published. Imported legacy posts using draft: true or draft: false remain supported.
npm run new:talkOr:
npm run new:talk -- --title "The Agentic Engineer" --slug "the-agentic-engineer"Every new talk starts with:
status: draftvisibility: privateshowcase: falseTo publish a curated talk, complete its public metadata and set both:
visibility: publicshowcase: trueBoth flags are required. The public loader exports only title, slug, description, status, audience, takeaways, relatedArticles, slides, and recording. It never exports private notes or the Markdown body.
npm run new:opportunityOpportunities are stored in content/opportunities/, which is never read by Eleventy. Use these files for event and CFP facts such as URLs, deadlines, dates, format, topics, and private research notes.
Create an opportunity and talk first, then link them:
npm run new:submission -- --talk the-agentic-engineer --opportunity example-conf-2026Submissions are stored in content/submissions/. A submission owns the status, submission and decision dates, abstract version, and submission-specific notes. A talk and an opportunity can each have any number of submissions.
All submissions must keep:
public: falseThe workflow does not scrape platforms or submit CFPs.
Public discovery sources are configured in:
content/discovery-sources.json
Discovery is preview-only by default:
npm run speaker:discoverAfter reviewing the preview, explicitly create new opportunity Markdown files:
npm run speaker:discover -- --writeThe importer:
- Reads public JSON, RSS, or Atom URLs
- Also accepts repository-relative files for testing or downloaded public feeds
- Skips disabled sources
- Skips existing matches by slug, event URL, or CFP URL
- Never overwrites an existing opportunity
- Creates only
status: interestingopportunity files - Adds the source name and discovery date for review
- Does not log in, scrape private pages, or submit CFPs
Example JSON source:
{
"sources": [
{
"name": "Example public CFP feed",
"enabled": true,
"type": "json",
"url": "https://example.com/public-events.json",
"itemsPath": "events",
"fields": {
"name": "title",
"url": "website",
"cfpUrl": "callForPapers.url",
"location": "venue.city",
"format": "format",
"deadline": "callForPapers.closesAt",
"eventDate": "startsAt",
"topics": "topics"
},
"defaults": {
"platform": "other"
}
}
]
}Field paths use dot notation. Optional defaults fill values that are constant for a source. RSS and Atom sources use the feed title and link, with optional defaults for platform, format, location, deadline, eventDate, and topics.
Generate an iCalendar file from opportunity deadlines and event dates:
npm run speaker:calendarThe generated file is:
.speaker-calendar/speaking-opportunities.ics
Each opportunity can produce:
CFP deadline: <name>fromdeadlineEvent: <name>fromeventDate
Ignored opportunities are skipped. The output directory is gitignored and never enters the website build. Import the .ics file into a calendar application or rerun the command whenever opportunity Markdown changes.
Articles reference talks with relatedTalks; talks reference articles with relatedArticles. Both fields contain YAML lists of slugs:
relatedTalks:
- the-agentic-engineerValidation requires referenced slugs to exist. Published article pages omit private or unshowcased talks, so a private relationship cannot reveal the talk publicly.
Run content checks without building:
npm run check:contentThe validator checks required fields, statuses, dates, slug format and uniqueness, article/talk references, submission talk/opportunity references, public talk metadata, internal article links, image text coverage, and public em dashes. Imported Medium articles report existing em dashes as warnings so legacy imports do not block unrelated work.
Run the complete publishing gate:
npm run publish:checkThis runs content validation, the production build, and a built-output privacy scan. The privacy scan checks generated HTML, XML, and JSON for private talk, opportunity, and submission values. CI runs the same command through npm run qa:publish.
Start the local site:
npm run serveThen open:
http://localhost:8080/dashboard/
The local navigation includes a Dashboard link while this server is running. The read-only page shows article and talk status counts, opportunities by status and deadline, submissions grouped by talk and opportunity, accepted or delivered submissions, validation errors, and warnings.
npm run serve sets LOCAL_DASHBOARD=true only for the local Eleventy process. Normal production builds do not load private dashboard data, do not show the menu item, and do not generate /dashboard/.
To generate a standalone local snapshot instead:
npm run dashboard:contentThis writes .content-dashboard/index.html, which is gitignored and outside _site.
- Published articles are public.
- Draft articles are excluded from public collections, feeds, and search.
- Talks are private unless both public flags are explicitly enabled.
- Talk notes and Markdown bodies are always private, including for showcased talks.
- Speaking opportunities and submissions are always private.
- The public talks listing and detail pages use only sanitized talk data.
- The
/dashboard/route and navigation item exist only undernpm run serve. npm run publish:checkverifies the generated output against private values.
Future source adapters can cover public Sessionize event APIs, Meetup feeds, conference-specific APIs, or other structured public sources. They should keep the same preview-first behavior and never automate submissions.