Early in a static project, the mental model is pleasantly simple: source files go in, a command produces an output directory, and hosting serves it. That is still technically true for Blupoli, but it no longer describes what publishing means. The repository contains more than the public product. It contains finished games and games still being prepared, editorial material at different stages, compatibility with older names, assets that need normalization, routes in several languages, and scripts deriving information that should never be maintained by hand. If the build only copied things, it would expose internal implementation state as product decisions.

Recent changes forced us to formalize an idea that had already appeared in several parts of the system: existing in source is not the same as existing in production. Between those states we need a publication layer that interprets registries, applies localization, generates routes, normalizes resources, injects observability and finally rejects output that contradicts public contracts. This is not another explanation of why we chose quality over quantity — that story is in Quality before quantity. This is the machinery that makes that choice enforceable without relying on somebody remembering what should or should not ship.

The build stopped being a photocopier

A simple case shows the difference. Suppose we add a game to the catalog because we intend to work on it. We need a name, category, metadata and perhaps a visible card so people can see that it is coming. But we do not want a playable public route, we do not want the random-game action to select it, we do not want the sitemap to advertise it as available, and we do not want counts of finished games to increase. If the existence of a directory were enough to publish, every one of those behaviors would require a later exception.

The release registry reverses that relationship. A new game starts as coming-soon. The catalog can know about it, while public surfaces derive behavior from state. Once the game passes completion work, the state changes and the same pipeline can produce coherent routes, links and statistics. The source of truth stops being “files exist” and becomes “the registry declares this experience available.” That distinction is tiny in JSON and enormous in product behavior.

Publication pipeline

The repository contains possibilities; the build decides which combination satisfies the public contract.

SourcesApps, games, editorial content, translations and assets.
RegistriesRelease state, editorial grouping and locales.
TransformationRoutes, normalization, SEO, observability and resources.
GatesInvariants that must hold before deployment.
DistOnly the validated public representation.

A release state has to affect everything, not only the button

Showing “Coming soon” on a card is the visible part of the system, but it would be meaningless if an indexable game route still existed behind it. Release state therefore flows through several layers. Future games appear as non-navigable cards with differentiated treatment. Their public game routes are not emitted. They are excluded from sitemap and from links that promise play. Random discovery must not send somebody into an unpublished experience. Global, mode and category counters distinguish between announced and playable inventory instead of inflating availability.

The follow-up fix that kept upcoming games visible without counting them as available matters because it shows how even a good state model can fail when a consumer asks the wrong question. Having available and coming-soon is not enough. Every surface needs to know what it is measuring. “How many games does the catalog know?” and “how many can I play now?” are different metrics. The pipeline derives data so pages do not reinvent that definition independently.

That is also why catalog-stats.json exists. Instead of maintaining a fixed catalog number in several pieces of copy, the build calculates the real state. That removes a kind of debt we had already experienced: public claims becoming stale as soon as inventory changes. The game count stops being an editorial constant and becomes a result. More importantly, the result can use the correct definition of “published.”

English-first changed the input side of the pipeline

Multilingual publication created a different challenge. Blupoli had grown with a lot of content and decisions authored in Spanish while the product already served several languages. Making English the default public experience could not be reduced to translating the home page. We had to decide what the root means, how localized variants are constructed, how the shared selector behaves, and what happens when an editorial item does not yet have an equivalent translated version.

Recent work established English as the real default on the main site and added localized home experiences for Spanish, Italian, Portuguese, French and German. In Puzzles, the game runtime also needs to provide real English UI. The subtlety is that “default” does not necessarily mean “the same route shape” in every product. The publication layer has to understand each application’s conventions and generate coherent links without duplicating content or creating competing SEO routes.

Editorial fallback illustrates the balance. If an English index does not yet have a translated card, the system may temporarily need to use the Spanish source rather than leave a hole or break the page. That does not make Spanish the platform default; it is an explicit degradation strategy while editorial coverage catches up. What matters is that the decision happens in a known, testable layer rather than accidentally because one file happened to be discovered first.

Localization also means localizing metadata

A page showing English while keeping a Spanish title, description or structured data is not really localized. The same applies to canonical links, hreflang, Open Graph and social cards. The editorial architecture already treated SEO as part of content; English-first made it even clearer that the pipeline must review each locale as a complete publication.

That is why localization and SEO are attached to the build instead of being a manual afterthought. Variants need natural titles, not mechanical substitutions. Technical identifiers remain stable while visible prose changes. Routes must point to real equivalents. And if a page is published across six languages, checks must traverse all six representations. A defect in the fifth variant is still a production defect even when the English root works.

Observability had to enter before deployment

Another limitation of the “copy and serve” model is that it says nothing about what happens after deployment. A static site can fail in the browser while hosting delivers every file successfully: a JavaScript exception, a rejected promise, a navigation path that does not execute as expected. Without a minimum observability layer, those failures exist only for the person who encounters them.

The recent integration added Firebase Analytics to the main site and Puzzles through Hosting configuration, together with consent shared across the parent domain and subdomains. The banner is localized for the six public languages. We also capture window.error and unhandledrejection as exception events, while a small window.BlupoliObservability.logEvent(...) API allows product code to record its own events without coupling every module to initialization details.

It is important to state the scope accurately. This is not browser Crashlytics and it is not a complete advanced error-reporting system with rich traces. That level would require a dedicated reporting solution. What we do have is a common layer that turns some previously silent failures into signals and can be inserted consistently into generated HTML. Publication no longer ends when a file reaches the CDN; it includes a basic ability to observe how the result behaves afterwards.

Shared consent is another platform boundary

Analytics introduced the same boundary problem as theme and language: Blupoli should not ask for an independent consent decision every time a user crosses to a subdomain. The solution needed shared state and a banner that could render correctly in each locale. Observability infrastructure could not be designed only as “send events.” It also had to encode when and under what user decision those events may be sent.

That requirement reaches the build as well. Instrumentation is injected into the relevant outputs, and Content Security Policy needs to permit the required endpoints. If a page misses the injection or security policy blocks transport, observability exists in source but not in the product. The same theme appears again: the pipeline does not passively move files; it composes an operational contract.

Editorial covers exposed a second kind of state

While the game registry separates “implemented” from “published,” the editorial system needed to separate “asset exists” from “asset is canonical.” As Blog and Devlog evolved, old article-local cover references coexisted with the newer editorial asset scheme. That created a concrete risk: a page could inherit one cover, insert another, and end up with duplicates, or Open Graph could point at a resource different from the image shown in the article.

The recent fixes addressed this in two steps. First, the build correctly preserved required source images, removed duplicate inherited article-cover markup, and migrated old references toward canonical editorial assets. Then a dedicated normalization phase was placed at a stable point in the pipeline: after editorial architecture and before the quality gate. It removes legacy cover img or figure remnants, inserts exactly one canonical cover, and updates og:image, twitter:image and JSON-LD image metadata so they all describe the same resource.

One public truth

The same principle governs games and articles: source may exist without every representation being publishable.

Game in sourceMay be announced while still unavailable.
Release registryControls routes, links and public counting.
Article in sourceMay contain legacy references or partial assets.
Editorial normalizationControls canonical cover, metadata and structure.

Why “exactly one cover” deserves a gate

Failing a build because a page contains two covers may sound excessive. Yet editorial duplication is not only aesthetic. It can affect layout, performance, screen-reader output, social previews and perceived quality. More importantly, it is a condition a machine can verify without ambiguity. If the contract says one canonical cover per article, accepting zero or two turns a precise rule into a suggestion.

The current gate checks language variants and fails if legacy cover paths remain or the cover count is not what the contract expects. Validation also catches local editorial images that do not exist. This kind of check has excellent leverage: a broken image can hide in an article nobody happens to open during review, while the build can traverse every document systematically.

Normalize after generation, validate after normalization

The order of phases turned out to matter as much as the phases themselves. If we validate too early, we may reject source that the pipeline is explicitly designed to normalize. If normalization happens after the quality gate, the gate reviews a representation different from the one we actually deploy. That is why canonical cover processing happens after editorial architecture and before final validation. The gate sees the output we intend to publish.

The same logic applies beyond covers. Localization must run before language coverage can be checked. Route generation must incorporate release state before searching for links to future games. Instrumentation needs to be present before CSP assumptions are validated. A reliable pipeline is not a list of scripts; it is a sequence in which every stage can rely on contracts established by the previous one.

Gates should not test vague intentions

Another lesson has been to phrase invariants concretely. “The site should be well localized” is too broad for a test. “No legacy selector remains,” “each article has one canonical cover,” “a coming-soon game has no public route,” “no public link points to a future experience,” and “no stale fixed claim about an old catalog size remains” are much more useful. A gate works when it can tell us which contract was violated.

That does not remove human review. An article can have exactly one cover and still have a weak cover. A translation can contain every required string and still sound unnatural. A game can be correctly marked available and have confusing interaction. We automate invariants so human review does not waste attention counting tags or hunting missing files. Editorial and product quality still require reading, playing and judgment.

The pipeline also protects project history

The transition from Blupoli Puzzles to Blupoli left older names in internal files and routes that still serve compatibility purposes. The build has progressively normalized the public identity without requiring a destructive purge of every historical identifier. That matters because migration does not mean pretending the past never happened. Historical references can remain correct in articles describing that period while current titles, navigation and product language use Blupoli.

The same strategy reduces technical risk. Renaming every internal file in one operation can break scripts without producing any user-facing improvement. Establishing new public contracts and adding gates against stale branding on current surfaces lets us move incrementally instead. The pipeline becomes a boundary: it tolerates internal compatibility where necessary while preventing that compatibility from redefining the public identity.

What daily development gains

The practical consequence is that adding work to the repository becomes safer. We can register a game before finishing it without accidentally publishing it. We can retain editorial source and let a canonical phase resolve the cover. We can add a locale knowing checks will traverse its output. We can update a shared asset and detect stale references. Infrastructure does not replace care, but it turns intermediate state into an expected condition rather than a threat.

Debugging becomes more structured too. If a count is wrong, we can ask which registry and derivation produced it. If a cover duplicates, there is a specific phase responsible for normalization. If a future game has a link, the gate should identify the violation. If a browser exception occurs, we have at least a basic observability signal. Architecture creates places to look, and that property matters more as the repository grows.

What the public experience gains

For visitors, the result is less dramatic but more important. Counts describe what can actually be played. Upcoming games can be discovered without becoming dead ends. The entry language is coherent. Editorial covers do not duplicate, and social previews reference the same canonical resource as the page. Consent does not arbitrarily reset when crossing a product boundary. And some browser failures leave a signal we can investigate.

All of these changes share one characteristic: they are consistency problems. None adds a puzzle mechanic. None creates a spectacular release-note screenshot on its own. Together, however, they determine whether Blupoli behaves like a pile of generated files or like a product that understands its own state.

What remains

The pipeline can still improve. Web observability is intentionally basic and does not replace a specialized error-reporting platform. End-to-end tests can cover cross-domain, cross-locale and persisted-state transitions more deeply. Editorial publication can continue removing compatibility layers once they stop being necessary. And every future product in the monorepo will test whether current contracts are truly platform-level or were accidentally tailored too closely to Puzzles.

We also need to watch the cost of gates. A useful validation should be fast enough and explain failure clearly. If every rule adds minutes or produces opaque messages, developers start experiencing protection as friction. The natural direction is to keep cheap invariants close to the normal build and reserve expensive tests for places where they produce meaningful evidence.

The lesson: publishing means interpreting state

The idea that summarizes this phase is simple: a modern publication system, even when its final output is static HTML, is not a copy operation. It is an interpreter for project state. It reads which games are ready, which locales exist, which content belongs to each editorial section, which assets are canonical, which compatibility must remain and which conditions make output invalid. It then produces a public representation that should be stricter and more coherent than the source tree it came from.

This connects with our earlier work on six-language i18n and quality gates and with the architecture that separated Blog and Devlog, but the recent system goes a step further: those ideas are no longer isolated by area. Game releases, localization, editorial structure, assets and observability now converge at the same publication boundary.

The advantage is not sophistication for its own sake. The advantage is the ability to express product decisions as executable contracts. “Coming soon does not mean playable.” “English by default does not mean Spanish metadata.” “One canonical cover means one.” “A shared preference should not reset when changing surfaces.” When those sentences live only in documentation, they depend on memory. When a reasonable portion lives in the pipeline, the system helps remember them.

That lets the repository keep growing without requiring every change to know Blupoli’s entire history. Source can contain experiments, migrations and unfinished work. Public output does not need to expose that complexity. There is now a more deliberate boundary between the two: a pipeline that asks not only “can I generate this page?” but “does this page correctly represent the state we intend to publish?” That second question is what turns a build into product infrastructure.