Upload/browse/delete UI for files under public/uploads/, covered by the
existing /admin/* auth gate with no separate feature flag needed (no
SQLite dependency to gate). Extension allowlist and max upload size are
configurable; filenames are sanitized and de-duplicated on upload, and
deletes re-verify the resolved path lands inside the upload directory
before touching disk. Docker gains a fourth-turned-third named volume
for public/uploads/ so uploads survive a rebuild.
images/ (reserved scaffolding for a future image feature) is removed —
nothing ever consumed it, and public/uploads/ now covers that use case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Colors <pre><code> blocks site-wide via vendored highlight.js v11.11.1
(pinned to that stable tag, not main, which tracks an in-progress
11.0.0-beta1), auto-detected and restricted to
configure({ languages: ['php', 'bash', 'xml', 'css', 'python',
'javascript', 'yaml', 'json', 'ini'] }) - no per-block markup needed for
the ~60 existing code blocks across the site. css/python/javascript ship
in the core bundle; yaml/json/ini (ini covers .env-style files too)
don't and are vendored as separate per-language files.
Themes swap with the existing dark/light toggle: ir-black (dark) +
github (light, resolving the entry's own open question), via the same
data-theme-driven mechanism as the main palette
(syntax-highlight-init.twig/syntax-highlight.twig, mirroring
theme-init.twig/nav.twig's split) - a MutationObserver swaps the theme
link live without touching the existing toggle button's click handler.
Twig-syntax code blocks have no highlight.js grammar and are marked
class="nohighlight" by hand (15 blocks across 9 files, found by grepping
for literal {% %}/{{ }} syntax rather than guessing) rather than
force-matched into the restricted candidate set, which would color them
wrong instead of leaving them plain.
One correction to the original backlog entry's suggested approach: it
suggested vendoring highlight.js under novaconium/vendor/ next to Twig.
That would have silently 404ed on every request - Twig is server-side
PHP, never fetched by a browser, but highlight.js's .js/.css files are,
and only public/ is web-reachable. Vendored to public/vendor/highlightjs/
instead; documented in AGENTS.md and a new upgrading-highlightjs doc,
since public/ isn't touched by the usual novaconium/-swap framework
update workflow, so a future highlight.js bump won't propagate to
existing projects automatically the way it does for everything else
under novaconium/.
Caught two real bugs via testing rather than review: hljs.highlightAll()
silently no-ops if called before the document finishes parsing rather
than deferring itself, and a bash example starting with the word "php"
auto-detects as PHP, not bash.
Also adds App/pages/blog/code-highlighting/ - a new blog post
demonstrating the feature with a verified worked example in each of the
nine languages, plus how to force a language via an explicit
language-<name> class when auto-detection isn't enough.
Closes the "Syntax highlighting on code blocks" backlog item in
novaconium/ISSUES.md.
Blog RSS feed (novaconium/ISSUES.md):
- App/pages/blog/feed/index.php - main feed, built from the same
hand-written $posts array App/pages/blog/index.php renders from, so it
works with content_index_enabled left at its default false. Added a
published date field per post entry.
- App/pages/blog/tag/[tag]/feed/index.php - per-tag feed, gated on
content_index_enabled the same way blog/tag/[tag]/index.php is.
- novaconium/lib/Rss.php - shared RSS 2.0 envelope builder both feeds
use, generic (title/link/description/items in, XML string out).
- New head_extra block in the root layout (empty by default) so
App/pages/blog/_layout/layout.twig can add feed auto-discovery scoped
to /blog/* only.
Footer:
- New content_index_enabled Twig global (Renderer/bootstrap.php), same
pattern as admin_auth_enabled.
- Right-aligned footer menu linking to /sitemap.xml (only shown when
content_index_enabled, so it never links to a 404) and /blog/feed
(always shown, no content-index dependency).
Docs:
- New /admin/docs/rss page: Lib\Rss API, the two shipped feeds, a worked
example of adding a feed for another content collection, and how to
advertise multiple feeds via head_extra.
- New /admin/docs/sitemap page: changefreq/priority blocks, what's
included/excluded, an honest callout on the relative-<loc>-URL spec
deviation (consistent with how canonical/og:url already work) with an
override path for strict compliance, and submitting it to search
engines.
- /admin/docs (Overview) gains a Requirements section: minimum
requirements, then optional requirements for the database/content-index
features (pdo_sqlite, optional pdo_mysql, FTS5) - and a Documentation
heading separating it from the doc links list.
- /admin/docs/getting-started's Requirements line was stale (missing
pdo_sqlite entirely, unlike README) - fixed and cross-linked to the
Overview page's fuller list.
- /admin/docs/content-index trimmed to point at the new RSS/sitemap pages
instead of duplicating their detail.
Also adds an "In-house comments" entry to novaconium/ISSUES.md's Backlog
- a Lib\ class for sidecar-attached comments on any page, depending on
the not-yet-built Admin login & user management for real user accounts.
Closes the "Blog RSS feed" backlog item.
Injects a hover-revealed copy button into every <pre><code> block
site-wide via a single layout partial, reading textContent so escaped
samples copy as literal text. Ships copy/check icons and matching Sass.
Passes icon markup to JS via <template> elements instead of Twig's
|escape('js'), which calls mb_ord() and fatals without mbstring — same
class of bug as the existing |slice/mb_substr gotcha, now documented in
AGENTS.md.
Closes the "Copy-to-clipboard button on code blocks" backlog item in
novaconium/ISSUES.md.
Full rewrite: swap out the v1 framework (src/, controllers/, views/,
twig/, sass/, skeleton/) for the working v2 codebase from phpproject
(App/, novaconium/, public/).