Open navigation

Blog/release

v0.13.0 — API Surface Convergence + Phase Branded Types

Core API surface reduced from 18 to 6 exports, compile-time phase ordering via branded types, virtual module IDs migrated from core to adapter-vite, and CI coverage collection.

Overview

v0.13.0 is an architecture hardening release (ADR 0021). The focus is on narrowing the public API surface, adding compiler-enforced phase ordering, and eliminating the last cross-package coupling points.

No new features. Just tighter contracts.


Breaking Changes

Core exports: 18 → 6

@openelement/core now exports only 6 public subpaths:

// Remaining (public)
import { ... } from '@openelement/core';              // runtime utilities
import { LessError } from '@openelement/core/errors';
import { createSsrContext } from '@openelement/core/context';
import { createLogger } from '@openelement/core/logger';
import { navigate } from '@openelement/core/navigation';
// ./constants kept for cross-package shared constants

Removed (internal implementation details, no longer exposed):

  • ./render-dsd — internal, not user-facing
  • ./html-escape — internal
  • ./adapter-registry — internal
  • ./ssr-handler — deleted in v0.12, lingering export removed

Generated code resolves through CORE_SUBPATHS mapping in @openelement/adapter-vite.

ssr-handler.ts deleted

The pure re-export facade ssr-handler.ts is finally gone. Consumers import directly from @openelement/core.

Virtual module IDs moved

All Vite virtual module constants (VIRTUAL_NAV_ID, RESOLVED_BLOG_DATA_ID, etc.) moved from @openelement/core/constants to @openelement/adapter-vite/virtual-ids. Core no longer knows about Vite internals.


New (Internal)

Compile-time Phase ordering

Branded Type state machine ensures build phases execute in the correct order:

type Phase1Ready = { __phase: 'phase1' };
type Phase2Ready = { __phase: 'phase2' };
type Phase3Ready = { __phase: 'phase3' };

function buildPhase1(ctx): Phase1Ready & { ... }
function buildPhase2(state: Phase1Ready): Phase2Ready & { ... }
function buildPhase3(state: Phase2Ready): void

buildPhase2 only accepts Phase1Ready output. buildPhase3 only accepts Phase2Ready. The compiler catches out-of-order calls.

CI coverage

All test jobs now collect --coverage. Coverage reports generated on every CI run.


By-Product Cleanup

Item Change
Zero barrel files content/src/nav/index.ts, sitemap/types.ts — inlined
islandEffect interval 5000ms → 30000ms (reduced polling)
@openelement/app tests 0 → 16 tests

Package Versions

Package v0.12.x v0.13.0
@openelement/core 0.12.1 0.13.0
@openelement/adapter-vite 0.2.0 0.3.0
@openelement/content 0.3.2 0.3.3
@openelement/signal 0.6.2 0.6.3
@openelement/app 0.3.0 0.3.1
@openelement/i18n 0.1.0 0.1.1
@openelement/create 0.8.0 0.8.1

No changes to adapter-lit, ui, rpc.


Migration Guide

No changes needed. lessjs() abstracts all internal restructuring.

If you import from @openelement/core subpaths

Remove imports from removed subpaths:

// Delete these:
import { ... } from '@openelement/core/render-dsd';    // ❌ removed
import { ... } from '@openelement/core/html-escape';    // ❌ removed
import { ... } from '@openelement/core/adapter-registry'; // ❌ removed
import { ... } from '@openelement/core/ssr-handler';    // ❌ removed in v0.12

All runtime exports are available from the main entry:

import { escapeHtml, registerAdapter, renderDSD } from '@openelement/core'; // ✅

Stats

  • 86 files changed, +5,427 / −1,506 lines
  • 268 tests passing (41 new)
  • CI coverage: all jobs collect --coverage
  • Zero globalThis bridges: verified deleted in codebase
  • Zero barrel files: verified

Release date: 2026-05-12