Tempest-Pico is still a work in progress, but I will try to keep it up to date.
Documentation
Why?
Goal of this Project is to create a Base to quickly build mostly static websites, used by a very small community. It is built on top of Tempest v2 (I still need PHP 8.4).
My own "modern PHP" Framework was written in PHP5 and ported 2015 to Hack, but Hack is dead and I need to switch back to PHP again. Around 10 Years I only use PHP for small scripts and to fix WordPress sites, so I'm a bit rusty and not really up to date with modern PHP Tools. So fell free to give me feedback like "Hey, you should use this library." or "This is not how you should do it, you should do it like this!".
In the long run, I like to replace some WordPress sites with Tempest. Unfortunately, Tempest own View system is not really suitable for me, thats why I created my own "HTML View Tree Builder" on top of it. It is not battle-tested, but it solved some Issus I had. I'm happy with it so far.
Take a look at the Components code to see how I use it, maybe it is also useful for you.
Well, the next is (mostly) generated by Copilot for itself… But why not use it?
Tempest Pico — Copilot Instructions
Quick Start
Tech Stack: PHP 8.4, Tempest Framework (v2), HtmlViewTree, UnoCSS, Pico CSS, PHPUnit, PHPStan (level 7)
Yohn's Fork of PicoCSS is used - see https://yohn.github.io/PicoCSS/ and https://github.com/Yohn/PicoCSS/
HtmlViewTree: a alternative to string-based template engines, see Html View Tree Builder below.
QA Workflow:
composer qa # fmt + phpunit + lint (use this before PRs)
composer phpunit # Run tests with detailed output
composer fmt # Format with mago
composer lint # Lint with mago (auto-fixes)
pnpm unocss --watch # Watch CSS during development
Key Namespaces:
TempestPico\→/src/(main application)Tests\→/tests/(PHPUnit tests)
PHPStan: Level 7 strict checking on src/, app/, tests/
Type Hints and common abbreviations
MD→ Markdown (GitHub flavored), helper:MD()IMD→ Inline Markdown (no Block elements like<p>), helper:IMD()- If you see
MD/IMDas Type hint it means the corresponding helper is used on the givenstring - (Component-)
Contentone of Type:string|Stringable→ auto-escaped for safe output in HTMLHtmlString→ raw HTML, must be used with care -> useHtml(…)helper insteadComponent→ any class implementingIsComponent(renders to HTML viagetViewTree())View→ ! not implemented ! - any other Tempest View (should work - with Issues)
VT/HtmlViewTree→ A Tree of the above types, useVT()orHtml(…)helper to create it
Project Principles
1. Semantic HTML/CSS First
- Prefer semantic HTML elements over generic divs
- Use Pico CSS utilities + UnoCSS for modifiers (BEM workflow: B for semantic, M/E for utilities)
- Avoid Tailwind-style utility-heavy markup; maintain readability
- Prefer HtmL View Tree Builder over template strings
2. IDE & Static Analysis Clarity
- All template variables must be IDE-discoverable (no
$$keymagic, explicit type hints) - Property promoted constructors for class dependency injection
- PHPStan level 7: catches undefined classes, type errors, null access issues
- Common issue: undefined exception classes → Always create Exception classes in
Exception/
3. Component Architecture
- Components live in
src/Components/with a.php(logic and agetViewTree()) and.view.php(that start rendering) pair - Components should also provide:
- a example returning
VT()orHtml()(src/Components/Examples/*.php) - a description / usage notes (
src/Components/Examples/*.md)
- a example returning
- Extend
Componentbase class and implementIsComponentinterface
4. Html View Tree Builder
- Purpose: Build HTML trees programmatically without template strings
- Key Methods:
__invoke(tag, content[], attributes[])→ Create HTML element (validates tag)customTag(tag, content[], attributes[])→ Custom tag (no validation)appendContent()→ Add children to current noderender()→ Convert tree toHtmlString
- Common Patterns:
Html('div') ('h1', ['Headline']); - Known Pitfalls:
- Void tags (
<br>,<hr>, etc.) cannot have content → throwsVoidWithContentexception
- Void tags (
Common Tasks
Add a New Component
- use
src/Components/Accordion.phpandsrc/Components/Table.phpas reference - Create
src/Components/*.php(logic + dependencies) - Create
src/Components/*.view.php(only needs:<?= $this->toHtml();) - Implement
IsComponentand extendComponent - Use it in other components or controllers
- Add the
class::nametosrc/Components/Doc.php::COMPONENTS
Add Examples, short Documentation and Tests for a Component
- Place usage examples and a additional Notes (
*.mdfile) insrc/Components/Examples - The Notes don't need more examples or be long. Just known Bugs or a "Read more @"-Link is fine.
- Use the example in test at
test/.
Add a Helper Function
- Place HtmlViewTree Helpers in
src/Support/Html/functions.php(auto-loaded via composer.json) - Example:
Html()is aliased toHtmlViewTreeconstructor - Always import with
use function TempestPico\Support\...
Run Tests Before PR
composer qa
This ensures:
- Code is formatted (mago fmt)
- All tests pass (PHPUnit)
- Linting passes (mago lint --fix)
Debug HtmlViewTree Issues
- Use
render()return value type (must beHtmlViewTree) - Verify void tags never get
appendContent()called - PHPStan will catch missing exception definitions immediately
Architecture Overview
src/
Components/ # Reusable UI components
*.php # Logic
*.view.php # Render (returns HtmlString)
Examples/
*.php # Example (used in Tests)
*.md # Notes / Documentation
Layout/ # Page layout (header, nav, footer) and configs
Page/ # Base Html for pages
Page/ # Route handlers / page builders / Controllers
Support/
Html/ # HTML View Tree Builder, Helper
Exception/ # Custom exceptions (InvalidTag, VoidWithContent, etc.)
functions.php # Helper functions / Shortcuts
tests/ # Test code (PhpUnit)
Key Files & References
- GitHub Page the final generated Documentation of this project.
- Tempest Framework v2 — Tempest Features
- shell command
./tempest --helpshows Tempest commands - Yohn's PicoCSS Fork — Styled HTML elements and CSS Classes
- Accordion and Table — Example for Components
- README.md — Project rationale & deployment
- phpstan.neon — Static analysis config (level 7)
- composer.json — QA scripts & autoloading
PHPStan Common Fixes
❌ "Undefined type 'TempestPico\Support\Html\Exception\InvalidTag'"
Fix: Create the exception class in src/Support/Html/Exception/InvalidTag.php:
<?php declare(strict_types=1);
namespace TempestPico\Support\Html\Exception;
class InvalidTag extends HtmlBuilderException implements HasContext {
public function __construct(private string $tag) {
parent::__construct("The HTML tag {$tag} is unknown.");
}
public function context(): array {
return ['tag' => $this->tag];
}
}
Then import it:
use TempestPico\Support\Html\Exception\InvalidTag;
❌ "Cannot access offset on mixed"
Fix: Add explicit array type hints:
/** @var array<string, string> $config */
$config = $arr->toArray();
PHPUnit & Testing
- All tests inherit from
IntegrationTestCase(unless unit-only) - Test file naming:
*Test.phpin/tests/ - Run with:
composer phpunit
Deployment & Static Generation
# Generate static HTML (GitHub Pages)
./tempest static:generate --verbose
pnpm unocss
# Clean after deploy
./tempest static:clean --force
Git Workflow
Current Branch: POC--View-Tree-Builder → PR #1 (View Tree Builder)
Before pushing:
- Run
composer qa(must pass) - Verify no PHPStan errors:
vendor/bin/phpstan analyse - Commit with short meaningful message using a Git Emoji
Tips for AI Agents
- Run
./tempest routes --jsonif you need Data about amiable Pages / Routes - JSON formatted - Short Doc Blocks — Don't repeat yourself or simple PHP code, only add
@parametc. if it improve type hinting - Always run
composer qaafter changes to catch formatting, test, and lint issues - PHPStan level 7 is strict — it will catch undefined classes, missing imports, type mismatches
- Semantic HTML over generic divs — review existing Components for patterns
- HTML View Tree Builder is still evolving — check tests for expected behavior before guessing
- Make use of Tempest Helpers — check
vendor/tempest/framework/packages/support/src/*/functions.php - Don't create loose Exception files — always implement
HasContext