folderweb/docs/04-development/07-rendering.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

4.5 KiB

Rendering & Caching

Content Rendering

renderContentFile(string $filePath, ?Context $ctx = null): string

Renders a single content file to HTML string based on extension:

Extension Rendering
md Parsedown + ParsedownExtra → HTML. Cached. Language prefix injected into internal links.
html Included directly (output buffered)
php Included with Hook::TEMPLATE_VARS variables extracted into scope

PHP content files receive variables from Hook::TEMPLATE_VARS (starting with an empty array). This includes plugin-provided variables like $translations, $currentLang, etc. However, page-level context like $metadata and $pageTitle is not included — those are only available in the wrapping template (page.php/list.php), not in content files.

Page Rendering

renderMultipleFiles(Context $ctx, array $files, string $pageDir): void

Used for both frontpage and page views:

  1. Load metadata for $pageDir
  2. Load page plugins (from metadata plugins field)
  3. Render all content files, concatenate HTML
  4. Compute: $pageTitle, $metaDescription, $pageCssUrl/$pageCssHash, $socialImageUrl
  5. Fire Hook::TEMPLATE_VARS with all variables
  6. extract() variables → render page.php → capture output as $content
  7. Render base.php with $content + base variables
  8. exit

List Rendering

Handled directly in router.php (not a separate function):

  1. Render directory's own content files as $pageContent
  2. Load metadata, check hide_list
  3. Select list template from page_template metadata
  4. Build $items array from subdirectories (metadata + extraction)
  5. Sort items by date
  6. Fire Hook::TEMPLATE_VARS
  7. Render list template → capture as $content
  8. Pass to renderTemplate() which renders base.php

renderTemplate(Context $ctx, string $content, int $statusCode = 200): void — Wraps content in base template. Used for list views and error pages.

Markdown Caching

Defined in app/cache.php. File-based cache in /tmp/folderweb_cache/.

Cache key: md5($filePath . $mtime . $langPrefix)

  • Invalidates when file is modified (mtime changes)
  • Invalidates per-language (different link rewriting)
  • No explicit TTL — entries persist until temp directory cleanup
  • Does not track plugin state — if a plugin modifies Markdown output (e.g., via PROCESS_CONTENT on files), changing plugin config won't bust the cache. Clear /tmp/folderweb_cache/ manually after plugin changes that affect rendered Markdown.
getCachedMarkdown(string $filePath, string $langPrefix = ''): ?string
setCachedMarkdown(string $filePath, string $html, string $langPrefix = ''): void

Static File Serving

Content Assets (router.php)

Before content routing, the router serves static files from content directory with an explicit MIME type allowlist:

css, jpg, jpeg, png, gif, webp, svg, pdf, woff, woff2, ttf, otf

Files not in this list are not served as static assets. Notably, .js files are excluded — JavaScript must be placed in custom/assets/ to be served (at the document root URL), or linked from an external source.

Custom Assets (router.php)

Files in custom/assets/ are served at the document root URL. Example: custom/assets/favicon.ico/favicon.ico. Uses mime_content_type() for MIME detection.

Framework Assets (static.php)

/app/* requests are handled by static.php with directory traversal protection (../ stripped):

URL Path Filesystem Path
/app/styles/* custom/styles/*
/app/fonts/* custom/fonts/*
/app/assets/* custom/assets/*
/app/default-styles/* app/default/styles/*

MIME types resolved from extension map, falling back to mime_content_type().

CSS Cache Busting

Page-specific CSS gets an MD5 hash appended: ?v={hash}. Computed by findPageCss(). The default theme's CSS is linked directly without hash (uses browser caching).

Parsedown

Markdown rendering uses Parsedown + ParsedownExtra from app/vendor/. These are the only third-party dependencies. Loaded lazily on first Markdown render.

Internal link rewriting: After Markdown→HTML conversion, href="/..." links are prefixed with the current language prefix (e.g., /no). This ensures Markdown links work correctly in multilingual sites.

Error Responses

  • 403: Invalid path (outside content directory or unreadable)
  • 404: Slug resolution failed or unknown route type
  • Both render via renderTemplate() with appropriate status code