This is the multi-page printable view of this section. .
Design decisions
A decision explains why OINK chose one compatible design over another. The five contracts above it remain the normative description of current behaviour; implementation and owning checkers remain the executable facts.
OINK used to keep reviews, PRDs, and execution notes in a local plan/
directory. That made useful reasoning hard to discover and allowed abandoned
designs to look authoritative. Accepted reasoning now lives here, in the same
bilingual, versioned site as the contracts it supports.
Decision map
| Decision | What it settles |
|---|---|
| Warnings and safe fallbacks | Why ordinary preview survives invalid input while publication remains strict |
| Configuration model | Where configuration belongs, how pages override it, and why OINK has no parallel configuration namespace |
| Markdown-first authoring | Why native Markdown is preferred and Docs, Blog, Book, and Landing extend shared systems |
Record format
An accepted decision records context, the choice, consequences, and the proof that makes the choice current. It does not reproduce a parameter reference or a tutorial. Every decision links to its owning contract and verification surface, and its English and Simplified Chinese pages change together.
When a decision changes, update the implementation, checker, affected contract, and decision record in one delivery. Preserve the old answer in Git history and the release changelog instead of leaving two active answers in the navigation tree.
Related
- Design contracts — current normative behaviour
- Research — dated evidence that informs decisions
- Proposals — ideas that have not been accepted
1 - Warnings and safe fallbacks
OINK does not call Hugo’s errorf. Invalid author or site input emits a
warning and either uses a documented safe fallback or omits the invalid
fragment. Release and deployment builds use --panicOnWarning, so the same
warning remains a hard publishing failure.
Context
Hugo builds the whole site as one transaction. An errorf raised while one
page is being edited makes every URL served by that rebuild return an error,
including unrelated pages and the home page. The server process survives and
recovers after the input is fixed, but collaborative preview is unavailable in
the meantime.
A warning has a different development cost. The affected value can fall back,
the rest of the site remains inspectable, and the author receives a precise
message. A publication build still fails because OINK’s CI and integration
gates add --panicOnWarning.
Decision
Validation follows four rules:
- Name the invalid key and value, the allowed shape, and the fallback.
- Include a page position when the value came from page front matter; avoid repeating one site-wide warning for every page.
- Never pass an invalid value into a later operation. Validate first, then render from the normalized value.
- Where no honest fallback exists, warn and render nothing. Do not invent content, make a network request, or emit an unsafe URL merely to keep going.
The shared enum, boolean, CSS-length, and number shapes live in
layouts/_partials/validate.html. Domain resolvers may add narrower checks,
but they preserve the same warning/fallback contract.
Safety boundary
Continuing a build never means continuing with unsafe output. A rejected CSS length falls back before it reaches a style attribute. An incomplete remote service configuration omits the component before the browser can make a request. An unsafe action URL is dropped. The protection is the absence of the bad output, not the act of terminating Hugo.
This also separates editing from publication cleanly:
| Stage | Invalid input |
|---|---|
hugo server or an ordinary local build |
Warn, fall back or omit, keep other pages available |
| CI, release validation, deployment | The same warning becomes a non-zero build under --panicOnWarning |
Consequences
- Every fallback is part of the public contract and must match the default declared by the theme.
- A change from failure to fallback also changes its tests. A negative test proves ordinary build survival, the warning text, the rendered fallback, and strict-build failure.
- Checkers must test the rejected output directly. A URL security test, for example, asserts that the unsafe URL is absent instead of treating any build failure as sufficient proof.
- The source-level parameter checker protects the invariant that theme layouts
contain no
errorfcalls.
Verification
The owning references are the
architecture contract,
bin/check-params.py, and strict builds of both the theme fixture and this
integration site.
2 - Configuration model
OINK keeps Hugo’s native keys and useful Docsy-compatible keys in place,
places theme presentation and behaviour under params.ui.*, and exposes a
matching top-level front-matter key for a page override. It does not add a
params.oink.* tree or a registry that shadows Hugo’s configuration model.
Context
OINK inherits a mature configuration surface and adds shells, content output, and local interaction. Earlier designs attempted to move every theme-owned key under a new namespace and resolve a complete configuration dictionary once per page. That produced a second language beside Hugo’s own keys, complicated section cascades, and made migration larger than the behaviour it was meant to control.
The current model keeps ownership visible instead:
| Layer | Responsibility | Examples |
|---|---|---|
| Hugo | Site identity, languages, menus, outputs, taxonomies, markup, modules | baseURL, languages, outputs |
| Site facts and integrations | Repository, version, author, local search, comments, external services | params.github_repo, params.version, params.comments |
| OINK interface | Shell, navigation, presentation, and local interaction | params.ui.sidebar_*, params.ui.typography, params.ui.share |
| Page or section | A narrow override of an eligible site default | sidebar_enabled, featured_image, share |
| Data files | Structured facts and ordered content that are not switches | data/landing, data/download, data/docs_nav.json |
Decision
The configuration API follows these rules:
- Site facts remain at the established top level. Interface choices belong
under
params.ui.*. - A page override drops the
ui.prefix and otherwise keeps the same name. A sectioncascadecan apply that top-level key to its descendants. - Boolean features use a scalar where that is the complete policy. A map is reserved for features with real subordinate settings; an established map may accept a boolean shorthand.
- Names are positive, snake_case, and grouped by function. Closely related settings share a prefix instead of growing another nested resolver.
- Theme defaults are declared in the theme’s
hugo.yaml. Templates may add a derived default only when one static value would erase a deliberate shell-specific distinction. - Each feature family owns its normalization and validation. A shared helper supplies common shapes, but there is no global compatibility registry that silently rewrites arbitrary old keys.
The complete current key list, types, and defaults live in the configuration reference. This decision records the placement rules; it is not a second parameter catalogue.
Compatibility
Public renames receive a targeted warning from the owning resolver, a migration note, and a negative test. Removed or misspelled keys do not justify a permanent alias layer. Hugo and third-party camelCase keys remain camelCase where changing them would break their native API; OINK-owned additions use snake_case.
Page values resolve through Hugo’s ordinary front-matter and cascade model.
OINK does not ask authors to put a nested ui: tree in front matter and does
not promise to merge arbitrary nested page maps.
Consequences
- Adding a public setting requires a declared default or an explicitly derived default, an owning resolver, documentation, and a positive and negative test.
- Configuration guides link to the one reference table instead of repeating types and defaults.
- A new data structure is justified by ordered or repeated facts, not merely by a desire to avoid adding a parameter.
- Invalid scalar values follow the warning and fallback decision.
Verification
bin/check-params.py audits declared defaults, page aliases, warning
behaviour, and the no-errorf invariant. The public reference and its Chinese
peer are checked in the integration site’s bilingual and rendered-link suites.
3 - Markdown-first authoring
Prefer a native Markdown form when Goldmark can preserve the intended semantics. Keep a shortcode only when it provides a capability the native form cannot express. Add a content scenario by extending an existing shell and data model, not by creating a parallel rendering system.
Context
OINK serves short manuals, large references, release archives, landing pages, and books. A survey of eleven consumer sites covered more than five thousand Markdown files and exposed both extremes: pages with almost no theme syntax and pages assembled from many nested shortcodes and local layout overrides.
A component API optimized only for the second group becomes a private DSL. An API optimized only for plain Markdown leaves books, rich figures, tab groups, and structured releases to site-local HTML. The useful boundary is capability, not novelty.
Decision
OINK applies the following order:
- Native Markdown first. Lists become Steps, Cards, or FileTree markers; tables become Fields or matrices; blockquotes become callouts; fenced code, images, and passthrough blocks carry attributes through render hooks.
- Shortcodes for missing capability. A full-form shortcode remains where CommonMark indentation, nested containers, processing options, or cross-page registration cannot express the same result safely.
- One semantic implementation. Native and full forms normalize into the same partials and output contract. They are not two components that merely look alike.
- One extension line. A new Landing section joins the section registry; a new Blog presentation remains a Blog variant; Book numbering joins the content primitive and navigation systems. OINK does not add a second card, landing, navigation, or article shell for one feature.
- Facts stay outside presentation strings. Versions, repositories, dates, and ordered records come from front matter, site parameters, or data files. A shortcode argument is not a second source of truth.
Output contract
An authoring form is complete only when its semantic content has a deliberate result in every enabled output:
| Output | Requirement |
|---|---|
| HTML | Semantic server-rendered content; JavaScript only enhances it |
| Static, expanded, and free of controls that require interaction | |
| Markdown / LLMS | Source-shaped prose, links, lists, tables, and fences; no component HTML |
| RSS | Safe static content or an explicit omission |
This requirement prevents an attractive HTML-only component from silently damaging agent output, feeds, or a printable book.
Trust and presentation
Render hooks and shortcodes consume explicit allowlists. Unsafe URL schemes, inline event handlers, and arbitrary style input are dropped. Author-provided classes are accepted only on the documented surfaces where downstream site CSS is part of the established authoring contract. Icons use one Font Awesome class pair; OINK does not invent a second icon-ID language.
Consequences
- A proposed component must first show why Markdown plus an existing hook is insufficient.
- Keeping a full-form shortcode requires a named capability and tests for both forms reaching the same normalized output.
- Shell variants use independent presentation keys so opting into a hero or a flow outline does not change taxonomies, feeds, pager order, or content type.
- Consumer evidence is dated research, not a permanent excuse to freeze an accidental syntax. The current public surface remains defined by the component contract and shell contract.
Verification
The authoring contract is exercised by theme component, Book, output, and golden checkers, then by this site’s bilingual examples and browser suites. The Goldmark facts behind the native forms are recorded in block-attribute research.