folderweb/docs/04-development/01-architecture.md
Ruben b03511f99b Update AGENT.md and add architecture documentation
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
2026-02-05 23:30:44 +01:00

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.