ADR 006: Unified Perimeter — Storage Namespace & Blog Locale Sovereignty
Status: Active
Decider: Architecture Lead
Date: 2026-04-27 (v0.7.0 sprint, CEO 051, commit 3188387)
Context
This ADR is specific to the zenzic.dev documentation site (this repository),
not to the Zenzic CLI core. It documents two independent locale-bleed bugs that
were introduced when future.v4: true was activated in docusaurus.config.ts.
Bug 1 — The Theme Flip
With future.v4: true, Docusaurus enables siteStorageNamespacing: it auto-generates
a per-locale localStorage key by hashing url + baseUrl + locale. This produced:
| Locale | localStorage key |
|---|---|
English (/) | theme-926 |
Italian (/it/) | theme-3d7 |
When a user switched from the English to the Italian documentation, their browser
loaded a different localStorage key. Since the Italian key had no stored
preference, Docusaurus fell back to defaultMode: 'dark'. If the user had
previously switched to light mode in English, the switch caused an instant
dark mode revert — a visible FOUC (Flash of Unstyled Content) on every
locale switch.
Bug 2 — The Blog Locale Bleed
The Blog link in the navbar pointed to the blog using a standard Docusaurus navbar item:
// docusaurus.config.ts — original, broken
{ to: '/blog', label: 'Journal', position: 'left' }
Docusaurus's static build pipeline rewrites both to: and href: values in
navbar items for each locale's HTML output. In the Italian static build, this
became:
<!-- build/it/docs/*/index.html -->
<a href="/it/blog">Journal</a>
When a user navigated from Italian documentation to the Journal via that link,
they landed on /it/blog — which loaded the blog with the Italian locale UI:
dates rendered as "25 aprile 2026", labels appeared as "Etichette", the
reading time showed "9 minuti di lettura". The Blog is an English-only
content space and must never be locale-translated.
Switching from to: to href: did not fix the issue: href: values in
standard navbar items are also rewritten by the Docusaurus i18n build pipeline.
Decision
Two independent fixes were applied to docusaurus.config.ts:
Fix 1 — Unified Storage Namespace
// docusaurus.config.ts
storage: {
namespace: false,
},
The top-level storage.namespace: false overrides the future.v4
namespacing behaviour. Both locales now share the single key "theme" in
localStorage. Dark mode preference persists across all locale switches.
Verified in build output: The anti-FOUC inline script in both
build/index.html and build/it/index.html reads:
localStorage.getItem("theme")
Fix 2 — type: 'html' Locale-Sovereign Link
// docusaurus.config.ts — Blog navbar item
{
type: 'html',
value: '<a class="navbar__item navbar__link" href="/blog">Blog</a>',
position: 'left',
},
Docusaurus does not process the innerHTML of type: 'html' navbar items
through the i18n rewrite pipeline. The raw href="/blog" is preserved verbatim
in every locale's static HTML output.
Verified in build output: The Italian locale HTML contains:
href=/blog>Blog</a>
Not /it/blog — locale-sovereign.
Rejected Approaches
themeConfig.siteStorage.themeKey
Proposed in the CEO directive as a way to control the storage key. This property
does not exist in Docusaurus 3.x. There is no themeConfig.siteStorage
namespace. The correct API is the top-level storage object.
respectPrefersColorScheme: true
Also proposed in the CEO directive. This would instruct Docusaurus to follow the
OS-level color scheme preference on every page load — overriding the user's
explicit in-app preference. This directly reverts the CEO 149 invariant
(respectPrefersColorScheme: false) which was established as a permanent
protection against OS-preference-driven theme resets. It was not applied.
Invariants (Non-Negotiable)
-
storage: { namespace: false }must remain indocusaurus.config.tsfor aslong as
future.v4: trueis active and the Italian locale is supported. Removing it silently re-introduces per-locale storage key fragmentation. -
colorMode.respectPrefersColorSchememust remainfalse. This is animmutable invariant (CEO 149). Any PR that sets it to
trueis an automatic revert candidate. -
The Blog navbar item must remain
type: 'html'. Converting it back to astandard
to:orhref:item will re-introduce locale bleed in the next build. This is not immediately visible in development mode (npm run start) becausenpm run startserves a single locale without the rewrite pipeline. Bugs of this class are only visible injust buildoutput.
Consequences
-
Dark mode preference is now fully locale-independent. A user who sets dark mode
in English documentation retains dark mode when switching to Italian.
-
The Blog (blog) always loads at
/blogregardless of which locale the usernavigated from.
-
The
type: 'html'navbar item does not participate in Docusaurus'si18ntranslation pipeline (i.e., it does not appear in
code.jsontranslation keys). The label "Blog" is therefore hardcoded in the HTML value — this is intentional, as the blog is English-only and the label does not require translation.