v0.2.1 release

This commit is contained in:
Michael Freno
2025-11-16 09:36:46 -05:00
parent 4142b7e831
commit 39ccf0c450
26 changed files with 11070 additions and 220 deletions

0
docs/.nojekyll Normal file
View File

View File

@@ -4,23 +4,28 @@ This directory contains auto-generated API documentation from LuaLS annotations.
## Files
- **api.html** - Beautiful, searchable API documentation (2.2MB)
- **index.html** - GitHub Pages landing page
- **doc.md** - Markdown API reference (47,000+ lines)
- **doc.json** - JSON API reference for tooling (11MB)
- **build-docs.js** - Node.js script to convert markdown to HTML
- **package.json** - Node.js dependencies for HTML generation
- **.nojekyll** - Tells GitHub Pages to bypass Jekyll processing
- **doc.md** - Raw markdown (gitignored, 960KB)
- **doc.json** - Raw JSON (gitignored, 11MB)
## Regenerating Documentation
To regenerate the documentation after making changes:
```bash
./generate_docs.sh
./scripts/generate_docs.sh
```
Or manually:
```bash
lua-language-server --doc=. --doc_out_path=./docs
```
This will:
1. Extract version from `FlexLove.lua` (single source of truth)
2. Generate markdown from LuaLS annotations
3. Convert to beautiful, searchable HTML with syntax highlighting
4. Create navigation sidebar with search functionality
5. Display version in page titles and headers
## Viewing Locally
@@ -66,4 +71,66 @@ The documentation is generated from LuaLS (Lua Language Server) annotations usin
## Requirements
- lua-language-server (install via `brew install lua-language-server` on macOS)
- **lua-language-server** - For generating markdown from annotations
- macOS: `brew install lua-language-server`
- Linux: See https://github.com/LuaLS/lua-language-server
- **Node.js** - For converting markdown to beautiful HTML
- macOS: `brew install node`
- Linux: Use your package manager or https://nodejs.org
## Features
The generated HTML documentation includes:
- 🔍 **Live search** - Find classes and methods instantly
- 📱 **Responsive design** - Works on all devices
- 🌙 **Dark theme** - Easy on the eyes
- 🎨 **Syntax highlighting** - Code examples are beautifully formatted
- 🗂️ **Collapsible navigation** - Organized class/method structure
- ⚡ **Fast** - Single-page application, no page reloads
- 🎯 **Filtered** - Only user-facing classes, no internal implementation
- 🏷️ **Versioned** - Auto-displays version from `FlexLove.lua`
## Customizing Documentation
Edit `doc-filter.js` to control which classes appear in the documentation:
```javascript
module.exports = {
// Whitelist mode: Only these classes will be included
include: [
'Animation',
'Color',
'Element',
'Theme',
// ... add more
],
// Blacklist mode: These classes will be excluded
exclude: [
'Context',
'Performance',
// ... add more
],
// Which mode to use
mode: 'whitelist' // or 'blacklist'
};
```
**Current filter:** Whitelist mode with 20 classes (down from 33)
## Version Management
The documentation automatically pulls the version from `FlexLove.lua`:
```lua
flexlove._VERSION = "0.2.0" -- Single source of truth
```
To update the version:
1. Change `_VERSION` in `FlexLove.lua`
2. Run `./scripts/generate_docs.sh`
3. Version appears in page titles, sidebar, and footer
See `VERSIONING.md` for detailed version management workflow.

80
docs/VERSIONING.md Normal file
View File

@@ -0,0 +1,80 @@
# Version Management for FlexLöve Documentation
The documentation automatically pulls the version from `FlexLove.lua` and displays it throughout the docs.
## How It Works
1. **Version Source**: `FlexLove.lua` contains the authoritative version:
```lua
flexlove._VERSION = "0.2.0"
```
2. **Automatic Detection**: The build script reads this value and injects it into:
- Page title: `FlexLöve v0.2.0 - API Reference`
- Sidebar header: `FlexLöve v0.2.0`
- Landing page: `FlexLöve v0.2.0`
3. **Single Source of Truth**: Update the version in ONE place (`FlexLove.lua`) and docs auto-update
## Updating the Version
### Option 1: Manual Update
Edit `FlexLove.lua`:
```lua
flexlove._VERSION = "0.3.0" -- Change here
```
Then regenerate docs:
```bash
./scripts/generate_docs.sh
```
### Option 2: Script-Based (Recommended for Releases)
Create a release script that:
1. Updates version in `FlexLove.lua`
2. Regenerates documentation
3. Commits changes
4. Tags release
Example `release.sh`:
```bash
#!/bin/bash
NEW_VERSION=$1
if [ -z "$NEW_VERSION" ]; then
echo "Usage: ./release.sh <version>"
echo "Example: ./release.sh 0.3.0"
exit 1
fi
# Update version in FlexLove.lua
sed -i '' "s/flexlove._VERSION = \".*\"/flexlove._VERSION = \"$NEW_VERSION\"/" FlexLove.lua
# Regenerate docs
./scripts/generate_docs.sh
# Commit and tag
git add FlexLove.lua docs/
git commit -m "Release v$NEW_VERSION"
git tag "v$NEW_VERSION"
echo "✓ Released v$NEW_VERSION"
echo "Don't forget to: git push && git push --tags"
```
## Version Display Locations
- **API Reference** (`api.html`):
- Browser tab title
- Sidebar header (smaller, grayed out)
- **Landing Page** (`index.html`):
- Footer: "FlexLöve v0.2.0 | MIT License"
## Future Enhancements
Consider adding:
- **CHANGELOG.md** - Track changes between versions
- **Version dropdown** - View docs for older versions
- **GitHub Releases link** - Link to release notes
- **Breaking changes banner** - Warn users about API changes

153
docs/WORKFLOW.md Normal file
View File

@@ -0,0 +1,153 @@
# Documentation Workflow
## Overview
FlexLöve's documentation system automatically manages versioning and archival. When you generate new documentation, the previous version is automatically archived.
## How It Works
### 1. Manual Documentation Updates (No Version Change)
When you update annotations without bumping the version:
```bash
./scripts/generate_docs.sh
```
**What happens:**
- Script detects current version (e.g., v0.2.0) from `docs/api.html`
- Compares with `FlexLove.lua` version
- If versions match: **Overwrites** `docs/api.html` (same version)
- Previous archived version remains unchanged
**Use case:** You added better documentation, fixed typos, or improved examples without releasing a new version.
### 2. Version Bump (New Release)
When you bump the version in `FlexLove.lua`:
```bash
# 1. Update version in FlexLove.lua
# flexlove._VERSION = "0.3.0"
# 2. Generate documentation
./scripts/generate_docs.sh
```
**What happens:**
- Script detects old version (v0.2.0) from `docs/api.html`
- Compares with new version (v0.3.0) from `FlexLove.lua`
- **Archives** old `docs/api.html``docs/versions/v0.2.0/api.html`
- Generates new `docs/api.html` for v0.3.0
### 3. Automated Release (via GitHub Actions)
When you push a git tag:
```bash
git tag v0.3.0
git push origin v0.3.0
```
**What happens:**
1. GitHub Actions workflow triggers
2. Archives previous documentation version
3. Generates new documentation for v0.3.0
4. Commits both archived and new docs to repository
5. Creates release package with checksums
6. Creates GitHub release with assets
## Directory Structure
```
docs/
├── api.html # Always the LATEST version
├── index.html # Landing page
└── versions/
├── v0.1.0/
│ └── api.html # Documentation for v0.1.0
├── v0.2.0/
│ └── api.html # Documentation for v0.2.0
└── v0.3.0/
└── api.html # Documentation for v0.3.0
```
## Version Detection
The system automatically detects versions by:
1. **Current docs version**: Reads from `docs/api.html` header (`FlexLöve v0.2.0`)
2. **Code version**: Reads from `FlexLove.lua` (`flexlove._VERSION = "0.2.0"`)
### Behavior Matrix
| Old Version | New Version | Action |
|-------------|-------------|--------|
| v0.2.0 | v0.2.0 | Overwrite current (same version update) |
| v0.2.0 | v0.3.0 | Archive v0.2.0, generate v0.3.0 |
| None | v0.2.0 | Generate v0.2.0 (first time) |
## Examples
### Scenario 1: Fix Documentation Typo
```bash
# Fix typo in annotations
# Version still 0.2.0 in FlexLove.lua
./scripts/generate_docs.sh
# Output: "Same version (v0.2.0), will overwrite current documentation"
# Result: docs/api.html updated, no archival
```
### Scenario 2: Release New Version
```bash
# Update FlexLove.lua
# flexlove._VERSION = "0.3.0"
./scripts/generate_docs.sh
# Output: "Found previous version v0.2.0, archiving before generating new docs..."
# Output: "✓ Archived previous documentation to docs/versions/v0.2.0/"
# Result:
# - docs/versions/v0.2.0/api.html (archived)
# - docs/api.html (new v0.3.0)
```
### Scenario 3: Automated Release
```bash
# Tag and push
git tag v0.3.0
git push origin v0.3.0
# GitHub Actions will:
# 1. Archive v0.2.0 automatically
# 2. Generate v0.3.0 docs
# 3. Commit both to repository
# 4. Create GitHub release
```
## Benefits
**No manual archival needed** - Automatically handled
**Safe overwrites** - Same version updates won't create duplicate archives
**Version history preserved** - All previous versions accessible
**Seamless workflow** - Just run `./scripts/generate_docs.sh`
**Automated releases** - Tag and forget
## Version Dropdown
Users can access any version via the dropdown in the documentation header:
- Current version shows "(Latest)" badge
- Previous versions listed chronologically
- Click to navigate to archived documentation
## Manual Archival (If Needed)
If you ever need to manually archive a version:
```bash
./scripts/archive-docs.sh
```
This creates `docs/versions/v{version}/api.html` based on the current `FlexLove.lua` version.

3803
docs/api.html Normal file

File diff suppressed because it is too large Load Diff

683
docs/build-docs.js Normal file
View File

@@ -0,0 +1,683 @@
const fs = require('fs');
const path = require('path');
const MarkdownIt = require('markdown-it');
const anchor = require('markdown-it-anchor');
const hljs = require('highlight.js');
const filter = require('./doc-filter');
// Extract version from FlexLove.lua
function getVersion() {
try {
const flexlovePath = path.join(__dirname, '..', 'FlexLove.lua');
const content = fs.readFileSync(flexlovePath, 'utf8');
const match = content.match(/flexlove\._VERSION\s*=\s*["']([^"']+)["']/);
return match ? match[1] : 'unknown';
} catch (e) {
return 'unknown';
}
}
const VERSION = getVersion();
console.log(`Building docs for FlexLöve v${VERSION}`);
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (__) {}
}
return '';
}
}).use(anchor, {
permalink: anchor.permalink.headerLink()
});
// Read the markdown file
let markdownContent = fs.readFileSync(path.join(__dirname, 'doc.md'), 'utf8');
// Filter content based on doc-filter.js configuration
function filterMarkdown(content) {
const lines = content.split('\n');
const filtered = [];
let currentClass = null;
let skipUntilNextClass = false;
let classContent = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const h1Match = line.match(/^# (.+)$/);
if (h1Match) {
// New class found - decide if we should keep previous class
if (currentClass && !skipUntilNextClass) {
filtered.push(...classContent);
}
currentClass = h1Match[1];
classContent = [line];
// Check if this class should be included
if (filter.mode === 'whitelist') {
skipUntilNextClass = !filter.include.includes(currentClass);
} else {
skipUntilNextClass = filter.exclude.includes(currentClass);
}
} else {
classContent.push(line);
}
}
// Don't forget the last class
if (currentClass && !skipUntilNextClass) {
filtered.push(...classContent);
}
return filtered.join('\n');
}
markdownContent = filterMarkdown(markdownContent);
// Sort properties: public first, then internal (prefixed with _)
function sortAndInjectWarning(content) {
const lines = content.split('\n');
const result = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
// Check if this is a class heading (h1)
if (line.match(/^# .+$/)) {
// Found a class, collect all its properties/methods
result.push(line);
i++;
const properties = [];
let currentProperty = null;
// Collect all properties until next class or end of file
while (i < lines.length && !lines[i].match(/^# .+$/)) {
const propLine = lines[i];
const h2Match = propLine.match(/^## (.+)$/);
if (h2Match) {
// Save previous property if exists
if (currentProperty) {
properties.push(currentProperty);
}
// Start new property
currentProperty = {
name: h2Match[1],
lines: [propLine],
isInternal: h2Match[1].startsWith('_')
};
} else if (currentProperty) {
// Add line to current property
currentProperty.lines.push(propLine);
} else {
// Line before any property (e.g., class description)
result.push(propLine);
}
i++;
}
// Save last property
if (currentProperty) {
properties.push(currentProperty);
}
// Sort: public first, then internal
const publicProps = properties.filter(p => !p.isInternal);
const internalProps = properties.filter(p => p.isInternal);
// Add public properties
publicProps.forEach(prop => {
result.push(...prop.lines);
});
// Add warning and internal properties if any exist
if (internalProps.length > 0) {
result.push('');
result.push('---');
result.push('');
result.push('## ⚠️ Internal Properties');
result.push('');
result.push('> **Warning:** The following properties are internal implementation details and should not be accessed directly. They are prefixed with `_` to indicate they are private. Accessing these properties may break in future versions without notice.');
result.push('');
result.push('---');
result.push('');
internalProps.forEach(prop => {
result.push(...prop.lines);
});
}
} else {
result.push(line);
i++;
}
}
return result.join('\n');
}
markdownContent = sortAndInjectWarning(markdownContent);
// Parse markdown structure to build navigation
const lines = markdownContent.split('\n');
const navigation = [];
let currentClass = null;
lines.forEach(line => {
const h1Match = line.match(/^# (.+)$/);
const h2Match = line.match(/^## (.+)$/);
if (h1Match) {
currentClass = {
name: h1Match[1],
id: h1Match[1].toLowerCase().replace(/[^a-z0-9]+/g, '-'),
members: []
};
navigation.push(currentClass);
} else if (h2Match && currentClass) {
currentClass.members.push({
name: h2Match[1],
id: h2Match[1].toLowerCase().replace(/[^a-z0-9]+/g, '-')
});
}
});
// Scan for available documentation versions
function getAvailableVersions() {
const versionsDir = path.join(__dirname, 'versions');
const versions = [];
try {
if (fs.existsSync(versionsDir)) {
const entries = fs.readdirSync(versionsDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name.startsWith('v')) {
const apiPath = path.join(versionsDir, entry.name, 'api.html');
if (fs.existsSync(apiPath)) {
versions.push(entry.name);
}
}
}
}
} catch (e) {
console.warn('Warning: Could not scan versions directory:', e.message);
}
// Sort versions (newest first)
versions.sort((a, b) => {
const parseVersion = (v) => {
const parts = v.substring(1).split('.').map(Number);
return parts[0] * 10000 + parts[1] * 100 + parts[2];
};
return parseVersion(b) - parseVersion(a);
});
return versions;
}
const availableVersions = getAvailableVersions();
console.log(`Found ${availableVersions.length} archived version(s):`, availableVersions.join(', '));
// Convert markdown to HTML
const htmlContent = md.render(markdownContent);
// Create HTML template
const template = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlexLöve v${VERSION} - API Reference</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
background-color: #0d1117;
color: #c9d1d9;
line-height: 1.6;
}
.container {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 280px;
background-color: #161b22;
border-right: 1px solid #30363d;
position: fixed;
height: 100vh;
overflow-y: auto;
padding: 20px;
}
.sidebar-header {
padding-bottom: 15px;
border-bottom: 1px solid #30363d;
margin-bottom: 15px;
}
.sidebar-header h2 {
color: #58a6ff;
font-size: 1.2rem;
}
.sidebar-header a {
color: #8b949e;
text-decoration: none;
font-size: 0.9rem;
display: block;
margin-top: 5px;
}
.sidebar-header a:hover {
color: #58a6ff;
}
#search {
width: 100%;
padding: 8px 12px;
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #c9d1d9;
font-size: 14px;
margin-bottom: 15px;
}
#search:focus {
outline: none;
border-color: #58a6ff;
}
.nav-section {
margin-bottom: 15px;
}
.nav-class {
color: #c9d1d9;
font-weight: 600;
padding: 8px 12px;
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s;
display: block;
text-decoration: none;
}
.nav-class:hover {
background-color: #21262d;
}
.nav-members {
display: none;
padding-left: 20px;
margin-top: 5px;
}
.nav-members.active {
display: block;
}
.nav-member {
color: #8b949e;
padding: 4px 12px;
font-size: 0.9rem;
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s;
display: block;
text-decoration: none;
}
.nav-member:hover {
background-color: #21262d;
color: #c9d1d9;
}
.content {
margin-left: 280px;
flex: 1;
padding: 40px 60px;
max-width: 1200px;
}
.content h1 {
color: #58a6ff;
font-size: 2rem;
margin: 2rem 0 1rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid #30363d;
}
.content h1:first-child {
margin-top: 0;
}
.content h2 {
color: #79c0ff;
font-size: 1.5rem;
margin: 1.5rem 0 0.8rem 0;
font-family: 'Courier New', monospace;
}
.content h3 {
color: #c9d1d9;
font-size: 1.2rem;
margin: 1.2rem 0 0.6rem 0;
}
.content p {
margin: 0.8rem 0;
color: #c9d1d9;
}
.content code {
background-color: #161b22;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.9em;
}
.content pre {
background-color: #161b22;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
margin: 1rem 0;
border: 1px solid #30363d;
}
.content pre code {
background-color: transparent;
padding: 0;
}
.content a {
color: #58a6ff;
text-decoration: none;
}
.content a:hover {
text-decoration: underline;
}
.content ul, .content ol {
margin: 0.8rem 0;
padding-left: 2rem;
}
.content li {
margin: 0.4rem 0;
}
.content table {
border-collapse: collapse;
width: 100%;
margin: 1rem 0;
}
.content th, .content td {
border: 1px solid #30363d;
padding: 8px 12px;
text-align: left;
}
.content th {
background-color: #161b22;
font-weight: 600;
}
.content blockquote {
background-color: #1c2128;
border-left: 4px solid #f85149;
padding: 12px 16px;
margin: 1rem 0;
border-radius: 6px;
}
.content blockquote p {
margin: 0.4rem 0;
}
.content blockquote strong {
color: #f85149;
}
.content hr {
border: none;
border-top: 1px solid #30363d;
margin: 2rem 0;
}
.version-selector {
margin-top: 10px;
position: relative;
}
.version-selector select {
width: 100%;
padding: 8px 12px;
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
color: #c9d1d9;
font-size: 14px;
cursor: pointer;
appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg fill="%238b949e" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z"/></svg>');
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 12px;
padding-right: 32px;
}
.version-selector select:hover {
background-color: #161b22;
border-color: #58a6ff;
}
.version-selector select:focus {
outline: none;
border-color: #58a6ff;
}
.version-badge {
display: inline-block;
background-color: #238636;
color: #fff;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
margin-left: 6px;
font-weight: 600;
}
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
background-color: #21262d;
border: 1px solid #30363d;
border-radius: 6px;
padding: 10px 15px;
color: #c9d1d9;
text-decoration: none;
opacity: 0;
transition: opacity 0.3s;
}
.back-to-top.visible {
opacity: 1;
}
.back-to-top:hover {
background-color: #30363d;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s;
z-index: 1000;
}
.sidebar.mobile-open {
transform: translateX(0);
}
.content {
margin-left: 0;
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<nav class="sidebar">
<div class="sidebar-header">
<h2>FlexLöve <span style="font-size: 0.6em; color: #8b949e;">v${VERSION}</span></h2>
<a href="index.html">← Back to Home</a>
${availableVersions.length > 0 ? `
<div class="version-selector">
<select id="version-dropdown" onchange="window.versionNavigate(this.value)">
<option value="">📚 Switch Version</option>
<option value="current">v${VERSION} (Latest)</option>
${availableVersions.map(v => `<option value="${v}">` + v + '</option>').join('\n ')}
</select>
</div>
` : ''}
</div>
<input type="text" id="search" placeholder="Search API...">
<div id="nav-content">
${navigation.map(cls => `
<div class="nav-section" data-class="${cls.name.toLowerCase()}">
<a href="#${cls.id}" class="nav-class">${cls.name}</a>
<div class="nav-members">
${cls.members.map(member => ` <a href="#${member.id}" class="nav-member">${member.name}</a>`).join('\n')}
</div>
</div>
`).join('')}
</div>
</nav>
<main class="content">
${htmlContent}
</main>
</div>
<a href="#" class="back-to-top" id="backToTop">↑ Top</a>
<script>
// Search functionality
const searchInput = document.getElementById('search');
const navSections = document.querySelectorAll('.nav-section');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
navSections.forEach(section => {
const className = section.querySelector('.nav-class').textContent.toLowerCase();
const members = section.querySelectorAll('.nav-member');
let hasMatch = className.includes(query);
members.forEach(member => {
const memberName = member.textContent.toLowerCase();
if (memberName.includes(query)) {
member.style.display = 'block';
hasMatch = true;
} else {
member.style.display = 'none';
}
});
section.style.display = hasMatch ? 'block' : 'none';
if (hasMatch && query) {
section.querySelector('.nav-members').classList.add('active');
}
});
});
// Expand/collapse navigation
document.querySelectorAll('.nav-class').forEach(navClass => {
navClass.addEventListener('click', (e) => {
const members = navClass.nextElementSibling;
members.classList.toggle('active');
});
});
// Back to top button
const backToTop = document.getElementById('backToTop');
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
backToTop.classList.add('visible');
} else {
backToTop.classList.remove('visible');
}
});
backToTop.addEventListener('click', (e) => {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// Auto-expand current section
const currentHash = window.location.hash;
if (currentHash) {
const section = document.querySelector(\`[href="\${currentHash}"]\`)?.closest('.nav-section');
if (section) {
section.querySelector('.nav-members').classList.add('active');
}
}
// Version navigation
window.versionNavigate = function(value) {
if (!value) return;
if (value === 'current') {
// Navigate to current/latest version (root api.html)
const currentPath = window.location.pathname;
if (currentPath.includes('/versions/')) {
// We're in a versioned doc, go back to root
window.location.href = '../../api.html';
}
// Already on current, do nothing
} else {
// Navigate to specific version
const currentPath = window.location.pathname;
if (currentPath.includes('/versions/')) {
// We're in a versioned doc, navigate to sibling version
window.location.href = \`../\${value}/api.html\`;
} else {
// We're in root, navigate to versions subdirectory
window.location.href = \`versions/\${value}/api.html\`;
}
}
};
</script>
</body>
</html>`;
// Write the HTML file
fs.writeFileSync(path.join(__dirname, 'api.html'), template, 'utf8');
console.log('✓ Generated api.html');

47
docs/doc-filter.js Normal file
View File

@@ -0,0 +1,47 @@
// Classes to INCLUDE in documentation (whitelist approach)
// Only these classes and their related types will appear in the docs
module.exports = {
// Main user-facing classes
include: [
"Animation",
"AnimationProps",
//"Border",
"Color",
"Element",
"ElementProps",
"EventHandler",
"FlexLove",
"FontFamily",
"InputEvent",
//"InputEventProps",
//"LayoutEngine",
//"LayoutEngineProps",
"Theme",
"ThemeComponent",
"ThemeDefinition",
"TextEditor",
"TextEditorConfig",
"TransformProps",
"TransitionProps",
],
// Alternative: exclude specific classes (blacklist)
exclude: [
"Context",
"Performance",
"Trace",
"Proto",
"LuaLS",
"ImageCache",
"ImageRenderer",
"Renderer",
"StateManager",
"ScrollManager",
"ErrorCodes",
"ThemeManager",
"ThemeRegion",
],
// Which mode to use: 'whitelist' or 'blacklist'
mode: "whitelist",
};

View File

@@ -430,10 +430,10 @@
16
],
"type": "doc.type.table",
"view": "{ width: number?, height: number?, opacity: number? }"
"view": "{ width: number, height: number, opacity: number }"
}
],
"view": "{ width: number?, height: number?, opacity: number? }"
"view": "{ width: number, height: number, opacity: number }"
},
"file": "modules/Animation.lua",
"finish": [
@@ -446,7 +446,7 @@
10
],
"type": "doc.field",
"view": "{ width: number?, height: number?, opacity: number? }",
"view": "{ width: number, height: number, opacity: number }",
"visible": "public"
},
{
@@ -736,10 +736,10 @@
16
],
"type": "doc.type.table",
"view": "{ width: number?, height: number?, opacity: number? }"
"view": "{ width: number, height: number, opacity: number }"
}
],
"view": "{ width: number?, height: number?, opacity: number? }"
"view": "{ width: number, height: number, opacity: number }"
},
"file": "modules/Animation.lua",
"finish": [
@@ -752,7 +752,7 @@
10
],
"type": "doc.field",
"view": "{ width: number?, height: number?, opacity: number? }",
"view": "{ width: number, height: number, opacity: number }",
"visible": "public"
},
{

View File

@@ -49,7 +49,7 @@ function Animation.fade(duration: number, fromOpacity: number, toOpacity: number
```lua
{ width: number?, height: number?, opacity: number? }
{ width: number, height: number, opacity: number }
```
## interpolate
@@ -86,7 +86,7 @@ function Animation.scale(duration: number, fromScale: table, toScale: table)
```lua
{ width: number?, height: number?, opacity: number? }
{ width: number, height: number, opacity: number }
```
## transform

1075
docs/examples.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,152 +1,237 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FlexLöve Documentation</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css"
/>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
background-color: #0d1117;
color: #c9d1d9;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.header {
text-align: center;
padding: 3rem 0;
border-bottom: 1px solid #30363d;
}
.header h1 {
font-size: 3rem;
margin: 0;
background: linear-gradient(45deg, #58a6ff, #79c0ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
font-size: 1.2rem;
color: #8b949e;
margin: 1rem 0;
}
.nav {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin: 2rem 0;
}
.nav a {
padding: 0.75rem 1.5rem;
background-color: #21262d;
color: #c9d1d9;
text-decoration: none;
border-radius: 6px;
border: 1px solid #30363d;
transition: all 0.2s;
}
.nav a:hover {
background-color: #30363d;
border-color: #58a6ff;
}
.markdown-body {
background-color: #0d1117;
padding: 2rem;
border-radius: 6px;
}
.section {
margin: 3rem 0;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 3rem 0;
}
.feature-card {
background-color: #161b22;
padding: 2rem;
border-radius: 6px;
border: 1px solid #30363d;
}
.feature-card h3 {
color: #58a6ff;
margin-top: 0;
}
code {
background-color: #161b22;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
}
pre {
background-color: #161b22;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
}
.footer {
text-align: center;
padding: 3rem 0;
border-top: 1px solid #30363d;
margin-top: 4rem;
color: #8b949e;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
Helvetica, Arial, sans-serif;
background-color: #0d1117;
color: #c9d1d9;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.header {
text-align: center;
padding: 3rem 0;
border-bottom: 1px solid #30363d;
}
.header h1 {
font-size: 3rem;
margin: 0;
background: linear-gradient(45deg, #58a6ff, #79c0ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
font-size: 1.2rem;
color: #8b949e;
margin: 1rem 0;
}
.nav {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin: 2rem 0;
}
.nav a {
padding: 0.75rem 1.5rem;
background-color: #21262d;
color: #c9d1d9;
text-decoration: none;
border-radius: 6px;
border: 1px solid #30363d;
transition: all 0.2s;
}
.nav a:hover {
background-color: #30363d;
border-color: #58a6ff;
}
.markdown-body {
background-color: #0d1117;
padding: 2rem;
border-radius: 6px;
}
.section {
margin: 3rem 0;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 3rem 0;
}
.feature-card {
background-color: #161b22;
padding: 2rem;
border-radius: 6px;
border: 1px solid #30363d;
transition: all 0.3s ease;
cursor: pointer;
text-decoration: none;
display: block;
color: inherit;
}
.feature-card:hover {
background-color: #1c2128;
border-color: #58a6ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(88, 166, 255, 0.15);
}
.feature-card h3 {
color: #58a6ff;
margin-top: 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.feature-card h3::after {
content: "→";
font-size: 1.5rem;
opacity: 0;
transition: opacity 0.3s ease;
}
.feature-card:hover h3::after {
opacity: 1;
}
.feature-card p {
color: #8b949e;
margin: 0.5rem 0 0 0;
}
code {
background-color: #161b22;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo,
monospace;
}
pre {
background-color: #161b22;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
position: relative;
}
.copy-button {
position: absolute;
top: 8px;
right: 8px;
background-color: #21262d;
color: #8b949e;
border: 1px solid #30363d;
border-radius: 6px;
padding: 6px 12px;
font-size: 12px;
cursor: pointer;
opacity: 0;
transition: all 0.2s;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
pre:hover .copy-button {
opacity: 1;
}
.copy-button:hover {
background-color: #30363d;
border-color: #58a6ff;
color: #c9d1d9;
}
.copy-button:active {
background-color: #238636;
border-color: #238636;
color: #ffffff;
}
.copy-button.copied {
background-color: #238636;
border-color: #238636;
color: #ffffff;
}
.footer {
text-align: center;
padding: 3rem 0;
border-top: 1px solid #30363d;
margin-top: 4rem;
color: #8b949e;
}
</style>
</head>
<body>
</head>
<body>
<div class="container">
<div class="header">
<h1>FlexLöve</h1>
<p>A comprehensive UI library for LÖVE Framework based on flexbox</p>
</div>
<div class="header">
<h1>FlexLöve</h1>
<p>A comprehensive UI library for LÖVE Framework based on flexbox</p>
</div>
<div class="nav">
<a href="https://github.com/mikefreno/FlexLove">GitHub</a>
<a href="#getting-started">Getting Started</a>
<a href="doc.md">API Reference (Markdown)</a>
<a href="doc.json">API Reference (JSON)</a>
</div>
<div class="nav">
<a href="https://github.com/mikefreno/FlexLove">GitHub</a>
<a href="#getting-started">Getting Started</a>
<a href="examples.html">Examples</a>
<a href="api.html">API Reference</a>
</div>
<div class="section">
<div class="features">
<div class="feature-card">
<h3>🎨 Flexbox & Grid Layouts</h3>
<p>Modern CSS-like layout system with full flexbox and grid support for building responsive UIs.</p>
</div>
<div class="feature-card">
<h3>🎭 Theme System</h3>
<p>9-patch NinePatch theming with state support (normal, hover, pressed, disabled).</p>
</div>
<div class="feature-card">
<h3>✨ Animations</h3>
<p>Built-in animation support for smooth transitions and effects with easing functions.</p>
</div>
<div class="feature-card">
<h3>📱 Responsive Design</h3>
<p>Viewport units (vw, vh, %) for automatic resizing and responsive layouts.</p>
</div>
<div class="feature-card">
<h3>⚡ Immediate Mode</h3>
<p>Optional immediate mode support alongside traditional retained mode rendering.</p>
</div>
<div class="feature-card">
<h3>🎯 Event System</h3>
<p>Rich event handling with callbacks, focus management, and input fields.</p>
</div>
</div>
<div class="section">
<h2 style="text-align: center; margin-bottom: 2rem">✨ Features</h2>
<div class="features">
<a href="examples.html#layout" class="feature-card">
<h3>🎨 Flexbox & Grid Layouts</h3>
<p>
Modern CSS-like layout system with full flexbox and grid support
for building responsive UIs.
</p>
</a>
<a href="examples.html#theme" class="feature-card">
<h3>🎭 Theme System</h3>
<p>
9-patch NinePatch theming with state support (normal, hover,
pressed, disabled).
</p>
</a>
<a href="examples.html#state" class="feature-card">
<h3>✨ State Management</h3>
<p>
Interactive components with state tracking, counters, toggles, and
dynamic updates.
</p>
</a>
<a href="examples.html#scroll" class="feature-card">
<h3>📜 Scrollable Content</h3>
<p>
Smooth scrolling containers with backdrop blur effects and
overflow handling for long content lists.
</p>
</a>
<a href="examples.html#slider" class="feature-card">
<h3>🎚️ Sliders & Controls</h3>
<p>
Draggable sliders with value tracking, perfect for settings menus
and adjustable parameters.
</p>
</a>
<a href="examples.html#input" class="feature-card">
<h3>⌨️ Input & Events</h3>
<p>
Rich event handling with mouse, keyboard, and touch support. Focus
management and input fields.
</p>
</a>
</div>
</div>
<div class="section markdown-body" id="getting-started">
<h2>Quick Start</h2>
<pre><code class="language-lua">local FlexLove = require("FlexLove")
<div class="section" id="getting-started">
<h2>Quick Start</h2>
<pre><code class="language-lua">local FlexLove = require("FlexLove")
-- Initialize with base scaling and theme
FlexLove.init({
@@ -174,29 +259,70 @@ end
function love.draw()
FlexLove.draw()
end</code></pre>
</div>
</div>
<div class="section">
<h2>Documentation</h2>
<p>The API reference is available in two formats:</p>
<ul>
<li><a href="doc.md">Markdown format</a> - Easy to read in browser or convert to HTML</li>
<li><a href="doc.json">JSON format</a> - For programmatic access and tooling</li>
</ul>
<p>These docs are auto-generated from LuaLS annotations using the Lua Language Server.</p>
</div>
<div class="section">
<h2>Installation</h2>
<p>Add the <code>modules</code> directory and <code>FlexLove.lua</code> into your LÖVE project.</p>
<pre><code>git clone https://github.com/mikefreno/FlexLove.git
<div class="section">
<h2>Installation</h2>
<p>
Add the <code>modules</code> directory and
<code>FlexLove.lua</code> into your LÖVE project.
</p>
<pre><code>git clone https://github.com/mikefreno/FlexLove.git
cp -r FlexLove/modules your-project/
cp FlexLove/FlexLove.lua your-project/</code></pre>
</div>
</div>
<div class="footer">
<p>FlexLöve v0.2.0 | MIT License | <a href="https://github.com/mikefreno/FlexLove" style="color: #58a6ff;">GitHub Repository</a></p>
</div>
<div class="footer">
<p>
FlexLöve v0.2.0 | MIT License |
<a href="https://github.com/mikefreno/FlexLove" style="color: #58a6ff"
>GitHub Repository</a
>
</p>
</div>
</div>
</body>
<script>
hljs.highlightAll();
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute("href"));
if (target) {
target.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
});
// Add copy buttons to code blocks
document.querySelectorAll("pre code").forEach((codeBlock) => {
const pre = codeBlock.parentElement;
const button = document.createElement("button");
button.className = "copy-button";
button.textContent = "Copy";
button.title = "Copy to clipboard";
button.addEventListener("click", async () => {
const code = codeBlock.textContent;
try {
await navigator.clipboard.writeText(code);
button.textContent = "Copied!";
button.classList.add("copied");
setTimeout(() => {
button.textContent = "Copy";
button.classList.remove("copied");
}, 2000);
} catch (err) {
console.error("Failed to copy:", err);
button.textContent = "Failed";
setTimeout(() => {
button.textContent = "Copy";
}, 2000);
}
});
pre.appendChild(button);
});
</script>
</body>
</html>

125
docs/package-lock.json generated Normal file
View File

@@ -0,0 +1,125 @@
{
"name": "flexlove-docs",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flexlove-docs",
"version": "1.0.0",
"dependencies": {
"highlight.js": "^11.9.0",
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.0.0"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"license": "MIT",
"peer": true,
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-anchor": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-9.2.0.tgz",
"integrity": "sha512-sa2ErMQ6kKOA4l31gLGYliFQrMKkqSO0ZJgGhDHKijPf0pNFM9vghjAh3gn26pS4JDRs7Iwa9S36gxm3vgZTzg==",
"license": "Unlicense",
"peerDependencies": {
"@types/markdown-it": "*",
"markdown-it": "*"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
}
}
}

13
docs/package.json Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "flexlove-docs",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "node update-version.js && node build-docs.js"
},
"dependencies": {
"markdown-it": "^14.0.0",
"markdown-it-anchor": "^9.0.0",
"highlight.js": "^11.9.0"
}
}

34
docs/update-version.js Normal file
View File

@@ -0,0 +1,34 @@
const fs = require('fs');
const path = require('path');
// Extract version from FlexLove.lua
function getVersion() {
try {
const flexlovePath = path.join(__dirname, '..', 'FlexLove.lua');
const content = fs.readFileSync(flexlovePath, 'utf8');
const match = content.match(/flexlove\._VERSION\s*=\s*["']([^"']+)["']/);
return match ? match[1] : 'unknown';
} catch (e) {
return 'unknown';
}
}
// Update index.html with current version
function updateIndexVersion() {
const version = getVersion();
const indexPath = path.join(__dirname, 'index.html');
let content = fs.readFileSync(indexPath, 'utf8');
// Update version in multiple places
content = content.replace(
/FlexLöve v[\d.]+/g,
`FlexLöve v${version}`
);
fs.writeFileSync(indexPath, content, 'utf8');
console.log(`✓ Updated index.html to version ${version}`);
}
const version = getVersion();
console.log(`Current version: ${version}`);
updateIndexVersion();

86
docs/versions/README.md Normal file
View File

@@ -0,0 +1,86 @@
# FlexLöve Documentation Versions
This directory stores versioned snapshots of the FlexLöve API documentation.
## Purpose
Each time a new version of FlexLöve is released, the documentation is archived here so users can reference docs for specific versions they're using.
## Structure
```
docs/
├── api.html # Latest/current version documentation
├── index.html # Landing page with version selector
└── versions/
├── v0.2.0/
│ └── api.html # Documentation for v0.2.0
├── v0.3.0/
│ └── api.html # Documentation for v0.3.0
└── v1.0.0/
└── api.html # Documentation for v1.0.0
```
## Naming Convention
- Version directories follow the pattern: `v{major}.{minor}.{patch}`
- Examples: `v0.2.0`, `v1.0.0`, `v2.1.3`
- Each directory contains `api.html` (the full API documentation for that version)
## Access
### Via GitHub Pages
Once deployed, versions are accessible at:
- Latest: `https://{user}.github.io/{repo}/api.html`
- Specific version: `https://{user}.github.io/{repo}/versions/v0.2.0/api.html`
### Via Repository
Browse directly in the repository:
- `docs/api.html` - Always shows latest
- `docs/versions/v0.2.0/api.html` - Specific version
## Automation
Documentation versions are automatically archived when:
1. A new git tag is pushed (e.g., `git push origin v0.3.0`)
2. GitHub Actions workflow runs
3. Documentation is generated and archived
4. Version is committed back to the repository
See `.github/workflows/release.yml` for implementation details.
## Manual Archival
To manually archive a version:
```bash
# Generate docs for current version
./scripts/generate_docs.sh
# Create version directory
VERSION="0.2.0"
mkdir -p "docs/versions/v${VERSION}"
# Copy documentation
cp docs/api.html "docs/versions/v${VERSION}/api.html"
# Commit
git add docs/versions/
git commit -m "Archive documentation for v${VERSION}"
git push
```
## Storage Considerations
- Each `api.html` file is approximately 200-300KB
- Storing 10-20 versions = 2-6MB total
- GitHub has a 1GB repository soft limit (we're well within it)
- Old versions are kept indefinitely for reference
## Version Dropdown
The main documentation pages (`api.html` and `index.html`) include a version dropdown that automatically detects and lists all available versions from this directory.
Users can switch between versions without leaving the documentation.

File diff suppressed because it is too large Load Diff