Files
novaconium/App/pages/blog/style-guide/index.twig
T
code 37b6764431 Add content index: keywords, tags/categories, search, and XML sitemap
Ships Blog tags/categories, Internal search, and XML sitemap together as
one shared mechanism, plus a new meta keywords request, rather than three
separate ones - the "worth deciding together" call ISSUES.md made when
XML sitemap was first written.

Content stays in files. Per-page metadata is four Twig blocks in the
root layout, the same override mechanism already used for
title/description/og_* - keywords (rendered), tags/changefreq/priority
(not rendered, harvested only). App\ContentIndexer crawls every routable
page (Overlay::listPageDirs(), new) and pulls each block via
Renderer::renderForIndex() (new) calling Twig's own renderBlock() API,
not regex-parsing .twig source, so overrides and layout inheritance
resolve exactly like a real render. Rendered HTML is stripped and
indexed into a SQLite FTS5 table for search.

Off by default (content_index_enabled) - same posture as Matomo/admin
auth, since this is a real SQLite dependency plenty of sites won't want.
Verified true zero footprint when disabled: no data/novaconium.sqlite
gets created, all three consumer routes 404 like they don't exist.
content_index_auto (default true) reindexes lazily on first stale touch
of a consumer route, never on a normal page view; both that path and the
explicit `php novaconium/bin/index-content.php` share one reindex().

Two real bugs caught by testing, not review: a reentrancy bug where
/search's own sidecar calling ensureFresh() during the crawl triggered a
nested reindex() mid-transaction (fixed with a static re-entrancy guard),
and a wrong PDO constant in the search sidecar. Also fixed two unrelated
pre-existing bugs found while building this: an unescaped {{ }} in
admin/docs/sidecars that fataled Twig on any real render of that page,
and a stale "no database layer yet" claim there and in Lib\Input's
docblock, left over from before Lib\Db shipped.

Lib\Db's migrations_dir now accepts an ordered list of roots, not just
one path, so the content index's schema could ship as a framework
migration (novaconium/migrations/) without colliding with project
migrations in App/migrations/ - the two-root extension point flagged in
AGENTS.md when SQLite groundwork shipped. Migrations are tracked by path
relative to the repo root rather than bare filename so two roots with a
same-named file can't shadow each other.

New consumer routes: novaconium/pages/sitemap.xml/ and
novaconium/pages/search/ (framework defaults), App/pages/blog/tag/[tag]/
(project-owned, since blog/ is project content - the existing hand
-written post array in App/pages/blog/index.php is untouched). Added
tags to the 4 existing blog posts as a real demonstration.

Closes the Blog tags/categories, Internal search, and XML sitemap
backlog items in novaconium/ISSUES.md.
2026-07-14 17:35:30 +00:00

130 lines
5.1 KiB
Twig

{% extends layout %}
{% import '_layout/icons.twig' as icons %}
{% block title %}Style Guide{% endblock %}
{% block description %}A showcase of this theme's default styling for headings, lists, tables, code, and other common HTML elements.{% endblock %}
{% block robots %}index, follow{% endblock %}
{% block tags %}css, reference{% endblock %}
{% block canonical %}{{ request_path|default('/') }}{% endblock %}
{% block og_type %}article{% endblock %}
{% block og_title %}{{ block('title') }}{% endblock %}
{% block og_description %}{{ block('description') }}{% endblock %}
{% block og_url %}{{ block('canonical') }}{% endblock %}
{% block twitter_card %}summary{% endblock %}
{% block twitter_title %}{{ block('title') }}{% endblock %}
{% block twitter_description %}{{ block('description') }}{% endblock %}
{% block blog_content %}
<h1>Style Guide</h1>
<p>A plain page, like <a class="icon-link" href="/blog/twig-syntax-guide">{{ icons.book() }}the Twig Syntax Guide</a>, this time showing off the default styling every element on this site gets for free from <code>novaconium/sass/main.sass</code> — no per-page CSS involved. If you've changed <code>App/sass/_colors.sass</code> (see <a class="icon-link" href="/admin/docs/styling">{{ icons.book() }}Styling</a>), this page is the fastest way to see the new palette applied across everything at once.</p>
<h2>Headings</h2>
<h1>Heading level 1</h1>
<h2>Heading level 2</h2>
<h3>Heading level 3</h3>
<h4>Heading level 4</h4>
<h5>Heading level 5</h5>
<h6>Heading level 6</h6>
<h2>Paragraph &amp; inline text</h2>
<p>A normal paragraph, with <strong>bold</strong>, <em>italic</em>, <code>inline code</code>, and <a href="/">a link</a> mixed in. <small>Small print, like this, is used for captions and secondary detail throughout the site.</small></p>
<h2>Blockquote</h2>
<blockquote>
<p>Design is not just what it looks like and feels like. Design is how it works.</p>
</blockquote>
<h2>Lists</h2>
<h3>Unordered</h3>
<ul>
<li>File-based routing</li>
<li>Optional sidecars</li>
<li>Static caching</li>
</ul>
<h3>Ordered</h3>
<ol>
<li>Write a Twig template</li>
<li>Optionally add a sidecar</li>
<li>Visit the URL</li>
</ol>
<h3>Nested</h3>
<ul>
<li>App/
<ul>
<li>pages/</li>
<li>lib/</li>
<li>sass/</li>
</ul>
</li>
<li>novaconium/
<ul>
<li>pages/</li>
<li>src/</li>
</ul>
</li>
</ul>
<h2>Table</h2>
<table>
<thead>
<tr><th>Element</th><th>Styled by</th></tr>
</thead>
<tbody>
<tr><td>Headings</td><td><code>h1, h2, h3, h4, h5, h6</code></td></tr>
<tr><td>Links</td><td><code>a</code>, <code>a:hover</code></td></tr>
<tr><td>Code</td><td><code>code</code>, <code>pre</code></td></tr>
<tr><td>Tables</td><td><code>table</code>, <code>th</code>, <code>td</code></td></tr>
</tbody>
</table>
<h2>Code</h2>
<p>Inline: <code>(new Mailer())-&gt;send($old['name'], $old['email'], $old['message']);</code></p>
<pre><code>{% verbatim %}{% extends layout %}
{% block content %}
...
{% endblock %}{% endverbatim %}</code></pre>
<h2>Horizontal rule</h2>
<p>Above this line:</p>
<hr>
<p>Below this line.</p>
<h2>Form elements</h2>
<form>
<p>
<label for="style-guide-example">A label</label><br>
<input type="text" id="style-guide-example" placeholder="A text input">
</p>
<p>
<label for="style-guide-textarea">A textarea</label><br>
<textarea id="style-guide-textarea" rows="3" placeholder="Some longer text"></textarea>
</p>
<p>
<label for="style-guide-select">A dropdown</label><br>
<select id="style-guide-select">
<option>Option one</option>
<option>Option two</option>
<option>Option three</option>
</select>
</p>
<p>
Radio buttons<br>
<label><input type="radio" name="style-guide-radio" checked> First choice</label><br>
<label><input type="radio" name="style-guide-radio"> Second choice</label><br>
<label><input type="radio" name="style-guide-radio"> Third choice</label>
</p>
<p>
<label><input type="checkbox" checked> A checkbox, too, while we're here</label>
</p>
<button type="button">A button</button>
</form>
<h2>Icons</h2>
<p>{{ icons.home() }} {{ icons.git() }} {{ icons.book() }} {{ icons.link() }} {{ icons.sitemap() }} {{ icons.email() }} {{ icons.search() }} {{ icons.rss() }} {{ icons.tag() }} {{ icons.lock() }} {{ icons.trash() }} {{ icons.external_link() }} {{ icons.menu() }} {{ icons.back_to_top() }} — every inline SVG icon in <code>novaconium/pages/_layout/icons.twig</code>, all inheriting the surrounding text color via <code>currentColor</code>.</p>
{% endblock %}