API / Minn\Theme

Minn\Theme

The block-theme reader, templates, global styles and the page renderer. 41 classes, 165 public methods and 90 internals. This page is the public surface; each class name opens the whole class.

ArchiveTitle final class

public/minn/src/Minn/Theme/ArchiveTitle.php · 55 lines · Open the class →

The label and name an archive titles itself with: `Category:` around the term, `Author:` around the display name, the date formatted for its granularity. One source of truth for the block path's query-title and the classic path's get_the_archive_title(), which used to compute the same labels separately. Search and post-type archives stay with their callers: their captured shapes differ between the two paths.

staticparts(Minn\Front\Resolution $resolution, ?string $dateFormat): array

The archive title's prefix and title for a resolution.

@return array{string, string} the prefix (its colon included, in the site's language) and the escaped bare name; both empty when the view has none
staticcompose(string $prefix, string $name): string

The reference's prefixed shape, `Category: <span>Uncategorized</span>`, through the language's own order.

insidedateParts(), taxonomyLabel()

BodyClasses final class

public/minn/src/Minn/Theme/BodyClasses.php · 86 lines · Open the class →

The body-class list a classic theme's body_class() starts from, in the reference's order: the query tokens (with the singular and template tokens spliced in front of the type token), logged-in, the embed and theme tokens, with the numbered paging tokens re-seated after the embed token. The body_class filter runs over this list in the facade.

staticclassic(Minn\Front\Resolution $resolution, array $coreClasses, ?string $customTemplate, string $themeSlug, ?string $parentSlug, Minn\Theme\BodyFacts $facts): array

The body classes a classic theme's page carries.

@param list<string> $coreClasses
@return list<string>
staticforView(?string $singular, array $coreClasses, ?string $customTemplate, string $themeSlug, ?string $parentSlug, Minn\Theme\BodyFacts $facts): array

The same list for a view named by the post type it shows alone (null when it shows no single post), as get_body_class needs off the front (probe post-template-remaining).

@param list<string> $coreClasses
@return list<string>
insidewithSingularTokens()

BodyFacts final readonly class

public/minn/src/Minn/Theme/BodyFacts.php · 10 lines · Open the class →

The facts about a page that decide the body classes a classic theme's page carries beyond the view tokens: a signed-in reader, a custom logo, responsive embeds, and the Minn bar.

new__construct(bool $loggedIn = false, bool $customLogo = false, bool $embedResponsive = true, bool $bar = false)
propreadonly bool $loggedIn
propreadonly bool $customLogo
propreadonly bool $embedResponsive
propreadonly bool $bar

ClassicContent final class

public/minn/src/Minn/Theme/ClassicContent.php · 39 lines · Open the class →

What a classic theme's the_content() prints: the engine's block pipeline (render, password gate, the more-tag teaser in listings), the cached embeds, then the runtime's shortcodes and the_content filters so plugin code sees the same hook order the reference runs.

staticrender(Minn\Content\PostRecord $post, ?string $moreLinkText): string

A post's content as the classic loop prints it, with the more link.

ClassicRenderer final readonly class

public/minn/src/Minn/Theme/ClassicRenderer.php · 125 lines · Open the class →

A whole page from the active classic theme: the reference's PHP template loader. The main query stands, template_redirect fires, the hierarchy picks a PHP file, template_include filters it, and load_template() runs it as the response body. The theme prints its own document; the engine contributes the reference's wp_head defaults (title tag, robots, feed links, REST and oEmbed discovery, canonical, site icon) as hooks the theme's wp_head() call fires.

ClassicTheme final readonly class

public/minn/src/Minn/Theme/ClassicTheme.php · 31 lines · Open the class →

The active classic (PHP-template) theme on disk. A theme is classic when it ships no block template index; its templates are PHP files the engine dispatches through the reference's hierarchy and runs via load_template(). The theme's own functions.php loads through the runtime's symbol gate.

propreadonly string $stylesheet
propreadonly string $template
propreadonly string $stylesheetDir
propreadonly string $templateDir
staticactive(Minn\Content\Site $site, string $themesDir): ?self

The active classic theme, or null under a block theme.

CustomCss final class

public/minn/src/Minn/Theme/CustomCss.php · 69 lines · Open the class →

Custom CSS as theme.json and blocks write it (a styles node's "css", a block's style.css): nested rules under "&" unfolded beneath the selector they belong to, each wrapped in :root :where() as the reference prints them. Pure: CSS and selectors in, rules out.

staticscoped(string $css, string $selector): string

A block's own CSS under its selector (probe editor-styles; a block's style.css too, probe block-supports), split at each ampersand: a part with no rule applies to the block itself; a nested selector that starts with a space is scoped, every comma part after the block's selector; any other is appended to it; a pseudo element moves to the end, outside :where().

staticofStyles(array $styles): string

The styles' own CSS (WP_Theme_JSON::get_custom_css): the top-level css, then each block's under its root selector.

@param array<string, mixed> $styles
insidescope(), append()

EmbedRenderer final readonly class

public/minn/src/Minn/Theme/EmbedRenderer.php · 33 lines · Open the class →

A post's embed page (its /embed/ address, or ?embed= on it), the card another site's iframe shows, as the reference prints it: the main query stood with the embed asked for, then the embed template (the theme's own embed-{type}.php or embed.php, else the engine's theme-compat one) through template_include, with embed_head, embed_content, embed_content_meta and embed_footer. An address that embeds nothing gets the 404 card.

new__construct(Minn\Theme\MainQueryBridge $bridge)
staticasked(Minn\Http\Request $request, Minn\Front\Resolution $resolution): bool

Whether a request for this resolution asks for its embed page.

methodrender(Minn\Front\Resolution $resolution, Closure $bodyClasses): Minn\Http\Response

The embed page, under the status the request's main query settled on; the body classes the theme's page would carry (the Minn bar's aside), read once its main query stands.

@param Closure(): list<string> $bodyClasses

FeatureSelectors final class

public/minn/src/Minn/Theme/FeatureSelectors.php · 33 lines · Open the class →

A block's styles split between the selectors its metadata gives its features (probe theme-json-css), in the metadata's order: a property with a selector of its own first, then what is left of its feature under the feature's selector. What is taken leaves the node.

staticsplit(array $selectors, array $node): array

The styles each feature selector takes, and the node left over.

@param array<string, mixed> $selectors the block's metadata selectors
@param array<string, mixed> $node the block's styles
@return array{0: list<array{0: string, 1: array<string, mixed>}>, 1: array<string, mixed>} the styles each selector takes, in order, and what stays

FeedHeaders final class

public/minn/src/Minn/Theme/FeedHeaders.php · 50 lines · Open the class →

The headers a feed is sent with, as the reference's send_headers sends them (suite feed-hooks): the feed type's content type, and when the site last changed (its posts, and its comments too for a comments feed) as Last-Modified with an ETag over it; a reader whose copy carries that date or tag is told the copy is current.

staticfor(array $vars, ?Minn\Http\Request $request): array

The headers, and whether the reader's copy is current.

@param array<string, mixed> $vars the request's query variables
@return array{0: array<string, string>, 1: bool}
insidecarriesComments()

Folder final readonly class

public/minn/src/Minn/Theme/Folder.php · 77 lines · Open the class →

A theme folder read from disk: its style.css headers, which folder its templates come from, its screenshot, whether it is a block theme.

propreadonly string $root
propreadonly string $slug
propreadonly bool $exists
propreadonly array $headers
propreadonly string $template
staticread(string $root, string $slug, array $labels, ?Closure $reader = NULL): self

A theme folder with its style.css headers.

@param array<string, string> $labels key => the style.css header label
@param Closure(string, array<string, string>): array<string, string>|null $reader the header reader (the facade's, so header filters apply); the engine's own by default
methoddir(): string

The folder's path.

methodscreenshot(): ?string

The screenshot file name, or null when the theme has none.

staticisBlockTheme(array $dirs): bool

Whether any of the folders ships a block template index.

@param list<string> $dirs the stylesheet and template directories
staticfilePath(string $stylesheetDir, string $templateDir, string $file): string

The first of the stylesheet and template directories that holds the file, or the template directory's path.

FontFaces final class

public/minn/src/Minn/Theme/FontFaces.php · 119 lines · Open the class →

fonts a theme.json declares in the shape they are printed from.

A face needs a family and at least one source it can name a format for. Its rule lists family (quoted unless it is one word or already carries quotes), style (normal), weight (400), display (fallback unless it is one the reference knows), the sources, then any other face property in the order given. Sources go data URIs first, then woff2, woff, truetype, embedded-opentype and opentype; anything else is dropped.

staticcss(array $fonts): string

The rules for fonts given as families of faces, one rule a line; '' when no face can be printed.

@param array<array-key, mixed> $fonts
staticfromSettings(array $settings, Closure $fileUri): array

The fonts a theme.json's settings declare, one list of faces per family that has any: each face's sources first (file:./ ones through $fileUri), then its family (the first name of its own, or of the family's, unquoted), then its other properties in kebab case.

@param Closure(string): string $fileUri a theme-relative path to its URL
@return list<list<array<string, mixed>>>
insiderule(), sources(), family(), variations(), firstName()

FrontLifecycle final class

public/minn/src/Minn/Theme/FrontLifecycle.php · 110 lines · Open the class →

WordPress's front-end request steps around the main query, as WP::main runs them (front lifecycle trace): parse the request (do_parse_request may take it over; query_vars names the public variables, a plugin's own read from the query string; request filters the result; parse_request follows), the main query, the 404 decision (pre_handle_404 first), the globals, and the headers (wp_headers, the status, send_headers). The engine has already resolved the URL; these steps tell plugins about it in the reference's order and let them change what the query asks.

staticparseRequest(WP $wp, array $vars, array $given, ?Closure $parse = NULL): bool

Parses the request: false when a plugin's do_parse_request took the parse over (the main query then does not run). The query string is the main query's to build, after.

@param array<string, mixed> $vars the variables the engine resolved
@param array<string, mixed> $given the query string and form, where a plugin's own variables are read
@param (\Closure(list<string>): array<string, mixed>)|null $parse the reference's parse vars for the public vars, when the request is known
statichandle404(WP_Query $query, bool $notFound): void

The 404 decision, the one place it is made: a plugin's pre_handle_404 may make it; otherwise a request for something the site does not have is a 404, as is an empty page past the first of any listing and an empty date archive that names nothing else (an existing term, author or type with no posts, a date inside one of those or inside a search, an empty search, the front: a 200); the status and no-cache headers follow. A feed is never a 404: one of nothing is an empty feed.

staticsendHeaders(WP $wp): void

The headers the reference sends for the request: no caching for a signed-in reader or a 404, the content type, a pingback address for a single post that takes pings; then wp_headers, the status an error variable names, and send_headers.

GlobalStyles final readonly class

public/minn/src/Minn/Theme/GlobalStyles.php · 547 lines · Open the class →

theme.json to CSS. Presets become custom properties on :root and their has-* utility classes; settings.layout sizes, root styles, elements, and per-block styles become rules; the containers, galleries, and style variations the page actually rendered get their own stylesheets. The layout rules themselves (flow, constrained, flex, grid, alignments) are the engine's own, written to the same class hooks.

constELEMENT_SELECTORS = array ( 'link' => 'a:where(:not(.wp-element-button))', 'heading' => 'h1, h2, h3, h4, h5, h6', 'h1' => 'h1', 'h2' => 'h2', 'h3' => 'h3', 'h4' => 'h4', 'h5' => 'h5', 'h6' => 'h6', 'button' => '.wp-element-button, .wp-block-button__link', 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', 'cite' => 'cite', )

Element selectors in the order the reference prints them, whatever order theme.json or the saved styles list them in: the heading group lands before the individual levels, so an h1 line-height beats the group's. Themes list h1..h6 before heading and would otherwise win.

new__construct(Minn\Theme\Theme $theme, ?array $user = NULL)
methodstyles(): array

The theme.json (plus site-editor) styles node, under the engine's own defaults.

methodresolvedStyles(): array

The same node with every `var:preset|…` token resolved to the custom property it names, which is the shape a caller reading the styles as data expects (the CSS writer resolves them on the way out instead).

methodcss(): string

The global stylesheet from theme.json and the user's styles, as a page prints it: only the core blocks it rendered.

methodstylesheet(array $types): string

The stylesheet wp_get_global_stylesheet answers (probe editor-styles), by type: the custom properties, the styles (every block's, rendered or not), the preset classes; in that order.

@param list<string> $types
methodfontFaces(): string

The @font-face rules for every family that declares font files (Theme\FontFaces), with file:./ resolved against the theme that carries the file. The page prints them through wp_print_font_faces on wp_head; the editor's styles read them here.

staticrootRules(bool $aware): string

The body's margin, the global padding rules under root-padding-aware alignments, and the site's alignments.

staticblockGapRules(string $gap): string

The block gap between the site's blocks and inside every layout.

staticlayoutRules(): string

The layouts' own rules: alignments inside flow and constrained layouts, the content and wide sizes, flex and grid.

staticgapRules(string $prefix, string $gap): string

The block-gap rules for a layout scope: "" for the root, or a block's -is-layout- prefix.

staticelementStyles(array $elements, string $scope): string

The element rules (links, headings, buttons, captions, citations), each state after it; under a block's selector when one is given.

staticblockStyles(string $name, array $blockStyles, string $scope = ''): string

A block's rules: its root rule, its layout gap rules, then a rule for each feature its metadata gives a selector of its own (the avatar's border on its image), its own CSS and its elements (probe editor-styles).

staticscoped(string $selector, string $scope): string

Each selector in a list under the scope ("" for none).

staticrootSelector(string $name): string

A block's root selector: its metadata's, the reference's own for a few core blocks, else .wp-block-{slug}.

staticselectorsOf(string $name): array

A core block's selectors from its metadata. @return array<string, mixed>

@return array<string, mixed>
staticbyFeature(array $styles, array $selectors): array

A block's styles split between its root selector and the features (or a feature's single properties) its metadata selects elsewhere.

@param array<string, mixed> $styles
@param array<string, mixed> $selectors
@return array{0: array<string, mixed>, 1: array<string, array<string, mixed>>}
staticvariationCss(string $name, string $style, int $instance, array $variation): string

A numbered style variation's CSS (probe block-supports): its element rules scoped by the numbered class alone, then the variation on the block's root selector (the class riding on the block's own class: .wp-block-button.is-style-outline--3 .wp-block-button__link), its own CSS, and the blocks styled inside it.

staticdeclarations(array $styles, array $skip): array

theme.json style properties to declarations: color, typography, spacing, border, shadow, dimensions, outline.

@return list<string>
insidejson(), parts(), fontUrl(), rootStyles(), blockCustomProperties(), withoutEmpty(), containerStyles(), ordered()

Hierarchy final class

public/minn/src/Minn/Theme/Hierarchy.php · 148 lines · Open the class →

The classic template hierarchy: the candidate file names each template type tries, most specific first, as the reference resolves them. The facade's get_{type}_template() functions feed these to get_query_template(), which locates the first candidate the theme (child, then parent) ships.

staticfrontPage(): array

The front page templates.

@return list<string>
statichome(): array

The home templates.

@return list<string>
staticprivacyPolicy(): array

The privacy policy templates.

@return list<string>
staticpage(string $custom, string $slug, int $id): array

The page templates, custom first.

@return list<string>
staticsingle(string $type, string $slug, string $custom): array

The single templates, custom first.

@return list<string>
staticattachment(string $mimeType): array

The attachment templates by mime type: type-subtype, subtype, type (probe template-getters).

@return list<string>
staticterm(string $taxonomy, string $slug, int $id): array

The term archive templates.

@return list<string>
staticauthor(string $nicename, int $id): array

The author archive templates.

@return list<string>
staticarchive(array $postTypes): array

The post type archive templates.

@param list<string> $postTypes @return list<string>

MainQueryBridge final readonly class

public/minn/src/Minn/Theme/MainQueryBridge.php · 136 lines · Open the class →

Stands the main query for a themed page and runs the front-end steps around it as WP::main does (FrontLifecycle): the request parsed, the main query through WP_Query (so pre_get_posts and every posts_* filter shape it), the 404 decision, the globals, the headers, then "wp" and template_redirect. A listing's posts are the query's, and so is the 404 for an empty page past the first or an empty date; a single post the query does not find (a preview, a draft its author reads) is the one the engine resolved. When a plugin takes the parse over no query runs, as on the reference, and the page stands empty.

new__construct(int $perPage)
methodstand(Minn\Front\Resolution $resolution, array $extra = array ( )): Minn\Content\Page

The page of posts a resolution shows, through the runtime's main query; the variables a request adds of its own (a feed's) win.

@param array<string, mixed> $extra
staticverdict(Minn\Front\Resolution $resolution): Minn\Front\Resolution

The resolution as the main query left it: a 404 when the query decided one (an empty page past the first, an empty date), with the vars it was asked under.

methodperPage(): int

Posts per page.

insidevars(), postVars(), queried(), seeded()

NotModified final class

public/minn/src/Minn/Theme/NotModified.php · 3 lines · implements Throwable, Stringable · extends Exception · Open the class →

Raised once a reader's copy of a feed has been found current and the headers that say so are sent: the reference stops the request there, so the engine answers with nothing more.

PageRenderer final readonly class

public/minn/src/Minn/Theme/PageRenderer.php · 185 lines · Open the class →

A whole page from the active block theme: the template the resolution maps to, rendered against the main query, inside the document shell the reference emits (skip link, wp-site-blocks, the skip-link target on the first main element).

new__construct(Minn\Content\Site $site, Minn\Theme\Theme $theme, Minn\Theme\Templates $templates, Minn\Blocks\Renderer $renderer, Minn\Theme\MainQueryBridge $bridge, Minn\Theme\HeadLinks $headLinks, ?Minn\Front\AdminBar $bar = NULL)
staticcreate(Minn\Db $db, Minn\Theme\Theme $theme, Minn\Front\Permalinks $permalinks, int $perPage, ?Minn\Front\AdminBar $bar = NULL): self

A page renderer over the database door.

methodbodyClasses(Minn\Front\Resolution $resolution, array $coreClasses): array

The reference's body-class tokens: the singular and template tokens sit in front of the type token ("page", "single"), the custom-logo and embed tokens follow the core set, the paging tokens come after those, and the theme (and child theme) tokens close the list; then the body_class filter.

@return list<string>
methodthemeClasses(Minn\Front\Resolution $resolution, array $coreClasses): array

The body-class tokens before plugins filter them (bodyClasses()). @return list<string>

@return list<string>
methodrender(Minn\Front\Resolution $resolution, array $coreClasses): ?string

The page for a resolution, or null when the theme has no template for it.

insidedocument(), skipLinkTarget(), head()

PatternHeaders final class

public/minn/src/Minn/Theme/PatternHeaders.php · 78 lines · Open the class →

A theme's pattern files read by their headers, as WP_Theme::get_block_patterns lists them (probe wp-theme). Each one gives title, slug and description (empty when the header has none), then whatever of viewportWidth (a number), inserter (false for "no"), and the lists categories, keywords, blockTypes, postTypes and templateTypes it declares, split on commas and whitespace. A file without a title and a slug is not a pattern.

staticall(string $themeDir): array

The patterns directory's files by file name, in name order.

@return array<string, array<string, mixed>>
staticfiles(string $themeDir): array

The pattern files a theme ships, file name => path, in name order.

@return array<string, string>
staticof(array $files): array

The records of a list of pattern files, file name => path (a list a plugin may have changed); a file that is not a pattern is left out.

@param array<string, string> $files
@return array<string, array<string, mixed>>
staticread(string $head): ?array

One file's record from the start of its text, or null when it names no title or slug.

@return array<string, mixed>|null

PatternText final class

public/minn/src/Minn/Theme/PatternText.php · 234 lines · Open the class →

Block-theme patterns are PHP files whose only code is a handful of echo-and-escape calls around literal strings. The engine never executes them: it interprets that small text grammar (string literals, "." concatenation, the i18n and escaping wrappers, the theme URI helper, and printf with %s) and leaves anything else out. A translated string is looked up in its domain (the theme's own), with its context where it gives one, as the reference's patterns ask for theirs. The header comment block is dropped, and every byte outside the PHP tags is kept as-is.

Printed final class

public/minn/src/Minn/Theme/Printed.php · 3 lines · implements Throwable, Stringable · extends Exception · Open the class →

Raised once a WordPress handler has printed a whole response (a sitemap, a stylesheet) or sent a redirect, where the reference exits: the request ends there and the engine answers with what was printed.

QueryClasses final readonly class

public/minn/src/Minn/Theme/QueryClasses.php · 128 lines · Open the class →

The body-class tokens the main query stands for, in the reference's order (captured from the oracles): the view tokens (home, blog, privacy-policy, archive, date, search with its results token, paged, attachment, error404), then the singular's or the archive's own, then the numbered paging tokens. Flags combine as the query's do: a search inside a category carries both sets. The bare paged token follows the listing's page; the numbered ones follow a single's page too, and take the view's prefix in one precedence (single, page, category, tag, date, author, search, post type; the front and a plain taxonomy have none). A 404 carries no paging tokens. The theme renderers seat the numbered tokens after the embed token and add the template and theme tokens.

new__construct(Minn\Db $db)
methodof(WP_Query $query): array

The tokens for a main query.

@return list<string>
insideformat(), singular(), archive(), paging(), named()

SpacingScale final class

public/minn/src/Minn/Theme/SpacingScale.php · 48 lines · Open the class →

The spacing sizes a theme.json spacing scale makes (probe theme-json-css): Medium (slug 50) at the medium step; below it, half the other steps rounded down, each divided by (or less) the increment, named Small, X-Small, 2X-Small; above it the rest, multiplied by (or plus) it, named Large, X-Large, 2X-Large; slugs ten apart, values rounded to two places. An invalid scale makes none.

staticsizes(array $scale): array

The sizes the scale makes, smallest first.

@param array<string, mixed> $scale operator, increment, steps, mediumStep, unit
@return list<array{name: string, slug: string, size: string}>
insidename(), format()

StyleLayers final class

public/minn/src/Minn/Theme/StyleLayers.php · 67 lines · Open the class →

What plugins change about the global stylesheet's layers and nodes, for GlobalStyles: core's preset lists when wp_theme_json_data_default changed core's layer, and the style nodes wp_theme_json_get_style_nodes keeps (probe hook-inputs-2). Without those filters the stylesheet is the engine's own, byte for byte.

staticcorePresets(): array

Core's preset lists: as stored, unless a plugin changed core's layer (wp_theme_json_data_default), whose lists then stand in for them.

@return array<string, list<array{slug: string, value: string}>>
statickeptBlocks(array $blocks, array $kept): array

The blocks' styles as far as the kept nodes reach: a block's own declarations with its node, each of its elements with that element's node (the reference lists them apart); a block with neither goes.

@param array<string, mixed> $blocks
@param list<string> $kept
@return array<string, mixed>
statickeptNodes(array $styles): ?array

The style nodes a plugin keeps (wp_theme_json_get_style_nodes) as their paths, the root, element and block nodes printed only when kept; null when no plugin filters them.

@return list<string>|null

StylePresets final class

public/minn/src/Minn/Theme/StylePresets.php · 148 lines · Open the class →

The preset side of theme.json: the colour, gradient, font-size, font-family, spacing, and shadow lists a theme declares over core's own, as the custom properties on :root and the has-* utility classes the reference prints for them. Pure: settings in, CSS fragments out.

staticpresets(array $settings, ?array $core = NULL): array

Every preset list the theme declares, over core's own (as given, else data/presets.json), keyed by kind.

staticpresetList(mixed $presets): array

A preset list as theme.json writes it is a plain list; as the site editor saves it, it is keyed by origin (default, theme, custom). The reference prints the origins in that order, so the two shapes flatten to one list here.

@return list<array>
staticfontFamilies(array $settings): array

The font family presets in the settings.

@return list<array>
staticpresetProperties(array $presets): string

The presets as custom properties for :root.

staticcustomProperties(array $custom, string $prefix = '--wp--custom--'): string

A settings.custom tree as custom properties: --wp--custom-- and the path to each value, every key in kebab case, lists by index; empty values are left out.

@param array<array-key, mixed> $custom
staticpresetClasses(array $presets, string $prefix = ''): string

The has-* utility classes the presets give every colour, gradient, font size, and font family; each under the prefix given (a block's :where(selector)).

staticdefaultSlugsFirst(array $presets, array $defaults): array

A theme size that reuses one of core's slugs prints in core's position; the theme's own slugs follow.

@param list<array> $presets
@param list<string> $defaults
@return list<array>
staticspacingPresets(array $defaults, array $own): array

The default spacing scale (20 to 80) is always present, whatever defaultSpacingSizes says; a theme size with the same slug replaces the default in place, and the theme's other sizes follow the scale.

@return list<array{slug: string, value: string}>
staticfluidFontSize(array $preset, array $settings): string

A font size preset as the global stylesheet writes it: fluid by the theme's settings (Typography::fontSize).

StyleSettings final class

public/minn/src/Minn/Theme/StyleSettings.php · 88 lines · Open the class →

The settings and styles nodes as wp/v2/global-styles reports them: preset lists keyed by the origin that declared them, appearanceTools spelled out as the flags it stands for, and var:preset tokens resolved to the custom properties they name. Pure: a node in, a node out.

staticnormalize(array $settings, string $origin): array

A settings node as written (theme.json, a variation, or the site editor's post) with its presets keyed by origin and appearanceTools expanded.

staticpresetsByOrigin(array $settings, string $origin): array

The preset lists, at the root and under each block, keyed by the origin given; nothing else changed.

staticresolved(array $node): array

Every var:preset|kind|slug token replaced by the custom property it names.

insidepresetsUnder(), withAppearanceTools()

TemplateBuffer final class

public/minn/src/Minn/Theme/TemplateBuffer.php · 31 lines · Open the class →

The reference includes a front-end template inside the template-enhancement buffer: wp_before_include_template fires with the template's file, its default callback opens the buffer when a plugin filters wp_template_enhancement_output_buffer, and that filter is handed the whole page. The engine builds the page as a string, so the string is passed through the same buffer: every buffer the action opened is closed in turn, its handler writing into the capture the page is read from.

staticaround(string $template, callable $render): ?string

The page a render gives, as the buffers opened before it leave it; null when the render gives none. A render that fails leaves nothing behind.

@param callable(): ?string $render

TemplateHierarchy final class

public/minn/src/Minn/Theme/TemplateHierarchy.php · 75 lines · Open the class →

The templates a block theme falls back through for a template slug, as get_template_hierarchy builds them (probe rest-templates-lookup): the slug itself, then its family's chain to index. A single entry's slug reaches single-{type} and single only when the type is registered (and goes straight to singular when not); a taxonomy's reaches taxonomy-{tax} and taxonomy only when the taxonomy is; pages, categories, tags and authors step through their bare name when the slug has more to it. A prefix names the next step outright. A custom template is a page's.

staticfor(string $slug, bool $custom, string $prefix, array $types, array $taxonomies): array

A slug's fallback chain, most specific first, ending at index.

@param list<string> $types the registered post types
@param list<string> $taxonomies the registered taxonomies
@return list<string>
insidechain(), single(), taxonomy(), registered()

TemplateIndex final class

public/minn/src/Minn/Theme/TemplateIndex.php · 304 lines · Open the class →

Every block template and template part the site offers, in the order the reference lists them: the rows the site editor saved first (newest post date first), then the theme's own files in the order the directory hands them back, then whatever a plugin registered. A saved row shadows the theme file of the same slug and reports has_theme_file so the file can be restored by deleting the row.

constTEMPLATE = 'wp_template'
constPART = 'wp_template_part'
new__construct(Minn\Db $db, Minn\Theme\Theme $theme, Minn\Content\Site $site, ?Minn\Runtime\BlockTemplates $registered = NULL)
methodall(string $type): array

Every template of a type: the theme's files with the saved rows over them.

@return list<TemplateRecord>
methodfind(string $type, string $id): ?Minn\Theme\TemplateRecord

Null when the id names another theme, or a slug nothing provides.

methodpostRecord(array $row): Minn\Theme\TemplateRecord

A saved wp_template or wp_template_part row as a template. @param array<string, mixed> $row

@param array<string, mixed> $row
methodfiles(string $type): array

The theme's files of a type, in the directory's order, each as _get_block_templates_files describes it (fileInfo).

@return list<array<string, mixed>>
methodfileInfo(string $type, string $slug): ?array

One theme file: its slug, path, theme (the parent's for a file the child does not have) and type; a part's title (when theme.json gives one) and area; a custom template's title and post types. Null when the theme has no such file.

@return array<string, mixed>|null
methodpostTypes(string $slug): array

The post types a custom template declares; none for any other template.

@return list<string>
methodthemeSlug(): string

The theme the index reads.

methodhasFile(string $type, string $slug): bool

Whether the theme ships a file for a slug.

methodauthorText(Minn\Theme\TemplateRecord $record): string

Who the caller is told made this: the theme's own name for anything that has a file behind it, the plugin for a registered one, the author's display name for a row a person saved, and the site's name when nobody is recorded.

methodoriginalSource(Minn\Theme\TemplateRecord $record): string

theme when a file backs it, plugin when one registered it, user when someone saved it, else site.

staticisCustom(string $slug): bool

A slug the reference does not name in its default template types is a custom template.

staticdefaults(): array

The default template types.

@return array<string, array{title: string, description: string}>
methodmarkup(string $content): string

Markup as a caller of the REST route sees it: patterns spliced in where the template only named them, then every template-part block told which theme it belongs to. A record carries the markup as stored, because that is what get_block_templates() hands a plugin.

insidesavedRows(), pluginRows(), fromRow(), fromFile(), fromPlugin(), fileTitle(), savedArea()

TemplatePartTheme final readonly class

public/minn/src/Minn/Theme/TemplatePartTheme.php · 45 lines · Open the class →

A template-part block inside a template says which theme's part it means. The theme's own files usually leave that out, and the reference fills the active theme in on the way out, for saved rows as much as for files. It appends the attribute and leaves every other byte of the markup alone.

staticapply(string $markup, string $theme): string

Template part blocks with the theme attribute set.

insidewithTheme(), encode(), pair()

TemplatePatterns final readonly class

public/minn/src/Minn/Theme/TemplatePatterns.php · 88 lines · Open the class →

A template can name a pattern instead of carrying its blocks, and the reference hands the caller the blocks: every "wp:pattern" delimiter is replaced by the pattern's own markup before a template or part is served. A slug the theme does not know stays exactly as it is, which is what tells an editor the pattern went missing rather than the block.

staticexpand(string $markup, Minn\Theme\Theme $theme, int $depth = 0): string

Markup with its pattern blocks replaced by the patterns' content.

insidecloserAt(), pattern(), stamped(), isOneBlock()

TemplateRecord final readonly class

public/minn/src/Minn/Theme/TemplateRecord.php · 34 lines · Open the class →

One block template or template part, whatever it came from: a theme file, a row the site editor saved, or a template a plugin registered. The id every caller uses is "<theme>//<slug>" in all three cases.

new__construct(string $theme, string $slug, string $type, string $content, string $title, string $description, string $source, ?string $origin, bool $hasThemeFile, int $author, ?string $modified, ?string $date, int $wpId, string $status = 'publish', ?string $area = NULL, ?string $plugin = NULL)
propreadonly string $theme
propreadonly string $slug
propreadonly string $type
propreadonly string $content
propreadonly string $title
propreadonly string $description
propreadonly string $source
propreadonly ?string $origin
propreadonly bool $hasThemeFile
propreadonly int $author
propreadonly ?string $modified
propreadonly ?string $date
propreadonly int $wpId
propreadonly string $status
propreadonly ?string $area
propreadonly ?string $plugin
methodid(): string

The reference's template id: theme//slug.

methodisPart(): bool

Whether this is a template part.

TemplateWriter final readonly class

public/minn/src/Minn/Theme/TemplateWriter.php · 116 lines · Open the class →

Saving and removing block templates. A template the theme ships is never touched on disk: editing one writes a wp_template row that shadows the file, and deleting that row is what "reset to the theme version" means.

new__construct(Minn\Db $db, Minn\Content\PostWriter $writer, Minn\Content\Terms $terms, Minn\Content\Site $site, Minn\Theme\TemplateIndex $index)
methodsave(Minn\Theme\TemplateRecord $record, array $fields, int $authorId): int

Writes the site's own copy of a template, creating the row the first time. Returns the post id.

@param array{title?: string, content?: string, description?: string, area?: string} $fields
methodcreate(Minn\Theme\TemplateRecord $record, array $fields, int $authorId): int

A new site copy from the collection's create: as a save inserts one, but recording no origin, even over a theme file (probe rest-template-create).

@param array<string, string> $fields
methodtrash(Minn\Theme\TemplateRecord $record): void

Moves a site copy to the trash, which hands the theme's file back.

methoddestroy(Minn\Theme\TemplateRecord $record): void

Deletes a saved template row.

insideinsert(), setArea(), themeTermId()

Templates final readonly class

public/minn/src/Minn/Theme/Templates.php · 204 lines · Open the class →

Which template renders a resolution, and where its markup comes from: a wp_template post saved from the site editor (matched by slug and the theme term) wins over the theme's file. Parts resolve the same way through wp_template_part.

new__construct(Minn\Db $db, Minn\Content\Posts $posts, Minn\Theme\Theme $theme, ?Minn\Runtime\BlockTemplates $registered = NULL)
methodforResolution(Minn\Front\Resolution $resolution): ?array

The template that renders a resolution, or null.

@return array{slug: string, markup: string}|null
methodtemplate(string $slug): ?string

A template's markup by slug, saved first.

methodpart(string $slug): ?string

A template part's markup by slug, saved first.

methodpartSource(string $slug): array

Where a template part comes from: a part saved for the theme (its post id), the theme's file (its path), or nowhere; with its markup.

@return array{0: 'post'|'file'|'none', 1: int|string, 2: ?string}
methodcandidates(Minn\Front\Resolution $resolution): array

The block-theme template hierarchy for each kind of resolution.

@return list<string>
methoduserStyles(): ?array

The site editor's saved global styles for the active theme, when any. An emptied node is stored as [] and reads as nothing, the way the reference reads it; a list where a map belongs is dropped the same way.

methodcustomTemplate(int $pageId): ?string

A page's chosen custom template, from _wp_page_template meta.

insidefiltered(), hierarchy(), savedOfTheme(), saved(), savedRow()

Theme final class

public/minn/src/Minn/Theme/Theme.php · 347 lines · Open the class →

The active block theme on disk, read as data: theme.json, the templates and parts directories, and the patterns index. A child theme's files win and its parent fills in what the child does not define; theme.json merges the same way, with the child's preset lists replacing the parent's whole. The engine reads the site's installed theme the way it reads the site's database; it never runs the theme's PHP.

new__construct(string $slug, string $dir, string $uri, ?Minn\Theme\Theme $parent = NULL)
propreadonly string $slug
propreadonly string $dir
propreadonly string $uri
propreadonly ?Minn\Theme\Theme $parent
staticactive(Minn\Content\Site $site, Minn\Front\Permalinks $permalinks, string $themesDir): ?self

The active block theme, or null under a classic one.

staticforStyles(Minn\Content\Site $site, Minn\Front\Permalinks $permalinks, string $themesDir): ?self

The active theme as styling data (theme.json present), whether or not it is a block theme; the classic renderer prints its presets where the reference prints a classic theme's global styles.

methodparentSlug(): ?string

The theme's own name, and the parent's, for body classes.

methodjson(): array

The theme.json with the parent's merged in; with plugins loaded, through wp_theme_json_data_theme.

methodstyleFiles(): array

Every JSON file under styles/, the parent theme's first and each theme's in path order: block style partials and style variations alike.

@return list<string>
staticmerge(array $base, array $over): array

Layered theme.json: maps merge key by key, lists (palettes, font sizes, template parts) replace as a whole.

methodtemplateFile(string $slug): ?string

A template file's markup, or null.

methodtemplatePath(string $slug): ?string

The file a template is read from: this theme's, else its parent's; null when neither has one.

methodpartFile(string $slug): ?string

A template part file's markup, or null.

methodpartPath(string $slug): ?string

The file a template part is read from: this theme's, else its parent's; null when neither has one.

methodname(): string

The theme's display name, as the stylesheet header states it; a child theme answers with its own. Falls back to theme.json's title, then the folder name.

methodfileSlugs(string $folder): array

Every template (or part) slug the theme offers, the child's files first and the parent's after, each in the order the directory hands them back: the reference walks these directories unsorted and its template list carries that order through.

@return list<string>
methodcustomTemplate(string $slug): ?array

A template title and description the theme declares for a custom template slug.

methodpartTitle(string $slug): ?string

The title theme.json gives a template part, when it names one.

methodpartArea(string $slug): string

The template-part area declared in theme.json (header, footer, or uncategorized).

methodpattern(string $slug): ?string

A pattern's markup, with its PHP text subset interpreted; null when unknown.

methodpatternMeta(string $slug): array

A pattern's declared Block Types and Categories, the header fields a plugin reads to tell (say) a header pattern from any other.

@return array{blockTypes: list<string>, categories: list<string>}
methodpatternHeader(string $slug): ?array

The header fields a pattern declares, as the metadata attribute the reference writes onto the pattern's first block when it splices the pattern into a template. A field the header omits is omitted here.

@return array{patternName: string, name: string, description?: string, categories?: list<string>}|null
methodstyleUri(): ?string

The theme's stylesheet URL when it ships one; a child's own, else nothing (the parent's is not enqueued for it).

insideat(), rawJson(), withStylePartials(), partialFiles(), safe(), htmlFiles(), patternIndex()

ThemeJsonData final class

public/minn/src/Minn/Theme/ThemeJsonData.php · 82 lines · Open the class →

The theme's theme.json as plugins and the theme's own functions may change it (wp_theme_json_data_theme, a WP_Theme_JSON_Data they update with presets and settings of their own), as the reference's resolver hands it to them, and the core and site editor layers likewise (wp_theme_json_data_default, wp_theme_json_data_user). Worked out once a request for each set of callbacks, so a filter added late still counts.

statictheme(array $json, string $dir): array

The theme layer through wp_theme_json_data_theme; as written when nothing filters it. @param array<string, mixed> $json

@param array<string, mixed> $json
staticlayer(string $origin, array $json): array

The core layer (wp_theme_json_data_default, origin default) or the site editor's (wp_theme_json_data_user, origin custom) as plugins leave it, each a WP_Theme_JSON_Data they update; as given when nothing filters it. Worked out once a request for each set of callbacks and layer.

@param array<string, mixed> $json
@return array<string, mixed>
insideasWritten()

ThemeStyles final readonly class

public/minn/src/Minn/Theme/ThemeStyles.php · 142 lines · Open the class →

The active theme's global styles as wp/v2/global-styles/themes/{stylesheet} reports them: the engine's defaults (captured from the reference under a theme with no theme.json) with the theme's own settings and styles over them, and the style variations the theme ships.

new__construct(?Minn\Theme\Theme $theme, string $stylesheet, ?array $core = NULL)
@param array{settings?: array, styles?: array}|null $core core's layer when not the stored one (a plugin changed it)
propreadonly string $stylesheet
methodwithFilteredCore(): self

The same styling data over core's layer as wp_theme_json_data_default leaves it, for the places the reference reads core's layer through that filter (its resolver's core and merged data).

staticforSite(Minn\Content\Site $site, Minn\Front\Permalinks $permalinks, string $themesDir): self

The site's active theme as styling data, whether or not it is a block theme.

methodsettings(): array

Settings: presets by origin, appearanceTools expanded, the defaults underneath.

methodpatterns(): array

The pattern directory slugs the theme.json lists (its patterns), as given; [] when it lists none. @return array<mixed>

@return array<mixed>
methodstyles(): array

Styles: the theme's over the defaults, block style partials and section styles in, every var:preset token resolved.

methodvariations(): array

The style variations under styles/: every JSON file there that names no blockTypes (those are block style partials), the parent theme's first, in path order, $schema dropped and the nodes normalized the way the theme's own are.

@return list<array>
insidewithSectionStyles(), partials(), defaults(), storedCore()

TreeStyles final readonly class

public/minn/src/Minn/Theme/TreeStyles.php · 259 lines · Open the class →

The CSS one theme.json tree writes, as WP_Theme_JSON keeps the tree (presets keyed by origin): its custom properties, its preset classes, its styles (the root layout, the root, elements, blocks) and its own CSS, by type, origin and option (probe theme-json-css). The rules are GlobalStyles'; what the page's writer takes for granted is read from the tree instead: block gaps only when the tree turns them on, the root padding only on the sides it gives, and none of the engine's own defaults.

new__construct(array $tree)
@param array<string, mixed> $tree
methodstylesheet(array $types, array $origins, array $options): string

The stylesheet for the types asked for, in the order variables, styles, presets, the tree's own CSS; origins narrow the presets.

@param list<string> $types
@param list<string> $origins
@param array<string, mixed> $options root_selector, scope, skip_root_layout_styles
methodvariables(array $origins, string $scope): string

The presets and settings.custom as custom properties: on :root, then on each block's settings selector that has its own.

@param list<string> $origins
methodvariablesOf(array $settings, array $origins, string $selector): string

One settings node's custom properties under its selector; nothing when it has none.

@param array<string, mixed> $settings
@param list<string> $origins
methodpresetClasses(array $origins, string $scope): string

The has-* classes: the root's bare, each block's under :where(its settings selector).

@param list<string> $origins
methodclassesOf(array $settings, array $origins, string $selector): string

One settings node's has-* classes, under :where(selector) unless it is the bare :root.

@param array<string, mixed> $settings
@param list<string> $origins
methodstyles(array $options): string

The styles: the root layout (unless skip_root_layout_styles), the root's own rule, its CSS when the root is not body, the elements, then each block; under the root_selector and scope options.

@param array<string, mixed> $options
methodrootLayoutRules(string $selector): string

The root layout: the content and wide sizes, the body's margin, the global padding rules, the alignments; for body, the block gap (or the fallback gap of flex and grid) and the layouts' own rules.

methodlayoutStyles(): string

The block gap and the layouts' own rules; flex and grid fall back to a 0.5em gap when the tree has no block gap.

methodrootStyles(string $selector): string

The root's own rule: body as it is, any other root under :root :where().

methodblockStyles(string $name, string $scope = ''): string

One block's rules; its own block gap only when the tree turns block gaps on.

methodblockOwnStyles(string $name): string

One block's own rules, its elements left out: each element is a style node of its own.

methodblockElements(string $name): array

The elements a block styles, in the order they print.

@return list<string>
methodelementStyles(string $element, string $block = ''): string

One element's rules, under a block's selector when it belongs to one.

methodcustomCss(): string

The tree's own top-level CSS, as written.

staticsettingsSelector(string $name): string

A block's settings selector: its experimental selector when it has one, else .wp-block-{slug}.

insideblockGap(), presets()

Typography final class

public/minn/src/Minn/Theme/Typography.php · 105 lines · Open the class →

Font sizes as the reference writes them (probe plugin-queue3): a size in px, rem or em, and, with fluid typography on, a clamp() that grows linearly from a minimum at a small viewport to the size (or the preset's own maximum) at the wide one.

staticvalueAndUnit(mixed $raw, array $options = array ( )): ?array

A CSS length split into its number and unit (px, rem or em, or the units given), converted to another unit on request at a 16px root (or the one given); a bare number is px. Null for anything else.

@param array{coerce_to?: string, root_size_value?: int|float, acceptable_units?: list<string>} $options
@return array{value: float, unit: string}|null
staticfontSize(array $preset, array $settings): mixed

A font size preset as CSS (wp_get_typography_font_size_value). It stays as written unless fluid typography is on and the size is in a known unit and above the floor (14px, or the settings' minFontSize); then it grows from a minimum (the preset's own, else the size scaled by 1 - 0.075 log2 of its px, held between 0.25 and 0.75, never under the floor) to a maximum (the preset's own, else the size).

@param array<string, mixed> $preset
@param array<string, mixed> $settings typography and layout, merged over the global settings
staticclamp(?string $minViewport, ?string $maxViewport, ?string $minSize, ?string $maxSize, int|float|null $scale): ?string

The clamp() between two sizes over two viewport widths (wp_get_computed_fluid_typography_value): the slope in the minimum's unit, the base in rem. Null when a size or a width is missing or in an unknown unit, or the widths are equal.

insidenumber()

UserStyles final readonly class

public/minn/src/Minn/Theme/UserStyles.php · 111 lines · Open the class →

The site editor's saved global styles: one wp_global_styles post per theme, tied to it by the wp_theme term, holding a versioned JSON body of the settings and styles the editor wrote. Every changed save leaves a revision behind, as a post's does.

new__construct(Minn\Db $db, Minn\Content\Posts $posts, Minn\Content\PostWriter $writer, Minn\Content\Terms $terms, Minn\Content\Site $site)
methodidFor(string $stylesheet): ?int

The newest published global-styles post carrying the theme's term, or null.

methodensure(string $stylesheet, int $userId): int

The theme's post, made the way the reference makes it when there is none yet.

methodfind(int $id): ?Minn\Content\PostRecord

The post when it is a global-styles post, else null.

staticdecode(string $json): array

What a global-styles body holds; a missing, malformed, or list-shaped node is empty, as the reference reads it.

@return array{settings: array, styles: array}
staticisBlank(string $json): bool

True when the body carries nothing at all under settings or styles, malformed or not.

methodsave(int $id, int $userId, ?string $title, ?array $settings, ?array $styles, ?Closure $filter = NULL): void

Replaces what the caller names (null keeps the stored value), stamps the modified time, and snapshots a revision when anything changed. A filter, when given, has the last word on the content and title written (the save filters a REST save runs them through).

@param null|Closure(array<string, string>): array<string, string> $filter