The Cloudbase Foundation · Internal Documents

Internal doc space styling & navigation — implementation plan

Internal Doc Space Styling & Navigation Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Give the internal board.cloudbase.foundation site a branded baseline template (masthead + footer) and a real top-bar navigation with auto-listed section pages, while leaving the public tier byte-for-byte unchanged.

Architecture: base.liquid gains a tier-gated masthead + footer (rendered only when the internal build sets tier: "internal"). A _data/nav.json-driven site-nav component (reusing the nav.doc-nav classes already in theme.css) is included in the layout and hand-retrofitted onto the standalone showcase/agenda HTML pages. A _data/sections.js filesystem reader powers auto-listed landing pages at /agendas/, /it/, /research/. Theory of Change moves to the internal tier.

Tech Stack: Eleventy v3, Liquid layout/includes (Markdown templating stays OFF), Cloudflare Pages.

Global Constraints


Task 1: Tier flag + branded masthead/footer on the template

Files:

Interfaces:

In eleventy.config.js, inside the exported function (after shared(eleventyConfig);), add:

  // Marks the internal tier so base.liquid renders the site chrome. The public
  // build (eleventy.public.config.js) deliberately does NOT set this.
  eleventyConfig.addGlobalData("tier", "internal");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title }} — CBF</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@300;400;500;600;700&family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,500;0,6..72,600;1,6..72,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<style>{% include "theme.css" %}</style>
</head>
<body>
{% if tier == "internal" %}
<header class="masthead">
  <div class="masthead-inner">
    <div>
      <div class="id-block">The Cloudbase Foundation · Internal Documents</div>
      <h1>{{ title }}</h1>
    </div>
  </div>
</header>
{% endif %}
<main class="doc-main">
{{ content }}
</main>
{% if tier == "internal" %}
<footer class="colophon">
  <div class="col-inner">
    <div>The Cloudbase Foundation · Internal Documents</div>
    <div>board.cloudbase.foundation</div>
  </div>
</footer>
{% endif %}
</body>
</html>

Run: npm run build && npm run build:public Expected: both exit 0.

Run: grep -c 'class="masthead"' _site/it/finances/index.html && grep -c 'footer class="colophon"' _site/it/finances/index.html Expected: both non-zero.

Run: grep -c 'class="masthead"\|colophon' _site-public/index.html; echo "exit ok" Expected: 0 then exit ok (no masthead/footer on public).

git add eleventy.config.js _includes/base.liquid
git commit -m "feat: tier-gated masthead + footer on the internal template"

Task 2: Site-nav component (_data/nav.json + _includes/site-nav.html)

Files:

Interfaces:

[
  { "label": "Home", "url": "/" },
  { "label": "Agendas", "url": "/agendas/" },
  { "label": "IT & Ops", "url": "/it/" },
  { "label": "Research", "url": "/research/" },
  { "label": "Reference", "url": "/tech/" }
]
<nav class="doc-nav" aria-label="Site navigation">
  <div class="nav-inner">
    <span class="nav-label">CBF docs ›</span>
    {% for link in nav %}
    <a href="{{ link.url }}" class="nav-item">{{ link.label }}</a>
    {% endfor %}
  </div>
</nav>

In _includes/base.liquid, change the masthead block so the nav follows the </header> (still inside the tier == "internal" guard):

{% if tier == "internal" %}
<header class="masthead">
  <div class="masthead-inner">
    <div>
      <div class="id-block">The Cloudbase Foundation · Internal Documents</div>
      <h1>{{ title }}</h1>
    </div>
  </div>
</header>
{% include "site-nav.html" %}
{% endif %}

Run: npm run build && grep -o 'class="nav-item"' _site/index.html | wc -l Expected: 5 (one per nav link).

Run: grep -oE 'href="(/|/agendas/|/it/|/research/|/tech/)" class="nav-item"' _site/index.html | wc -l Expected: 5.

Run: npm run build:public && grep -c 'nav-item' _site-public/index.html; echo done Expected: 0 then done.

git add _data/nav.json _includes/site-nav.html _includes/base.liquid
git commit -m "feat: site-nav top bar (nav.json) on the internal template"

Task 3: Auto-listed section landing pages (/agendas/, /it/, /research/)

Files:

Interfaces:

const fs = require("fs");
const path = require("path");

// Sections to auto-list. Each lists *.md + *.html, excluding index/template/partials.
const SECTION_DIRS = ["agendas", "it", "research"];

function titleFromMd(raw) {
  const fm = raw.match(/^---\n([\s\S]*?)\n---/);
  if (fm) {
    const t = fm[1].match(/^title:\s*(.+)$/m);
    if (t) return t[1].trim().replace(/^["']|["']$/g, "");
  }
  return null;
}

function titleFromHtml(raw) {
  const t = raw.match(/<title>([^<]*)<\/title>/i);
  return t ? t[1].replace(/\s+—\s+CBF\s*$/, "").trim() : null;
}

function urlFor(dir, file) {
  if (file.endsWith(".md")) return `/${dir}/${file.replace(/\.md$/, "")}/`;
  return `/${dir}/${file}`;
}

module.exports = function () {
  const out = {};
  for (const dir of SECTION_DIRS) {
    let files = [];
    try { files = fs.readdirSync(dir); } catch (e) { files = []; }
    const items = [];
    for (const file of files) {
      if (!/\.(md|html)$/i.test(file)) continue;
      if (/^index\./i.test(file)) continue;     // never list a section's own index
      if (/^_/.test(file)) continue;            // partials
      if (file === "agenda-template.html") continue; // template, not a real agenda
      const raw = fs.readFileSync(path.join(dir, file), "utf8");
      const title = (file.endsWith(".md") ? titleFromMd(raw) : titleFromHtml(raw)) || file;
      items.push({ title, url: urlFor(dir, file), sortKey: file });
    }
    // Date-prefixed filenames sort newest-first under reverse string order.
    items.sort((a, b) => b.sortKey.localeCompare(a.sortKey));
    out[dir] = items;
  }
  return out;
};

In eleventy.config.js, change the returned templateFormats so standalone .liquid listing pages render:

    templateFormats: ["md", "liquid"],

(The public config is untouched — it stays ["md"], and its input is public/ only, so these section pages never reach the public tier.)

Append to _includes/theme.css:

/* Section landing lists */
.doc-list { list-style: none; padding-left: 0; max-width: 60rem; }
.doc-list li {
  border-top: 1px dotted var(--line-soft);
  padding: 0.7rem 0;
}
.doc-list li:first-child { border-top: none; }
.doc-list a {
  font-family: 'Barlow Condensed', sans-serif;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
}
---
title: Board Agendas
---
<ul class="doc-list">
{% for doc in sections.agendas %}
  <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
{% endfor %}
</ul>
---
title: IT & Operations
---
<ul class="doc-list">
{% for doc in sections.it %}
  <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
{% endfor %}
</ul>
---
title: Research
---
<ul class="doc-list">
{% for doc in sections.research %}
  <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
{% endfor %}
</ul>

Run:

npm run build
test -f _site/agendas/index.html && test -f _site/it/index.html && test -f _site/research/index.html && echo "pages built"
grep -c 'doc-list' _site/agendas/index.html
grep -o 'href="/agendas/7-9-26-meeting.html"' _site/agendas/index.html

Expected: pages built; non-zero doc-list count; the 7-9-26-meeting.html link present (proves the HTML agenda was auto-listed). The agenda-template.html must NOT appear:

grep -c 'agenda-template' _site/agendas/index.html

Expected: 0.

Run:

npm run build:public
for d in it agendas research projects tech foundational resumes; do test -e _site-public/$d && echo "LEAK: $d"; done; echo "leak-check done"

Expected: leak-check done with NO LEAK: lines.

git add _data/sections.js eleventy.config.js _includes/theme.css agendas/index.liquid it/index.liquid research/index.liquid
git commit -m "feat: auto-listed section landing pages (agendas, it, research)"

Task 4: Retrofit the standalone HTML pages onto the site nav

The showcase pages (tech/domain-map.html, tech/roadmap.html) already carry a bespoke 3-item nav.doc-nav and the .doc-nav CSS in their embedded <style>. The agenda HTML pages have a masthead but no nav and no .doc-nav CSS. Standardize all of them to the site nav.

Files:

Interfaces:

Find the existing <nav class="doc-nav" ...>…</nav> block and replace the whole block with:

<nav class="doc-nav" aria-label="Site navigation">
  <div class="nav-inner">
    <span class="nav-label">CBF docs ›</span>
    <a href="/" class="nav-item">Home</a>
    <a href="/agendas/" class="nav-item">Agendas</a>
    <a href="/it/" class="nav-item">IT &amp; Ops</a>
    <a href="/research/" class="nav-item">Research</a>
    <a href="/tech/" class="nav-item current" aria-current="page">Reference</a>
  </div>
</nav>

Same replacement as Step 1 (identical block — Reference is current since roadmap lives under /tech/).

The agenda pages use the same :root tokens but lack the nav rules. In each of agendas/4-30-26-meeting.html, agendas/7-9-26-meeting.html, and agendas/agenda-template.html, insert this block immediately before the closing </style>:

  /* ----------- SITE NAV ----------- */
  nav.doc-nav { background: var(--paper-warm); border-bottom: 1px solid var(--line); padding: 0.65rem 0; position: relative; z-index: 3; }
  nav.doc-nav .nav-inner { max-width: 820px; margin: 0 auto; padding: 0 2.5rem; display: flex; align-items: center; gap: 1.8rem; flex-wrap: wrap; }
  nav.doc-nav .nav-label { font-family: 'IBM Plex Mono', monospace; text-transform: uppercase; letter-spacing: 0.16em; font-size: 0.66rem; color: var(--ink-mute); }
  nav.doc-nav .nav-item { font-family: 'Barlow Condensed', sans-serif; text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.82rem; font-weight: 500; color: var(--ink-soft); text-decoration: none; padding: 0.2rem 0; border-bottom: 2px solid transparent; transition: color 0.15s ease, border-color 0.15s ease; }
  nav.doc-nav .nav-item:hover { color: var(--navy); border-bottom-color: var(--gold); }
  nav.doc-nav .nav-item.current { color: var(--navy); border-bottom-color: var(--gold); cursor: default; pointer-events: none; }

In each of the three agenda files, insert this block immediately after the masthead's closing </header>:

<nav class="doc-nav" aria-label="Site navigation">
  <div class="nav-inner">
    <span class="nav-label">CBF docs ›</span>
    <a href="/" class="nav-item">Home</a>
    <a href="/agendas/" class="nav-item current" aria-current="page">Agendas</a>
    <a href="/it/" class="nav-item">IT &amp; Ops</a>
    <a href="/research/" class="nav-item">Research</a>
    <a href="/tech/" class="nav-item">Reference</a>
  </div>
</nav>

Run:

npm run build
for p in tech/domain-map.html tech/roadmap.html agendas/7-9-26-meeting.html agendas/4-30-26-meeting.html; do
  echo "$p: $(grep -c 'href="/agendas/" class="nav-item"' _site/$p) site-nav"
done

Expected: each prints 1 (the site nav's Agendas link is present on every page).

Run: grep -c 'CBF strategy ›\|project-timeline.html' _site/tech/domain-map.html Expected: 0 (the strategy-doc nav was replaced).

git add tech/domain-map.html tech/roadmap.html agendas/4-30-26-meeting.html agendas/7-9-26-meeting.html agendas/agenda-template.html
git commit -m "feat: standardize showcase + agenda pages onto the site nav"

Task 5: Move Theory of Change into the internal tier

Files:

Interfaces:

cd ~/cbf/documents
git mv public/theory-of-change.md foundational/theory-of-change.md

Replace the Theory of Change bullet with a "coming soon" line. The file should read:

---
title: Cloudbase Foundation — Public Documents
---

# Cloudbase Foundation

Public documents for The Cloudbase Foundation, a 501(c)(3) nonprofit (EIN 27-1359927).

- Theory of Change (coming soon)
- 501(c)(3) determination letter (coming soon)

In index.md, under the ## Reference list (or a fitting spot), add:

- [Theory of Change](/foundational/theory-of-change/)

Run:

npm run build && npm run build:public
test -f _site/foundational/theory-of-change/index.html && echo "internal ToC OK"
test -e _site-public/theory-of-change && echo "STILL PUBLIC (bad)" || echo "ToC absent from public — good"
grep -c 'coming soon' _site-public/index.html

Expected: internal ToC OK; ToC absent from public — good; non-zero coming soon count.

Run: grep -c 'class="masthead"\|nav-item' _site/foundational/theory-of-change/index.html Expected: non-zero (the relocated ToC picks up the internal template).

git add public/theory-of-change.md foundational/theory-of-change.md public/index.md index.md
git commit -m "content: move Theory of Change to the internal tier (public: coming soon)"

Task 6: Final end-to-end verification + push

Files: none (verification only).

Run: rm -rf _site _site-public && npm run build && npm run build:public Expected: both exit 0.

Run:

for p in index.html it/finances/index.html agendas/index.html foundational/theory-of-change/index.html \
         tech/domain-map.html agendas/7-9-26-meeting.html; do
  echo "$p: nav=$(grep -c 'class="nav-item"' _site/$p)"
done

Expected: every page reports a non-zero nav count.

Run:

for d in it agendas research projects tech foundational resumes; do test -e _site-public/$d && echo "LEAK: $d"; done; echo "leak-check done"
grep -c 'masthead\|nav-item\|colophon' _site-public/index.html; echo "public-chrome-check done"

Expected: leak-check done with NO LEAK: lines; 0 then public-chrome-check done (public has no chrome).

git push origin main

Expected: Cloudflare Pages redeploys both projects; board.* shows the new chrome/nav (verify in-browser as a board member), public.* unchanged.


Notes for the implementer

Self-review result