From 86ffb79193cfb271dc52c9f004f46fc51678a300 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 22 Mar 2026 12:46:00 -0500 Subject: [PATCH] refactor: better cdn failure messaging. --- docs/next-steps.md | 9 ++------- playwright/app.spec.ts | 22 ++++++++++++++++++++++ src/modules/render-runtime.js | 14 ++++++++++++-- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/next-steps.md b/docs/next-steps.md index de4148e..1eb5f7c 100644 --- a/docs/next-steps.md +++ b/docs/next-steps.md @@ -6,12 +6,7 @@ Focused follow-up work for `@knighted/develop`. - Explore authoring and running component-focused tests in-browser (for example, a Vitest-compatible flow) using CDN-delivered tooling. - Define a lightweight test UX that supports writing tests, running them on demand, and displaying results in-app. -2. **CDN failure recovery UX** - - Detect transient CDN/module loading failures and surface a clear recovery action in-app. - - Add a user-triggered retry path (for example, Reload page / Force reload) when runtime bootstrap imports fail. - - Consider an optional automatic one-time retry before showing recovery controls, while avoiding infinite reload loops. - -3. **Deterministic E2E lane in CI** +2. **Deterministic E2E lane in CI** - Add an integration-style E2E path that uses locally served/pinned copies of CDN runtime dependencies for test execution, while keeping production runtime behavior unchanged. - Keep the current true CDN-backed E2E path as a separate smoke check, but make the deterministic lane the required gate for pull requests. - Run this deterministic E2E suite on **every pull request** in CI. @@ -19,7 +14,7 @@ Focused follow-up work for `@knighted/develop`. - Suggested implementation prompt: - "Add a deterministic E2E execution mode for `@knighted/develop` that serves pinned runtime artifacts locally (instead of live CDN fetches) and wire it into CI as a required check on every PR. Keep a separate lightweight CDN-smoke E2E check for real-network coverage. Validate with `npm run lint`, deterministic Playwright PR checks, and one CDN-smoke Playwright run." -4. **Issue #18 continuation (resume from Phase 3)** +3. **Issue #18 continuation (resume from Phase 3)** - Current rollout status: - Phase 0 complete: feature flag + scaffolding. - Phase 1 complete: BYOT token flow, localStorage persistence, writable repo discovery/filtering. diff --git a/playwright/app.spec.ts b/playwright/app.spec.ts index 6a3205c..4bf0942 100644 --- a/playwright/app.spec.ts +++ b/playwright/app.spec.ts @@ -189,6 +189,28 @@ test('BYOT controls stay hidden when feature flag is disabled', async ({ page }) await expect(page.locator('#ai-chat-drawer')).toBeHidden() }) +test('shows actionable status when core CDN dependency loading fails', async ({ + page, +}) => { + await page.route('**/*', async route => { + const url = route.request().url() + + if (url.includes('@knighted') || url.includes('%40knighted')) { + await route.abort() + return + } + + await route.continue() + }) + await waitForAppReady(page) + await expect(page.locator('#status')).toHaveText( + 'Error loading CDN dependency. Reload to retry.', + ) + await expect(page.locator('#preview-host')).toContainText( + 'Unable to load core runtime from CDN:', + ) +}) + test('BYOT controls render when feature flag is enabled by query param', async ({ page, }) => { diff --git a/src/modules/render-runtime.js b/src/modules/render-runtime.js index d666ed4..879ed7d 100644 --- a/src/modules/render-runtime.js +++ b/src/modules/render-runtime.js @@ -815,11 +815,21 @@ export const createRenderRuntimeController = ({ setStatus('Rendered', 'neutral') setRenderedStatus() } catch (error) { - setStatus('Error', 'error') + const errorMessage = error instanceof Error ? error.message : String(error) + const isCdnDependencyLoadFailure = errorMessage.startsWith( + 'Unable to load core runtime from CDN:', + ) + + setStatus( + isCdnDependencyLoadFailure + ? 'Error loading CDN dependency. Reload to retry.' + : 'Error', + 'error', + ) const target = getRenderTarget() clearTarget(target) const message = document.createElement('pre') - message.textContent = error instanceof Error ? error.message : String(error) + message.textContent = errorMessage message.style.color = '#ff9aa2' target.append(message) } finally {