Update AGENT.md to reflect current project structure and philosophy Add comprehensive architecture documentation covering: - Directory layout - Stable contracts - Request flow - Module dependencies - Page vs list detection - Deployment models - Demo fallback Remove outdated plugin system documentation Add new content system documentation Add configuration documentation Add context API documentation Add hooks and plugins documentation Add templates documentation Add rendering documentation Add development environment documentation
6.9 KiB
Architecture
Directory Layout
app/ # Framework core — stable API surface
router.php # Entry point: all requests route here
constants.php # CONTENT_EXTENSIONS, COVER_IMAGE_EXTENSIONS
hooks.php # Hook enum + Hooks class
context.php # Context + Templates classes
config.php # createContext(): config merge + bootstrap
helpers.php # Utility functions (template resolution, extraction)
content.php # Content discovery, slug resolution, navigation
rendering.php # Markdown/HTML/PHP rendering, template wrapping
cache.php # Markdown render cache (/tmp/folderweb_cache)
plugins.php # PluginManager class
static.php # /app/* asset serving with traversal protection
vendor/ # Parsedown + ParsedownExtra (Markdown→HTML)
plugins/global/ # Built-in plugins (languages.php)
default/ # Demo/fallback theme (NOT for production use)
config.ini # Default configuration
templates/ # base.php, page.php, list.php, list-compact.php, list-grid.php
styles/ # Default stylesheet
languages/ # en.ini, no.ini
content/ # Demo content shown when custom/ has no content
custom/ # User layer — all site-specific work goes here
config.ini # Config overrides (merged over app/default/config.ini)
templates/ # Override any template by matching filename
styles/ # Stylesheets served via /app/styles/*
fonts/ # Fonts served via /app/fonts/*
assets/ # Static files served at document root (favicon, robots.txt)
languages/ # Translation overrides/additions (*.ini)
plugins/global/ # Custom global plugins
plugins/page/ # Custom page plugins (reserved, not yet active)
content/ # Website content (= document root in production)
devel/ # Dev environment (Containerfile, compose, Apache, perf tools)
docs/ # Documentation (01-03 human-facing, 04 machine-facing)
Stable Contracts
app/ is the framework. When developing custom/ sites, never modify app/. When developing the framework itself, preserve these contracts:
| Contract | Guaranteed Behavior |
|---|---|
| Override chain | custom/* always takes priority over app/default/* for templates, styles, languages, config |
| Template names | base.php, page.php, list.php are the three core template names |
| Hook enum | Hook::CONTEXT_READY, Hook::PROCESS_CONTENT, Hook::TEMPLATE_VARS — signatures documented in 05-hooks-plugins.md |
| Context API | $ctx->set(), $ctx->get(), $ctx->has() — stable key/value store |
| Content extensions | md, html, php — defined in CONTENT_EXTENSIONS |
| Config format | INI with sections, merged via array_replace_recursive |
| Metadata format | INI file named metadata.ini in content directories |
| URL structure | Folder path = URL path, with slug overrides via metadata |
| Plugin locations | app/plugins/{scope}/ and custom/plugins/{scope}/ |
| Asset routes | /app/styles/* → custom/styles/, /app/fonts/* → custom/fonts/, /app/default-styles/* → app/default/styles/ |
| Trailing slash | Pages and lists enforce trailing slash via 301 redirect |
Request Flow
Browser request
│
├─ Apache rewrite: all non-/app/ requests → app/router.php
│
▼
router.php
│
├─ 1. Load modules (constants, hooks, context, helpers, plugins, config, content, rendering)
├─ 2. createContext()
│ ├─ Parse + merge config (default ← custom)
│ ├─ loadGlobalPlugins() → fires Hook::CONTEXT_READY
│ ├─ Determine contentDir (custom content or demo fallback)
│ ├─ Parse REQUEST_URI → requestPath
│ └─ Resolve template paths (custom fallback to default)
│
├─ 3. Check custom/assets/{path} → serve static file + exit
├─ 4. Check content/{path} for static asset (css/img/pdf/font) → serve + exit
│
├─ 5. Empty path? → frontpage: findAllContentFiles + renderMultipleFiles
│
└─ 6. parseRequestPath() → {type, path}
│
├─ "page": trailing slash redirect → findAllContentFiles → renderMultipleFiles
│ (fires Hook::PROCESS_CONTENT for file filtering)
│ (fires Hook::TEMPLATE_VARS before template render)
│ Template chain: content → page.php → base.php
│
├─ "list": trailing slash redirect → build items array from subdirectories
│ ├─ Check hide_list metadata → treat as page if true
│ ├─ Select list template from metadata page_template
│ ├─ For each subdir: loadMetadata, extractTitle, extractDateFromFolder, findCoverImage
│ ├─ Sort items by date (metadata `order` = ascending|descending)
│ ├─ Fire Hook::TEMPLATE_VARS
│ └─ Template chain: items → list-*.php → base.php
│
└─ "not_found": 404 response
Module Dependency Order
Loaded sequentially in router.php:
constants.php → hooks.php → context.php → helpers.php → plugins.php → config.php → content.php → rendering.php
config.php calls createContext() which triggers plugin loading, so hooks.php and plugins.php must be loaded before it.
Page vs List Detection
A resolved directory becomes a list if it contains subdirectories, otherwise a page. Override with hide_list = true in metadata to force page rendering on directories with children.
Deployment Models
Framework development (this repo)
Both app/ and custom/ live in the same repository. custom/ holds demo/test overrides.
Site development (separate repo)
The site is its own git repository containing custom/, content, and deployment config. app/ is included as a symlink, git submodule, or copied directory pointing to a specific framework version. The site repo never modifies app/.
Typical site repo layout:
my-site/ # Site git repo
app/ → ../folderweb/app # Symlink to framework (or submodule, or copy)
custom/ # Site-specific templates, styles, plugins, config
content/ # Website content (often the document root)
devel/ # Site's own dev environment config (optional)
Either direction works: app/ symlinked into a site repo, or custom/ symlinked into the framework repo during development.
Demo Fallback
When content/ (document root) has no files, app/default/content/ is used automatically. This is demo mode only — production sites always provide their own content via the document root.