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
6 changes: 5 additions & 1 deletion .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ jobs:
with:
python-version: '3.14'
- run: python src/website/build.py
- run: node --check src/website/site.js
- run: python src/website/validate.py
- run: |
node --check src/website/site.js
node --check src/website/theme.js
node --check src/website/docs.js
1 change: 0 additions & 1 deletion src/website/.openai/hosting.json

This file was deleted.

14 changes: 12 additions & 2 deletions src/website/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
# Interprocess website

A static landing page with language examples and links to the library and protocol. No runtime framework or external dependencies.
A static landing page and developer documentation for all six language APIs. No runtime framework or external dependencies.

```sh
python3 src/website/build.py
python3 -m http.server --directory src/website/dist
```

The Website workflow validates the static build on pull requests and main. The public site at cloudtoid.com uses Sites hosting; `.openai/hosting.json` identifies it. Keep registry availability and the preview/release notice accurate when publishing packages.
Cloudflare Pages publishes this site directly from `cloudtoid/interprocess`. Changes under `src/website/**` on `main` trigger production deployments; other branches receive previews linked from GitHub. Project: `cloudtoid`; build command: `python3 src/website/build.py`; output directory: `src/website/dist`. The Website GitHub Actions workflow also validates the build and JavaScript syntax. Failed builds do not replace the last successful deployment.

Keep package availability and installation commands current when releasing packages. Benchmarks change only after new measurements.

The header and footer use the official blue wordmarks from [cloudtoid/assets](https://github.com/cloudtoid/assets/tree/master/logos), served locally. The black and white variants follow the selected color theme.

## Developer documentation

`docs/pages.json` defines page titles, descriptions, navigation, and URLs. Edit the corresponding HTML fragments under `docs/`; `docs/template.html` supplies the shared layout. `build.py` renders the pages into `dist/docs/`, creates the sitemap and robots.txt, and adds canonical, social, and structured metadata. API content is authored against the public implementations; it is not generated from source comments. Update the reference alongside API changes, including waiting, error, ownership, and truncation behavior. Link to generated ecosystem references where available.

Run `python3 src/website/validate.py` after building to check local links, anchors, metadata, and sitemap coverage. CI runs this check. All reference text and navigation work without JavaScript; `docs.js` enhances code blocks with highlighting and copy buttons. The homepage's language guide links point to these pages.

The social preview uses `assets/social-card.png`; its editable SVG source is alongside it. SEO metadata uses `https://cloudtoid.com` as the canonical origin. Publishing makes the sitemap available at `/sitemap.xml`; search-engine indexing happens independently of deployment.
Binary file added src/website/assets/social-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/website/assets/social-card.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 86 additions & 3 deletions src/website/build.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,94 @@
"""Build the static site for Sites, without runtime dependencies."""
"""Build the static marketing site and developer reference, without dependencies."""
from html import escape
import json
from pathlib import Path
import re
import shutil
from string import Template

root = Path(__file__).resolve().parent
output = root / 'dist'
output.mkdir(exist_ok=True)
for name in ('index.html', 'style.css', 'site.js', 'theme.js'):
base = 'https://cloudtoid.com'
pages = json.loads((root / 'docs/pages.json').read_text())
home = (root / 'index.html').read_text()
favicon = re.search(r'<link rel="icon"[^>]+>', home).group()


def metadata(title, description, path, kind='website'):
url = base + path
tags = [f'<link rel="canonical" href="{url}">']
for key, value in {
'og:type': kind, 'og:site_name': 'Cloudtoid Interprocess',
'og:title': title, 'og:description': description, 'og:url': url,
'og:image': base + '/assets/social-card.png',
'og:image:width': '1200', 'og:image:height': '630',
'og:image:alt': 'Cloudtoid Interprocess: fast shared-memory queues across six languages',
}.items():
tags.append(f'<meta property="{key}" content="{escape(value, quote=True)}">')
tags.append('<meta name="twitter:card" content="summary_large_image">')
return '\n'.join(tags)


home_title = re.search(r'<title>(.*?)</title>', home).group(1)
home_description = re.search(r'<meta name="description" content="([^"]+)"', home).group(1)
home = re.sub(r'<link rel="canonical"[^>]+>', metadata(home_title, home_description, '/'), home)
software = {
'@context': 'https://schema.org', '@type': 'SoftwareSourceCode',
'name': 'Cloudtoid Interprocess', 'url': base + '/',
'description': home_description,
'codeRepository': 'https://github.com/cloudtoid/interprocess',
'programmingLanguage': ['Rust', 'C', 'Python', 'JavaScript', 'Go', 'C#'],
'runtimePlatform': ['Linux', 'macOS', 'Windows'],
'license': 'https://github.com/cloudtoid/interprocess/blob/main/LICENSE',
}
home = home.replace('</head>', '<script type="application/ld+json">' + json.dumps(software) + '</script>\n</head>')
(output / 'index.html').write_text(home)
for name in ('style.css', 'site.js', 'theme.js', 'docs.css', 'docs.js'):
shutil.copyfile(root / name, output / name)
for name in ('assets', 'benchmarks', 'vendor'):
shutil.copytree(root / name, output / name, dirs_exist_ok=True)
print(f'Built {output}')


def page_path(page):
return '/docs/' + (page['slug'] + '/' if page['slug'] else '')


template = Template((root / 'docs/template.html').read_text())
for page in pages:
path = page_path(page)
content = (root / 'docs' / ((page['slug'] or 'overview') + '.html')).read_text()
navigation = ''.join(
'<a href="{}"{}>{}</a>'.format(page_path(item), ' aria-current="page"' if item == page else '', escape(item['label']))
for item in pages
)
headings = re.findall(r'<h2 id="([^"]+)">(.*?)</h2>', content)
content = re.sub(r'<h([23]) id="([^"]+)">(.*?)</h\1>',
lambda match: f'<h{match[1]} id="{match[2]}"><a class="heading-link" href="#{match[2]}">{match[3]}</a></h{match[1]}>', content)
toc = ''.join(f'<a href="#{key}">{title}</a>' for key, title in headings)
breadcrumbs = [{'@type': 'ListItem', 'position': 1, 'name': 'Documentation', 'item': base + '/docs/'}]
if page['slug']:
breadcrumbs.append({'@type': 'ListItem', 'position': 2, 'name': page['title'], 'item': base + path})
data = [
{'@context': 'https://schema.org', '@type': 'TechArticle', 'headline': page['title'],
'description': page['description'], 'url': base + path, 'inLanguage': 'en',
'author': {'@type': 'Organization', 'name': 'Cloudtoid', 'url': base + '/'}},
{'@context': 'https://schema.org', '@type': 'BreadcrumbList', 'itemListElement': breadcrumbs},
]
rendered = template.substitute(
title=escape(page['title']), description=escape(page['description'], quote=True),
metadata=metadata(page['title'] + ' | Cloudtoid Interprocess', page['description'], path, 'article')
+ '\n<script type="application/ld+json">' + json.dumps(data) + '</script>',
favicon=favicon, navigation=navigation, toc=toc, content=content,
breadcrumb=('<span>/</span><span>' + escape(page['label']) + '</span>') if page['slug'] else '',
)
target = output / path.strip('/') / 'index.html'
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(rendered)

urls = [base + '/'] + [base + page_path(page) for page in pages]
(output / 'sitemap.xml').write_text('<?xml version="1.0" encoding="UTF-8"?>\n'
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
+ '\n'.join(f'<url><loc>{url}</loc></url>' for url in urls) + '\n</urlset>\n')
(output / 'robots.txt').write_text('User-agent: *\nAllow: /\n\nSitemap: ' + base + '/sitemap.xml\n')
print(f'Built {output}: homepage and {len(pages)} documentation pages')
63 changes: 63 additions & 0 deletions src/website/docs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.docs-layout { display: grid; grid-template-columns: 170px minmax(0, 1fr) 145px; gap: 38px; padding-top: 40px; padding-bottom: 70px; }
.docs-sidebar, .docs-toc { align-self: start; position: sticky; top: 24px; max-height: calc(100vh - 48px); overflow-y: auto; }
.docs-sidebar .eyebrow, .docs-toc .eyebrow { font-size: 10px; margin-bottom: 16px; }
.docs-sidebar nav, .docs-toc nav { display: flex; flex-direction: column; align-items: stretch; gap: 3px; }
.docs-sidebar nav a { display: block; padding: 7px 10px; border-radius: 5px; color: var(--muted); font-size: 14px; }
.docs-sidebar a[aria-current=page] { background: var(--hero-background); color: var(--accent); font-weight: 650; }
.docs-sidebar .protocol-link { display: block; margin-top: 25px; padding-top: 18px; border-top: 1px solid var(--line); color: var(--muted); font-size: 12px; }
.docs-toc a { font-size: 12px; color: var(--muted); padding: 4px 0; line-height: 1.5; }
.doc-content { min-width: 0; }
.doc-breadcrumb { display: flex; flex-wrap: wrap; gap: 9px; font-size: 12px; color: var(--muted); margin-bottom: 24px; }
.doc-content h1 { font-size: clamp(32px, 3.5vw, 45px); letter-spacing: -.045em; line-height: 1.15; margin-bottom: 20px; }
.doc-content h2 { font-size: 25px; letter-spacing: -.7px; margin: 48px 0 20px; padding-top: 8px; border-top: 1px solid var(--line); }
.doc-content h3 { font-size: 16px; font-weight: 600; margin: 26px 0 10px; line-height: 1.7; scroll-margin-top: 25px; }
.doc-content h3 code { background: var(--soft); color: var(--ink); padding: 3px 5px; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
.doc-content p, .doc-content li, .doc-content dd { font-size: 15px; line-height: 1.8; color: var(--muted); }
.doc-content .doc-lead { font-size: 19px; line-height: 1.65; margin-bottom: 22px; }
.doc-content .doc-source { font-size: 12px; }
.doc-content a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.doc-content code { font: .86em/1.7 var(--mono); overflow-wrap: anywhere; }
.doc-content p code, .doc-content li code, .api-list dt code { background: var(--soft); color: var(--ink); border-radius: 3px; padding: 2px 4px; }
.doc-content pre { min-height: 0; font-size: 13px; padding: 22px; margin: 20px 0; border: 1px solid var(--line); border-radius: 7px; background: var(--surface); line-height: 1.75; }
.doc-content pre code { font-size: inherit; overflow-wrap: normal; }
.doc-code { position: relative; }
.doc-code pre { padding-top: 47px; }
.doc-copy { position: absolute; top: 10px; right: 10px; border: 1px solid var(--line); border-radius: 4px; padding: 3px 9px; font-size: 11px; color: var(--muted); background: var(--soft); cursor: pointer; }
.doc-copy:hover { color: var(--accent); border-color: var(--accent); }
.doc-note { padding: 18px 20px; margin: 24px 0; border-left: 3px solid var(--accent); background: var(--hero-background); border-radius: 0 6px 6px 0; font-size: 15px; color: var(--ink); }
.doc-note strong { display: block; }
.doc-table { overflow-x: auto; border: 1px solid var(--line); border-radius: 6px; margin: 24px 0; }
.doc-table td, .doc-table th { padding: 12px 15px; }
.doc-table td { font-size: 13px; font-family: inherit; font-weight: 400; line-height: 1.6; white-space: normal; }
.api-list dt { margin-top: 15px; font-weight: 600; }
.api-list dd { margin: 5px 0 16px; }
.doc-cards { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 14px; }
.doc-cards a { display: flex; flex-direction: column; gap: 10px; padding: 20px; background: var(--surface); border: 1px solid var(--line); border-radius: 7px; text-decoration: none; }
.doc-cards a:hover { border-color: var(--accent); background: var(--soft); }
.doc-cards strong { font-size: 17px; color: var(--ink); }
.doc-cards span { font-size: 13px; color: var(--muted); }
.doc-cards code { font-size: 11px; margin-top: auto; }
.doc-bottom { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 18px; margin-top: 55px; padding-top: 20px; border-top: 1px solid var(--line); font-size: 12px; }
.docs-page header nav a { display: inline; }
@media (max-width:1100px) {
.docs-layout { grid-template-columns: 155px minmax(0,1fr); gap: 28px; }
.docs-toc { display: none; }
}
@media (max-width:800px) {
.docs-layout { grid-template-columns: 1fr; padding-top: 25px; }
.docs-sidebar { position: static; max-height: none; padding-bottom: 20px; border-bottom: 1px solid var(--line); }
.docs-sidebar nav { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 4px; }
.docs-sidebar nav a { display: block; padding: 6px; font-size: 12px; }
.docs-sidebar .protocol-link { display: none; }
.docs-sidebar .eyebrow { margin-bottom: 10px; }
}
@media (max-width:520px) {
.docs-page header nav a:nth-child(2), .docs-page header nav a:nth-child(3) { display: none; }
.doc-cards { grid-template-columns: 1fr; }
.doc-content h1 { font-size: 34px; }
.doc-content pre { font-size: 12px; padding-left: 14px; padding-right: 14px; }
.doc-content .doc-lead { font-size: 17px; }
}

.doc-content .heading-link { color: inherit; text-decoration: none; }
.doc-content .heading-link:hover { text-decoration: underline; }
28 changes: 28 additions & 0 deletions src/website/docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
document.querySelectorAll('.doc-content pre').forEach((pre, index) => {
const code = pre.querySelector('code');
if (!code) return;
if (window.hljs) hljs.highlightElement(code);
const wrapper = document.createElement('div');
wrapper.className = 'doc-code';
pre.before(wrapper);
wrapper.append(pre);
const button = document.createElement('button');
button.type = 'button';
button.className = 'doc-copy';
button.textContent = 'Copy';
button.setAttribute('aria-label', `Copy code example ${index + 1}`);
const status = document.createElement('span');
status.className = 'sr-only';
status.setAttribute('role', 'status');
wrapper.append(button, status);
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(code.textContent);
button.textContent = 'Copied';
status.textContent = 'Code copied to clipboard';
} catch {
button.textContent = 'Select to copy';
status.textContent = 'Clipboard unavailable. Select the code to copy it.';
}
});
});
Loading