Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/instructions/memory.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
description: Session handoff for webware-theme — theme resolver state, spec-kit location, and where the decision log and standing rules live, as of 2026-10-01.
applyTo: '**/*'
---

# Webware Theme Memory

Tagline: Theme resolution by lookup, not by path stacking. The resolver is built; the asset helper, the
default theme and the template port are open. Everything needed to continue is in `specs/001-theme-resolution/`.

## Start here

1. `specs/001-theme-resolution/decisions.md` — current state, the owner's standing rules, the decision log
(D-001 onwards) with statuses, next actions, gotchas. **Read it first.**
2. `specs/001-theme-resolution/tasks.md` — what is done and what is left; the next free task ID is stated at
the top.
3. `spec.md` (requirements), `plan.md` (design, with a built-versus-planned table), `data-model.md`
(configuration shape), `research.md` (measurements), `quickstart.md`.

Spec-kit, `.specify/` and `specs/` are tracked in this repository on purpose; do not propose removing them.

## What exists

- `Webware\Theme\Resolver\ThemeResolver` (final, `ResolverInterface`): `maps[active][name] ?? maps['default'][name]
?? false`, no filesystem call. `ThemeResolverFactory` reads `theme.themes` and `theme.active`.
`AggregateResolverFactory` attaches theme at 100, `TemplateMapResolver` at 50, the namespaced path stack at 1.
`ConfigProvider` must be merged after `Mezzio\LaminasView\ConfigProvider`.
- The asset helper key is `view_helper_config.asset.resource_map` (not `view_manager.asset`).
- Assets are served from `/theme/<theme>/{css,js,img,fonts}`, never from a bare `/<theme>/` (`.htaccess` would let a
directory shadow a route).

## Rules that bite

- Every change goes through a pull request; never push to `1.0.x`. Releases and tags belong to the owner.
- Never delete, rename or move a file without approval. Never edit the IMS repository (read-only).
- Named arguments always; never change form field names.
- Never run application code while a debug session may be attached (`ss -ltnp | grep -E ':(9000|9003)'`).
- Check Mago gates by exit code, not by the last line of output.
- Commits: `git commit -s` as `Joey Smith <jsmith@webinertia.net>`; merge commits by default.

The full list is `decisions.md` section 2 and `vendor/webware/webware-tools/agent-working-agreements.md`.
79 changes: 49 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# webware/webware-theme

Theme support for Webware applications: a theme is a directory under a component's
`templates/`, resolved by name through laminas-view, with per-template fallback to the
`default` theme that every component ships.
Theme support for Webware applications. A theme is a set of template and asset overrides; the active theme
is consulted first and `default` answers for everything it does not override. Template lookups are a
single array read, with no filesystem walk.

[![PHP Version](https://img.shields.io/packagist/php-v/webware/webware-theme)](https://packagist.org/packages/webware/webware-theme)
[![Latest Version](https://img.shields.io/packagist/v/webware/webware-theme)](https://packagist.org/packages/webware/webware-theme)
Expand All @@ -11,45 +11,64 @@ Theme support for Webware applications: a theme is a directory under a component
[![codecov](https://codecov.io/gh/webinertia/webware-theme/graph/badge.svg)](https://codecov.io/gh/webinertia/webware-theme)
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fwebinertia%2Fwebware-theme%2F1.0.x)](https://dashboard.stryker-mutator.io/reports/github.com/webinertia/webware-theme/1.0.x)

## The convention
## Quickstart

A theme is a directory named for the theme, directly inside a module's `templates/` directory, holding one
subdirectory per template namespace:
1. Install it and register the provider **after** `Mezzio\LaminasView\ConfigProvider`:

```
src/App/templates/
├── default/ # every module that ships templates ships this one
│ ├── app/home-page.phtml -> app::home-page.phtml
│ ├── layout/default.phtml -> layout::default
│ └── admin/dashboard.phtml -> admin::dashboard.phtml (a vendor template, overridden here)
└── acme/ # a theme, same layout of files
└── layout/default.phtml -> layout::default
```bash
composer require webware/webware-theme
```

Addresses stay mezzio's namespaced ones — `<namespace>::<name>` — and the theme selects the first
directory segment, so it is never part of the address and a component's references to its own templates
survive a theme change. Resolution consults the paths a namespace is served from, the active theme's
directory before `default`, and falls back **per template**, so a theme that overrides one layout leaves
every other template coming from `default`. A module overrides another module's template by placing that
namespace under its own theme directory — which is how an application restyles a vendor template without
touching the package that ships it.

That is the whole mechanism: a redesign is a directory of templates plus the assets that go with it, not
a fork of every package. Templates resolve from `templates/`; assets are served from `public/theme/<theme>/`
(a theme's `templates/` directory is never a served location); nothing is compiled, published or watched.
```php
Mezzio\LaminasView\ConfigProvider::class,
Webware\Theme\ConfigProvider::class,
```

## Installation
2. Describe a theme in any autoloaded config file, for example `config/autoload/theme.acme.global.php`:

```bash
composer require webware/webware-theme
```php
use Webware\Theme\ConfigProvider;

return [
ConfigProvider::THEME => [
ConfigProvider::ACTIVE => 'acme',
ConfigProvider::THEMES => [
'acme' => [
// only the addresses this theme overrides
'layout::default' => __DIR__ . '/../../templates/acme/layout/default.phtml',
],
],
ConfigProvider::ASSETS => [
'acme' => ['theme.css' => 'css/acme.css'],
],
],
];
```

Register the `ConfigProvider` in your Mezzio application config aggregator:
3. Put the asset file at `public/theme/acme/css/acme.css`, and use it in a template:

```php
Webware\Theme\ConfigProvider::class,
<link rel="stylesheet" href="<?= $this->asset('theme.css') ?>">
```

`layout::default` now comes from the `acme` theme and every other address from `default`;
`asset('theme.css')` returns `/theme/acme/css/acme.css`, and a name `acme` does not define falls back to the
`default` theme's file. Anything not listed in a theme falls back to `default` per address and per name.
Addresses stay mezzio's namespaced ones (`<namespace>::<name>`) and never contain a theme name, so a
component's references to its own templates survive a theme change.

## Documentation

| Document | Contents |
|---|---|
| [Installation](docs/v1/installation.md) | Requirements, provider order, what the provider registers |
| [Configuration](docs/v1/configuration.md) | The `theme.active`, `theme.themes` and `theme.assets` keys and their rules |
| [Template resolution](docs/v1/template-resolution.md) | How `ThemeResolver` answers, and the resolver order |
| [Assets](docs/v1/assets.md) | The theme-aware `asset()` helper, URL layout, theme-name rules |

Not built yet: publishing assets into `public/`, the command that creates and switches themes, and the
admin widget. They are specified in `specs/001-theme-resolution/`.

## Design

The design and its measurements live with the component, in `specs/001-theme-resolution/`:
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"require": {
"php": "~8.4.1 || ~8.5.0",
"laminas/laminas-view": "^3.0",
"mezzio/mezzio-laminasviewrenderer": "^3.0",
"psr/container": "^2.0"
},
"require-dev": {
Expand Down
Loading
Loading