Skip to content

fix: make silent failures observable in dev and strip diagnostics fro… - #65

Merged
hexplus merged 5 commits into
mainfrom
chore/framework-hardening-feedback
Sep 7, 2026
Merged

fix: make silent failures observable in dev and strip diagnostics fro…#65
hexplus merged 5 commits into
mainfrom
chore/framework-hardening-feedback

Conversation

@hexplus

@hexplus hexplus commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Description

Six failure modes where the runtime did the wrong thing — or the right thing for an unstated reason — without saying so, plus the reason none of the existing warnings ever reached a developer.

The gate was broken. Dev diagnostics never compiled out. The dev flag led with a globalThis.__SIBU_DEV__ lookup, and a member expression is not something a bundler can substitute or fold, so every warning string shipped to production to be tested at runtime and never printed. Fixed with a statically foldable constant plus warning helpers that test the build-time define inline, so their bodies fold to nothing even in a published dist chunk.

The CDN shipped them too, and could not strip them. The CDN build applied no __SIBU_DEV__ define at all, and a <script src="…/cdn.global.js"> consumer has no bundler to fold it later — the published bytes are what runs. With no define and no process in a browser the gate resolves to false anyway, so those bytes were downloaded and parsed to never print. The build now emits two artifacts: dist/cdn.global.js (production, diagnostics compiled out) and dist/cdn.dev.global.js (development, exported as sibujs/cdn-dev), so the no-build workflow keeps its warnings.

Fixed

  • RouterLink silently ignored a reactive class — read with typeof classAttr === "string", so a getter or conditional map fell through to "" and the attribute was never written.
  • A focused field lost the caret on every reactive rebuild. Focus and selection are now restored when the rebuilt subtree contains an element of re-establishable identity (data-focus-key, id, name). It does not guess: a match must be unique (name is shared by every radio in a group), and focus is only restored when genuinely lost, never when the rebuild moved it deliberately. IME composition cannot be preserved — it belongs to the destroyed node — and the warning says so.
  • The style sanitizer dropped url() declarations in silence, so blocked images rendered as empty boxes with nothing in any console. The guard stays; each drop is announced in dev with property, value, element, reason and the sanctioned alternatives.
  • The duplicate-runtime warning gave obsolete optimizeDeps.exclude advice; the global registry already makes it unnecessary.

Changed

  • show, when and match accept both an element and a factory. Passing the wrong shape used to raise an obscure TypeError, or — for when — render nothing silently. A re-attached bare element keeps its own reactive bindings, since the directive did not create it and does not tear it down.
  • The lone-string class heuristic now requires two or more tokens with at least two utility-shaped. The old rule flagged item-0, home-content, user-42, v4.1.0, src/index.ts, https://example.com and N/A at a measured 29.8% false-positive rate, and a list rendering item-0item-999 produced a thousand warnings. The new rule measures 0% on the same 131-string corpus. The cost, stated plainly: single-token class lists no longer warn — div("space-y-6") passes silently, where it used to be caught. A warning developers learn to ignore protects nobody.
  • Repeated mistakes are de-duplicated, capped at 100 distinct entries. Reaching the cap stops reporting rather than merely stopping remembering — the latter let every mistake after the hundredth warn on every render. The suppression announces itself once.
  • sanitizeCSSValue, sanitizeStyleAttribute and sanitizeAttributeString take an optional context used only to enrich the dev warning; it never changes a security decision.

Size — production is smaller than 4.1.0 despite everything above, measured against a clean build of main:

gzipped, prod 4.1.0 4.2.0
sibujs 26,895 25,836
sibujs/plugins 28,877 28,615
sibujs/ui 16,462 16,112
dist/cdn.global.js 27,289 26,224
app exercising every warning 33,658 32,958

dist/cdn.dev.global.js is 29,720 gzipped and is not shipped to end users.

Guardrailstreeshaking-dev-diagnostics.test.ts bundles source and fails if any diagnostic string survives a production build; dist-artifacts.test.ts asserts the same against the built dist/ files, which is where the CDN leak hid. It skips locally when dist/ is absent but fails on CI.

Known limitation, deliberately not changed: --behavior: red is dropped by the property-qualified check, over-blocking a harmless custom property. Pre-existing, and loosening it changes the security posture.

Related Issue

Closes #

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • I have read CONTRIBUTING.md
  • My code builds without errors
  • I have tested my changes
  • I have updated documentation if needed

@hexplus
hexplus merged commit a2ce84b into main Sep 7, 2026
5 checks passed
@hexplus
hexplus deleted the chore/framework-hardening-feedback branch September 7, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant