OINK implementation diary: from copied shells to one theme

A technical diary of the decisions, migrations, safety boundaries, tests, and documentation work behind the OINK implementation preview.

OINK began with an awkward observation: several production documentation sites looked related because they were related, but their common implementation lived as copied files. The visible result was consistent enough; the maintenance model was not.

This diary records how the project moved from repeated site overrides to one directly evolved theme. It focuses on decisions and evidence rather than a commit-by-commit transcript.

Locking the contract

The first useful work was subtraction. We wrote down what the product must be before choosing how to implement it:

  • an independent theme derived directly from Docsy;
  • one canonical shell, not a switchable skin;
  • Hugo Extended as the only consumer build dependency;
  • local-first delivery for every theme-owned browser asset;
  • multilingual behavior derived from Hugo rather than PGSTY domains;
  • reusable components in the theme, business semantics in the site;
  • preserved Docsy history, licenses, and upstream traceability.

This ruled out an attractive but costly shortcut: adding params.oink.enabled and leaving the old shell in place. A mode switch would have made every layout change, accessibility fix, and test support two products. Direct evolution made the intended design the only design.

Replacing the shell

The documentation, blog, and API-reference layouts were rebuilt around shared, small partials. The resulting shell includes:

  • global navigation and responsive sub-navigation;
  • a resizable, foldable sidebar;
  • local search and quick links;
  • language and color-mode controls;
  • breadcrumbs, table of contents, page metadata, and feedback;
  • a consistent footer and print layout.

The hard part was not drawing a navbar. It was preserving existing Docsy extension points while removing copied baseof.html files. Narrow hooks remain; site-wide shell duplication no longer has to be the normal customization path.

Removing the consumer toolchain

The original dependency chain assumed npm-provided Bootstrap and Font Awesome, and some paths invoked PostCSS. OINK moved the required sources and compiled artifacts into the theme and kept SCSS inside Hugo’s own asset pipeline.

Tests do more than check that hugo succeeds. Fixture traps fail if a consumer build tries to run Node.js, npm, PostCSS, or Autoprefixer, or if a template uses resources.GetRemote. LTR and RTL pages go through the same constraint.

This distinction matters: the repository still uses Node for its maintainer test harness. “Hugo-only” describes what a consuming site needs after it has a complete theme, not a ban on development tooling inside the theme repository.

Vendoring browser runtimes

The next layer was every dependency the browser might otherwise fetch: Bootstrap, Font Awesome, fonts, jQuery, Lunr, Mermaid, KaTeX, Markmap, Swagger UI, Redoc, Asciinema, ECharts, Infographic, and their supporting libraries.

Each selected artifact received a source, fixed version, license path, checksum, and update procedure in theme/VENDOR.json. Licenses live beside the vendored material. The manifest is validated against the actual files rather than treated as an aspirational inventory.

PlantUML and Diagrams.net forced a useful distinction. They are service-backed features, not merely JavaScript libraries. OINK refuses to invent a public endpoint: enabling one without a configured service fails the build.

Building the multilingual core

The previous language behavior was scattered across navigation and site-specific assumptions. The new core starts with Hugo’s configured languages, .Translations, and .AllTranslations.

The presentation is deliberately stable: one language hides the selector; two or more use the same icon button. Clicking advances by configured weight, while a short hover or keyboard focus opens the complete language menu.

A missing translation falls back to the target-language home page. Language labels use each language’s own name. The same objects drive lang, direction, canonical, hreflang, and Open Graph locale metadata, so the visible selector cannot drift from SEO output.

The tests construct every state with an RTL current language as well as normal LTR cases. Native links and disclosure controls keep keyboard behavior predictable.

Promoting components

Asciinema, ECharts, Infographic, document carousel, details, tabs, cards, and parameter rendering had already proved valuable in PGSTY sites. The work was to turn them from copies into product APIs:

  • normalize parameter names and defaults;
  • generate unique IDs from page identity and shortcode ordinal;
  • load each runtime once per page and omit it from unused pages;
  • preserve subpath URLs;
  • support repeated identical instances;
  • provide print, dark-mode, mobile, keyboard, and reduced-motion behavior;
  • retain compatible aliases for imported content.

Product matrices and other business widgets did not move. Reuse is not measured by the number of repositories containing a copy; it requires a stable, business-neutral contract.

Designing safe ECharts

ECharts exposed the sharpest migration edge. Existing pages sometimes placed arbitrary JavaScript functions in chart blocks. Simply preserving that behavior would make executable content the default API; banning it immediately would break proven production pages.

The solution separates modes:

  1. new content supplies JSON or YAML, which Hugo parses and safely serializes;
  2. JavaScript is rejected by default;
  3. a reviewed legacy instance can set unsafe=true;
  4. a site-wide params.content.echarts_unsafe switch exists only as a migration bridge.

Tests include a </script> boundary payload, repeated identical charts, invalid CSS lengths, and explicit acceptance and rejection cases.

Creating the starter and archive

A contract is easier to trust when the smallest example demonstrates it. The starter contains bilingual home, docs, blog, and component pages; local search; dark mode; diagrams; API documentation; and the new components. It has no package.json and no site workflow.

The offline packager combines theme/, starter/, licenses, the upstream record, and migration guidance, excluding generated output and dependency caches. It writes a sidecar SHA-256 file and refuses to overwrite an existing artifact.

The acceptance test copies the starter and theme to a temporary directory, empties caches, blocks HTTP/HTTPS and Go proxies, builds with Hugo, and examines HTML and CSS for third-party subresources.

Rehearsing four migrations

SILO, PGSTY, SOW, and Pigsty provided the reality check. The rehearsal tool copies each workspace instead of modifying it, removes only classified common overrides, applies a local theme replacement, forbids network and frontend tools, and runs the production build.

The latest rehearsal removed 20 common overrides from SILO, PGSTY, and SOW and 24 from Pigsty. Pigsty retained its three business matrix shortcodes and used the explicit legacy ECharts bridge. All temporary copies built, producing 1,095, 16, 128, and 2,473 HTML files respectively.

Those numbers prove the rehearsal at the recorded commits. They do not prove that any production repository was changed or any hosted site was deployed.

Turning the sample into OINK docs

The inherited docsy.dev site was valuable as a regression corpus, but it described only Docsy. The documentation phase did four things:

  1. made English primary and Simplified Chinese second; a later shell review removed French from the demonstration site;
  2. translated every core documentation and blog source into a colocated .zh.md page;
  3. preserved English heading IDs explicitly in every Chinese heading;
  4. added the OINK product guide, announcement, and this implementation diary.

Before translation, we established a terminology and typography guide. A checker then verifies source/translation pairs, heading counts, explicit Chinese IDs, and rendered English/Chinese heading-ID equality.

Historical Docsy release posts remain faithful translations. Their npm-era instructions are historical context, while the OINK architecture and migration guides state the current Hugo-only product contract.

What testing changed

Several tests changed the design rather than merely blessing it:

  • subpath fixtures forced every local component URL through Hugo URL handling;
  • repeated-instance tests replaced content hashes with page-and-ordinal IDs;
  • offline browser checks exposed implicit runtime requests;
  • RTL language matrices prevented a selector implementation that only worked for the starter’s two LTR languages;
  • unsafe ECharts rejection made the security boundary executable;
  • migration rehearsals preserved site-specific partials that a blanket layouts/ deletion would have removed.

The strongest test suite is one that constrains the product boundary, not just the current HTML snapshot.

Remaining work

Two release gates remain intentionally open. The public brand, repository, module and package identities, and first version need approval. A real Cloudflare Pages project then needs to build from the source branch and pass hosted verification.

Production migrations should proceed one site at a time, with dedicated branches, preview deployments, visual regression, and rollback artifacts. The temporary four-site rehearsal is a foundation for that work, not a substitute.

Lessons

  • Write the product boundary before moving files.
  • A local-first promise needs both build-time and browser-time evidence.
  • Configuration is healthier when it expresses user choices, not internal implementation branches.
  • Translation quality includes stable links, code fidelity, typography, and rendered structure—not prose alone.
  • Reuse should remove maintenance copies without absorbing business semantics.
  • “Built,” “packaged,” “published,” “deployed,” and “migrated” are different claims and need different evidence.

The result is less dramatic than a rewrite and more useful: one theme that can be understood, built, tested, translated, and migrated as a coherent product.