Internal Document Space 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: Stand up a brand-styled, access-controlled internal document space at board.cloudbase.foundation (gated) and public.cloudbase.foundation (open), backed by the documents repo (Eleventy + PagesCMS) plus a Google Shared Drive for living files.
Architecture: One documents repo is the single source of truth. Eleventy builds it twice — the full site to board.* (gated by Cloudflare Access → board@ group) and the public/ subtree to public.* (ungated). Routine docs are Markdown rendered through one shared brand theme (lifted from the existing embedded CSS); showcase docs stay bespoke standalone HTML (passed through verbatim). Living/financial files live in a Google Shared Drive that internal index pages link to — no API integration.
Tech Stack: Eleventy (11ty) v3, Liquid layout (Markdown templating disabled for content), PagesCMS (.pages.yml), Cloudflare Pages (two projects), Cloudflare Access (Google Workspace IdP), Google Shared Drive.
Global Constraints
- Repo:
Cloudbase-Foundation/documents, branchmain, on this VM at~/cbf/documents. Docs go direct tomain(no PR) per org convention. - Brand tokens are authoritative as defined in the existing
<style>block oftech/domain-map.html(lines 12–483):--navy #0c2540,--gold #c8a14a,--paper #f6f1e7; fonts Barlow Condensed, Newsreader, IBM Plex Mono. Do NOT hand-retype the CSS — extract it. - Two tiers are two subdomains, never one site with path exclusions.
board.*gets one blanket Access policy with no path exceptions. - Markdown content is rendered with templating OFF (
markdownTemplateEngine: false) so non-technical editors cannot break the build with stray{{/{%, and so docs that show template syntax (this plan, the spec) build cleanly. Branded components are inline HTML using the theme's existing.pill/.panel/.calloutclasses — a refinement of spec decision #6 (was "Liquid shortcodes") made for editor safety. The Liquid layout still runs normally. - Repo-vs-Drive rule: living/collaborative files → Shared Drive; final/static published artifacts (501(c)(3) PDF, signed policies) → repo.
- No Google Workspace automation / GAM. Drive is native folders + group sharing + hand-maintained links.
- Node build artifacts (
node_modules/,_site/,_site-public/) must be gitignored. - Spec of record:
it/2026-06-25-internal-doc-space-design.md.
Task 1: Scaffold the Eleventy project
Files:
- Create:
package.json - Create:
eleventy.shared.js(tier-safe common setup) - Create:
eleventy.config.js(internal build) - Create:
eleventy.public.config.jsis added in Task 4 — not here - Create:
.eleventyignore - Modify:
.gitignore - Create:
_includes/.gitkeep,public/files/.gitkeep - Create:
index.md(temporary smoke-test landing page)
Interfaces:
-
Produces:
npm run build→_site/;eleventy.shared.jsexporting(eleventyConfig) => voidthat BOTH build configs call for tier-safe setup only. Internal-only passthroughs (e.g.tech/) live ineleventy.config.js, never in the shared module — this is what prevents internal docs leaking into the public build. -
[ ] Step 1: Create
package.json
{
"name": "cbf-documents",
"private": true,
"scripts": {
"build": "eleventy --config=eleventy.config.js",
"build:public": "eleventy --config=eleventy.public.config.js",
"serve": "eleventy --config=eleventy.config.js --serve"
},
"devDependencies": {
"@11ty/eleventy": "^3.0.0"
}
}
- [ ] Step 2: Create the directories the configs reference
cd ~/cbf/documents && mkdir -p _includes public/files && touch _includes/.gitkeep public/files/.gitkeep
- [ ] Step 3: Create
eleventy.shared.js(tier-safe ONLY)
// Tier-safe Eleventy setup shared by BOTH builds. Nothing internal-only here —
// internal-only passthroughs belong in eleventy.config.js so they can't leak to public.
module.exports = function (eleventyConfig) {
// Public uploads (PagesCMS media) — safe on both tiers.
eleventyConfig.addPassthroughCopy("public/files");
// The shared brand layout is enabled in Task 2 (base.liquid must exist first):
// eleventyConfig.addGlobalData("layout", "base.liquid");
};
- [ ] Step 4: Create
eleventy.config.js(internal build)
const shared = require("./eleventy.shared.js");
module.exports = function (eleventyConfig) {
shared(eleventyConfig);
// Showcase HTML docs carry their own <head>/<style> — copied verbatim. Internal ONLY.
eleventyConfig.addPassthroughCopy("tech");
return {
dir: { input: ".", includes: "_includes", output: "_site" },
markdownTemplateEngine: false, // content is pure Markdown; no brace landmines
templateFormats: ["md"],
};
};
- [ ] Step 5: Create
.eleventyignore
node_modules
_site
_site-public
README.md
resumes
.temp
- [ ] Step 6: Append build artifacts to
.gitignore
Append to the existing .gitignore:
# Node / Eleventy build
node_modules/
_site/
_site-public/
- [ ] Step 7: Create the temporary smoke-test landing page
index.md
---
title: CBF Internal Documents
---
# Cloudbase Foundation — Internal Documents
Smoke-test page. Replaced by the real index in Task 5.
- [ ] Step 8: Install and build
Run: cd ~/cbf/documents && npm install && npm run build
Expected: install completes; build prints Wrote N files and exits 0.
- [ ] Step 9: Verify the landing page rendered
Run: test -f _site/index.html && echo OK
Expected: OK
- [ ] Step 10: Verify the showcase HTML passed through verbatim (path + extension preserved)
Run: head -1 _site/tech/domain-map.html
Expected: <!DOCTYPE html> (original doc at the same .html path, not pretty-URL'd or wrapped).
- [ ] Step 11: Commit
git add package.json package-lock.json eleventy.shared.js eleventy.config.js .eleventyignore .gitignore _includes/.gitkeep public/files/.gitkeep index.md
git commit -m "build: scaffold Eleventy site (internal build + tier-safe shared config)"
Task 2: Extract the brand theme into a shared layout
Files:
- Create:
_includes/theme.css(extracted fromtech/domain-map.html) - Create:
_includes/base.liquid(shared layout) - Modify:
eleventy.shared.js(enable the global layout now thatbase.liquidexists)
Interfaces:
-
Consumes: nothing new.
-
Produces: every Markdown page wrapped in
base.liquid; the.pill/.panel/.calloutbrand classes available to inline-HTML components (Task 3). The layout is Liquid and uses{{ title }},{{ content }}, and{% include "theme.css" %}— layout templating is unaffected bymarkdownTemplateEngine: false. -
[ ] Step 1: Extract the existing CSS into a shared stylesheet
The <style> body is lines 12–483 (excludes the <style>/</style> tags):
cd ~/cbf/documents
sed -n '12,483p' tech/domain-map.html > _includes/theme.css
- [ ] Step 2: Verify the brand tokens came across
Run: grep -c -- '--navy\|--gold\|--paper' _includes/theme.css
Expected: a non-zero count.
- [ ] Step 3: Append a
.callout+ container rule reusing existing tokens
Append to _includes/theme.css:
/* Doc-space additions — reuse existing brand tokens */
.callout {
border-left: 4px solid var(--gold);
background: var(--paper-soft);
padding: 0.75rem 1rem;
margin: 1.25rem 0;
border-radius: 4px;
}
.callout strong { color: var(--navy); display: block; margin-bottom: 0.25rem; }
.doc-main { max-width: 60rem; margin: 0 auto; padding: 2.5rem 1.5rem; }
- [ ] Step 4: Create
_includes/base.liquid
<!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>
<main class="doc-main">
{{ content }}
</main>
</body>
</html>
- [ ] Step 5: Enable the global layout in
eleventy.shared.js
Replace the commented line in eleventy.shared.js with the live line:
eleventyConfig.addGlobalData("layout", "base.liquid");
(Front-matter layout: on any doc still overrides this.)
- [ ] Step 6: Build and verify the Markdown page is now branded
Run: npm run build && grep -c 'doc-main\|--navy' _site/index.html
Expected: non-zero (the layout + inlined theme are present in the rendered Markdown page).
- [ ] Step 7: Commit
git add _includes/theme.css _includes/base.liquid eleventy.shared.js
git commit -m "feat: shared brand theme layout for Markdown docs"
Task 3: Branded inline components + reference doc
Components are inline HTML using the theme's existing classes (no Liquid shortcodes — see Global Constraints). markdown-it renders raw HTML by default, so editors drop a snippet straight into a doc.
Files:
- Create:
it/components.md(the brand-component reference for editors)
Interfaces:
-
Consumes:
.pill/.panel/.calloutclasses (Task 2). -
Produces: a reference doc; confirmation that inline HTML renders inside Markdown.
-
[ ] Step 1: Confirm the component classes exist in the theme
Run: grep -c 'class="pill"\|\.pill\b' _includes/theme.css && grep -c '\.callout' _includes/theme.css
Expected: both non-zero (.pill from the extracted CSS, .callout from Task 2 Step 3).
- [ ] Step 2: Create
it/components.md
---
title: Brand components for docs
---
# Brand components
Drop these snippets straight into any Markdown doc — they use the shared theme.
Status pill: <span class="pill live">Live</span> <span class="pill future">Planned</span>
Panel:
<div class="panel">A boxed, on-brand panel of content.</div>
Callout:
<div class="callout"><strong>Heads up</strong>An emphasized note.</div>
- [ ] Step 3: Build and verify inline HTML survives into the page
Run: npm run build && grep -o 'class="pill live"' _site/it/components/index.html
Expected: class="pill live"
- [ ] Step 4: Verify the callout rendered
Run: grep -o 'class="callout"' _site/it/components/index.html
Expected: class="callout"
- [ ] Step 5: Commit
git add it/components.md
git commit -m "docs: brand component reference (inline HTML snippets)"
Task 4: Create the public/ subtree and the public build
Files:
- Create:
eleventy.public.config.js - Create:
public/index.md - Move:
foundational/toc.md→public/theory-of-change.md(currently untracked, so a plainmv) - (
public/files/already created in Task 1.)
Interfaces:
-
Consumes:
eleventy.shared.js(tier-safe setup). -
Produces:
npm run build:public→_site-public/containing ONLYpublic/content — what thepublic.cloudbase.foundationproject deploys. -
[ ] Step 1: Move Theory of Change into the public tier
cd ~/cbf/documents
mv foundational/toc.md public/theory-of-change.md
Then make the first lines of public/theory-of-change.md this front matter (board-approved 2026-06-25; ships public immediately):
---
title: Theory of Change
date: 2026-06-25
---
- [ ] Step 2: Create the public landing page
public/index.md
---
title: Cloudbase Foundation — Public Documents
---
# Cloudbase Foundation
Public documents for The Cloudbase Foundation, a 501(c)(3) nonprofit (EIN 27-1359927).
- [Theory of Change](/theory-of-change/)
- 501(c)(3) determination letter (coming soon)
- [ ] Step 3: Create
eleventy.public.config.js(builds ONLYpublic/)
const shared = require("./eleventy.shared.js");
module.exports = function (eleventyConfig) {
shared(eleventyConfig); // tier-safe setup; deliberately NOT the internal config
// Internal content dirs are excluded from the PUBLIC build.
["agendas", "it", "tech", "research", "projects", "resumes", "foundational"]
.forEach((dir) => eleventyConfig.ignores.add(`${dir}/**`));
eleventyConfig.ignores.add("index.md"); // the internal landing page
return {
dir: { input: ".", includes: "_includes", output: "_site-public" },
markdownTemplateEngine: false,
templateFormats: ["md"],
};
};
- [ ] Step 4: Build the public site
Run: npm run build:public
Expected: build exits 0, writes to _site-public/.
- [ ] Step 5: Verify the public build CONTAINS the public docs
Run: test -f _site-public/theory-of-change/index.html && test -f _site-public/index.html && echo OK
Expected: OK
- [ ] Step 6: Verify the public build EXCLUDES internal content (THE leak check)
Run: for d in it agendas tech research projects resumes foundational; do test -e _site-public/$d && echo "LEAK: $d"; done; echo "leak-check done"
Expected: leak-check done with NO LEAK: lines.
- [ ] Step 7: Commit
git add public eleventy.public.config.js
git commit -m "feat: public/ subtree + public-only build (theory of change)"
Task 5: Wire existing content + internal index
Files:
- Modify:
index.md(replace smoke-test with the real internal index) - Create:
it/finances.md(Drive link-out hub; real URLs filled in Task 11) - Modify: existing Markdown docs lacking a
title(add front matter)
Interfaces:
-
Consumes:
base.liquid(Task 2). -
Produces: the internal landing page linking to concrete existing pages;
it/finances.mdas the Drive link hub. -
[ ] Step 1: Replace
index.mdwith the real internal index (links only to pages that exist)
---
title: CBF Internal Documents
---
# Cloudbase Foundation — Internal Documents
Access-controlled to the `board@cloudbase.foundation` group.
## Reference
- [Domain & Subdomain Map](/tech/domain-map.html)
- [Roadmap](/tech/roadmap.html)
- [Brand components for docs](/it/components/)
## IT & operations
- [Internal Doc Space — Design](/it/2026-06-25-internal-doc-space-design/)
- [Internal Doc Space — Plan](/it/2026-06-25-internal-doc-space-plan/)
- [CRM rebrand finish plan](/it/2026-06-25-cloudbase-foundation-email-finish-plan/)
- [Finances & Filings](/it/finances/) — budgets & board packets (Google Drive)
Public documents are published at [public.cloudbase.foundation](https://public.cloudbase.foundation/).
- [ ] Step 2: Create
it/finances.md(Drive link hub; placeholder URLs replaced in Task 11)
---
title: Finances & Filings
---
# Finances & Filings
Living financial files are kept in the **CBF Internal** Google Shared Drive
(shared to `board@cloudbase.foundation`), not in this repo.
- Operating budgets (Google Sheets): _Drive link added in Task 11_
- Board meeting packets (PDF): _Drive link added in Task 11_
- Working financial statements: _Drive link added in Task 11_
Final, published filings (e.g. the 501(c)(3) determination letter) live on the
[public document site](https://public.cloudbase.foundation/).
- [ ] Step 3: Add
titlefront matter to any Markdown doc that lacks it
Find Markdown files with no front-matter title (these would render with an empty <title>):
cd ~/cbf/documents
for f in $(find agendas it research projects -name '*.md'); do
head -5 "$f" | grep -q '^title:' || echo "NEEDS TITLE: $f"
done
For each file printed, prepend a front-matter block using the doc's main heading text:
---
title: <human title>
---
- [ ] Step 4: Build and verify the internal index renders the link list
Run: npm run build && grep -c 'Finances & Filings' _site/index.html
Expected: non-zero.
- [ ] Step 5: Verify every internal index link resolves to a built file
Run:
for p in tech/domain-map.html tech/roadmap.html it/components/index.html \
it/2026-06-25-internal-doc-space-design/index.html \
it/2026-06-25-internal-doc-space-plan/index.html \
it/2026-06-25-cloudbase-foundation-email-finish-plan/index.html \
it/finances/index.html; do
test -f "_site/$p" || echo "MISSING: $p"
done; echo "link-check done"
Expected: link-check done with NO MISSING: lines.
- [ ] Step 6: Commit
git add index.md it/finances.md agendas it research projects
git commit -m "content: internal index, finances hub, doc front matter"
Task 6: Rewrite .pages.yml for the real structure
Files:
- Modify:
.pages.yml(the existing file points at a nonexistentcontent/tree — replace it)
Interfaces:
-
Consumes: the actual repo folders (
it/,agendas/,public/, media inpublic/files). -
Produces: a PagesCMS config matching reality, so non-technical editors can edit Markdown in-browser.
-
[ ] Step 1: Replace
.pages.yml
media:
input: public/files
output: /files
content:
- name: agendas
label: Board Agendas
type: collection
path: agendas
filename: '{year}-{month}-{day}-{primary}.md'
view:
fields: [title, date]
fields:
- { name: title, label: Title, type: string }
- { name: date, label: Meeting Date, type: date }
- { name: body, label: Content, type: rich-text }
- name: it
label: IT & Operations
type: collection
path: it
view:
fields: [title, date]
fields:
- { name: title, label: Title, type: string }
- { name: date, label: Date, type: date }
- { name: body, label: Content, type: rich-text }
- name: public_docs
label: Public Documents
type: collection
path: public
view:
fields: [title, date]
fields:
- { name: title, label: Title, type: string }
- { name: date, label: Date, type: date }
- { name: body, label: Content, type: rich-text }
- [ ] Step 2: Validate YAML
Run: cd ~/cbf/documents && python3 -c "import yaml; yaml.safe_load(open('.pages.yml')); print('valid')"
Expected: valid
- [ ] Step 3: Commit
git add .pages.yml
git commit -m "chore: rewrite .pages.yml for the real repo structure"
- [ ] Step 4: Manual — connect the repo in PagesCMS
Sign in to https://app.pagescms.org with GitHub, authorize the
Cloudbase-Foundation/documents repo, open it, and confirm the agendas, it,
and public_docs collections appear and one existing doc opens in the editor.
Expected: collections load; a doc is editable. (No git artifact — visual verification.)
Task 7: Create the board@cloudbase.foundation Google group (Jonathan)
Files: none (Google Admin console).
- [ ] Step 1: Create the group
Google Admin console → Directory → Groups → Create group:
-
Name:
Board, Email:board@cloudbase.foundation -
Access type: Restricted (members only). Add board + staff members.
-
[ ] Step 2: Verify the group exists with members
Admin console → Groups → board@cloudbase.foundation → Members shows the expected people.
Expected: group present, members listed. (Shared with the rebrand/audit workstreams — create once.)
Task 8: Two Cloudflare Pages projects (Jonathan; Claude verifies)
Files: none (Cloudflare dashboard).
Interfaces:
-
Consumes:
npm run build→_site(internal);npm run build:public→_site-public(public). -
[ ] Step 1: Create the internal Pages project
Cloudflare dashboard → Workers & Pages → Create → Pages → Connect to Git →
Cloudbase-Foundation/documents:
-
Project name:
cbf-docs-board -
Production branch:
main -
Build command:
npm run build -
Build output directory:
_site -
[ ] Step 2: Create the public Pages project
Repeat with:
-
Project name:
cbf-docs-public -
Build command:
npm run build:public -
Build output directory:
_site-public -
[ ] Step 3: Verify both first deployments succeeded
Each project's Deployments tab shows a green production build off main.
Expected: both succeed.
Task 9: DNS for the two subdomains (Jonathan; Claude verifies)
Files: none (Cloudflare — the cloudbase.foundation zone).
- [ ] Step 1: Attach custom domains
In each Pages project → Custom domains → Set up a custom domain:
cbf-docs-board→board.cloudbase.foundationcbf-docs-public→public.cloudbase.foundation
Cloudflare auto-creates the proxied CNAME records (the zone is on Cloudflare).
- [ ] Step 2: Verify DNS resolves (Claude)
dig +short board.cloudbase.foundation
dig +short public.cloudbase.foundation
Expected: both return Cloudflare/Pages addresses (proxied), not NXDOMAIN.
- [ ] Step 3: Verify the public site serves BEFORE gating (Claude)
Run: curl -sI https://public.cloudbase.foundation/theory-of-change/ | head -1
Expected: HTTP/2 200.
Task 10: Cloudflare Access on board.* (Jonathan; Claude verifies)
Files: none (Cloudflare Zero Trust).
- [ ] Step 1: Ensure Google Workspace is the IdP
Zero Trust dashboard → Settings → Authentication → Login methods. If Google Workspace isn't listed, add it (requires Workspace admin OAuth consent). Confirm a test login resolves group membership.
- [ ] Step 2: Create the Access application
Zero Trust → Access → Applications → Add → Self-hosted:
-
Name:
CBF Internal Docs; Session duration: 24h -
Application domain: subdomain
board, domaincloudbase.foundation, path empty (whole host). -
[ ] Step 3: Add the allow policy (the only policy)
-
Name:
Board group; Action: Allow -
Include → Google groups →
board@cloudbase.foundation(via the Google IdP). -
Save. Confirm there is no bypass policy and no path exceptions.
-
[ ] Step 4: Verify the internal site is gated (Claude)
Run: curl -sI https://board.cloudbase.foundation/ | head -1
Expected: a redirect to Access login (HTTP/2 302, location: to *.cloudflareaccess.com), NOT 200.
- [ ] Step 5: Verify the public site is still open (Claude)
Run: curl -sI https://public.cloudbase.foundation/ | head -1
Expected: HTTP/2 200.
- [ ] Step 6: Verify authenticated access (Jonathan, browser)
Open https://board.cloudbase.foundation/ as a board@ member → the internal index
loads. As a non-member → access denied.
Expected: member in, non-member blocked.
Task 11: Google Shared Drive + wire the finances hub (Jonathan + Claude)
Files:
-
Modify:
it/finances.md(replace the placeholder Drive links) -
[ ] Step 1: Create the Shared Drive (Jonathan)
Google Drive → Shared drives → New → CBF Internal. Subfolders: Budgets,
Board Packets, Working Financials.
- [ ] Step 2: Share it to the group (Jonathan)
Shared drive → Manage members → add board@cloudbase.foundation as Content manager.
Copy the shared-drive folder URL and the Budgets folder URL.
- [ ] Step 3: Replace the placeholder links in
it/finances.md(Claude)
Swap each _Drive link added in Task 11_ for the real Drive URLs as Markdown links,
e.g. [Operating budgets](https://drive.google.com/drive/folders/XXXX).
- [ ] Step 4: Build and verify the links are present
Run: npm run build && grep -c 'drive.google.com' _site/it/finances/index.html
Expected: non-zero.
- [ ] Step 5: Commit
git add it/finances.md
git commit -m "content: wire finances hub to the CBF Internal shared drive"
- [ ] Step 6: Final end-to-end verification (Claude)
curl -sI https://board.cloudbase.foundation/it/finances/ | head -1 # expect 302 (gated)
curl -sI https://public.cloudbase.foundation/theory-of-change/ | head -1 # expect 200
Expected: internal route gated, public route open. Then confirm (browser, as a
board@ member) the finances page shows working Drive links.
Notes for the implementer
- The two leak checks (Task 4 Step 6, Task 10 Step 4) are the most important verifications in this plan. Never mark them done without seeing the expected output — they are what keep internal docs (and the whole
tech/,it/,agendas/trees) off the public tier. The architecture enforces this two ways: internal-only passthroughs live ineleventy.config.js(not the shared module), and the public config ignores every internal dir. - Showcase docs stay bespoke.
tech/domain-map.htmlandtech/roadmap.htmlkeep their embedded<style>and are passed through verbatim (internal build only). The same embedded CSS is the source for_includes/theme.css. - Why templating is off for Markdown: non-technical editors can type
{freely, and docs that show template syntax build cleanly. The cost is no Liquid shortcodes in content — branded components are inline HTML (Task 3) using the same theme classes, which is why this refines spec decision #6. - 501(c)(3) PDF: when available, drop it in
public/files/and link it frompublic/index.md. Final/static artifact → repo, not Drive. - Drive↔site links are hand-maintained by design (no automation). Keep
it/finances.mdshort and reviewed. - Future enhancement (not in scope): auto-listing section landing pages (
/agendas/,/research/) via Eleventy collections. Today the internal index links to concrete docs to avoid dead links.
Self-review result
- Spec coverage: every spec decision (1–9), the taxonomy, and the dependency list map to a task above. Decision #6 (shortcodes) is intentionally refined to inline-HTML components and flagged.
- Leak safety: the original draft's shared-base passthrough (which would have leaked
tech/to public) is fixed — internal passthroughs are isolated to the internal config; verified by Task 4 Step 6. - Type/name consistency:
build/build:public,_site/_site-public,eleventy.config.js/eleventy.public.config.js/eleventy.shared.js,base.liquid/theme.cssare consistent across all tasks and the Cloudflare settings.