Skip to content

Live preview and reload contract #13

Open

Description

@dvcolomban

Native CI reload/capture investigation, 2026-10-02

Two failures in CI at 01aa441 have separate boundaries:

  1. Attempt 1 completes both Firefox production and development cancellation cases, then times out navigating the extension panel after native background reload. Both base f03f4cc and current 01aa441 pass unchanged and identically instrumented Firefox 157.0 development commands locally. Current navigation starts 441 ms after the edit and takes 60 ms; base starts 638 ms after and takes 64 ms. Each observes one surviving window and expected extension URL. Local macOS/Node 24 sequential runs do not reproduce concurrent Linux/Node 26 CI; no cause or pre-existing classification is claimed.
  2. Attempt 2 stops earlier taking the final Chromium natural-idle screenshot, after its functional assertions. This also occurred at bc5baa5; intervening unchanged full CI passed. The capture target is the first of two fresh tabs, loaded and connected but hidden. Pinned Chromium source shows hidden capture uses the native painting/surface path. Both unchanged and foreground-only local comparisons pass, so the Linux timeout's exact cause remains unproven.

Commit 0da5f55 changes only the final screenshot boundary, after every idle/recovery assertion:

await connection.command('Page.bringToFront', {}, panel.sessionId);
await connection.command('Page.captureScreenshot', {}, panel.sessionId);

This removes an unnecessary hidden-capture dependency. There is no SDK change, retry, readiness controller, timeout extension or Firefox patch. The investigation records pinned sources, comparison outcomes and the attribution limit.

Definition of Ready

  • Exact failing boundaries and CI attempts are separated from feature assertions.
  • Actual native visibility and base/current Firefox inputs are inspected.
  • A one-call native capture adjustment has a concrete scope and adds no recovery mechanism.

Definition of Done for this slice

  • Maintained Chrome153 idle command passes: natural idle 30,010 ms, fresh provider/worker, restored10, one action saved11, independent key44, no worker attachment/page errors and a visible connected screenshot.
  • Owned browser exits, profile is removed and the existing extension artifact hash stays unchanged.
  • Affected strict Oxlint, TypeScript 7 and formatting pass.
  • Combined full CI passed at 0da5f55; this does not retrospectively identify the earlier capture cause.

Run VITE_COUNTER_STORAGE_KEY=example.persisted-counter pnpm --filter @devkit/example-webext test:persistence:chromium:idle. The original Firefox navigation failure remains an acceptance investigation, with no recovery behavior added. Broad change-matrix/lifecycle obligations are not closed by this slice.

Full CI at 0da5f55 passed every workspace and native production/development gate. All 26 retained native receipts were downloaded and independently checked against every exact referenced scenario: 176 native checks, 814 API paths, 407 linked paths and 34 gap groups. This establishes acceptance of the declaration/capture slices; it does not identify the earlier intermittent failures.

Firefox panel replacement observation, 2026-10-02

The diagnosis and correction, committed as ce546ce, addresses the failure in CI 36894953619. Firefox destroyed the old Marionette actor during a native DevTools panel HTML reload while the test was reading it. The read-only readiness poll now tolerates that exact document-replacement error within its existing deadline. Unrelated failures still propagate and effectful scripts are never retried.

Five executor-boundary regression/control cases, all 14 WebExtension example units and affected strict checks pass. Both actual concurrent development suites pass, with 15 Chromium and 11 Firefox scenario groups. The unchanged local baseline passed; the native race was recorded in CI and replayed deterministically at the driver boundary. No application reload policy changed. Full CI passed at ce546ce68e7df120a1adec6141768a8ca44a24d0, including the full workspace checks, production browser suites, concurrent development transitions and executed API-evidence gate.

Firefox sidebar control readiness, 2026-10-01

Correction 8b8af184412f00522d1e545c17801337a88aae2b fixes the test's missing readiness check when opening Firefox's native extension sidebar.

CI 36877758024 passed workspace validation and all production browser suites, then failed in the concurrent development step at toggleSidebar: its immediate browser-chrome lookup returned null and the test called .click() on that value. The lookup runs before the extension sidebar document mounts. Five fresh-profile local runs of the unchanged helper passed; the failure has not been reproduced locally or dismissed as a pre-existing runtime bug. Transient native rendering and a discarded lookup during a browser-context transition remain possible causes of that null.

The helper now uses Selenium's WebElementCondition to wait up to the existing 10-second UI allowance for the actual browser-owned shadow button. It then performs exactly one native WebDriver click. Optional lookup handles an absent sidebar root, shadow root or extension button; a persistently missing registration still fails with a named timeout. No application retry, synthetic click, extension policy or SDK API changed.

Affected strict Oxlint, TypeScript and formatting pass. Actual Firefox production passes all 51 checks, and the exact concurrent development command passes 11 Firefox and 15 Chromium scenarios. Full CI passed at 8b8af184412f00522d1e545c17801337a88aae2b, including workspace build/types/strict lint/tests, all production browser suites, both concurrent development suites and the combined executed API-evidence gate.

This corrects the browser test's precondition. It adds no worker-recovery or persistence guarantee and does not reopen the settled native reload contract.

Registration HTML and Firefox observation correction, 2026-10-01

Implementation bd0354e adds direct entrypoints/devtools.html edits to both native development suites and fixes the Firefox observation race exposed by the preceding registration-module test.

The earlier CI failure was reproduced locally. The test first observed counter 9 and the expected provider in a connected options document. A separate provider read returned null; the following snapshot had a different performance.timeOrigin and was still connecting. The installed Firefox 157 Marionette implementation returns null when a non-replayable executeScript command is interrupted by document replacement. The test had combined observations from different document lifetimes. Diagnosis and sequence diagram, captured failure with successful controls.

The correction waits for connected status, provider identity and counter in one native snapshot. The existing timeout remains unchanged; it does not repeat the action or add application recovery. CI now retains development JSON and screenshots after failures.

Direct registration HTML edit Chromium 153.0.8010.12 Firefox 157.0
Native update Fresh hidden registration document contains the changed HTML; existing tab remains Native build output contains the changed HTML; extension tab disappears
Explicit toolbox reopen Exactly one updated tab Exactly one updated tab
Panel and action Fresh document/caller, same provider and counter, one action effect observed by options Fresh document/caller, same provider and counter, one action effect observed by options

Chromium HTML receipt, Firefox HTML receipt, Firefox peer observations.

Validation: concurrent maintained pnpm --filter @devkit/example-webext test:dev passed 15 Chromium groups and 11 Firefox groups. Changed-file strict lint, affected-workspace TypeScript 7 and formatting passed. Full CI passed for this exact commit, including workspace build/types/lint/tests, both native browser suites, the concurrent development transitions and the executed-browser evidence gate. The preceding failed run remains recorded below.

This completes the direct registration-HTML check, not the full reload contract. Content/page updates, rapid edits, popup/DevTools/sidebar background/config transitions, mixed-backend view HMR and watched-production coverage remain open. Native toolbox reopening is explicit; no SDK lifecycle API or production behavior changed.

Direct DevTools registration module changes, 2026-10-01

Implementation 4681c9c adds a real registration-module edit to both maintained native development suites. The fixture changes src/devtools.ts from the panel name Devkit to Devkit updated. Production SDK and extension behavior are unchanged.

Native observation Chromium 153.0.8010.12 Firefox 157.0
After the imported registration module changes Hidden registration document reloads; existing tab keeps its old name Extension tab disappears from the toolbox
After explicit native toolbox close/reopen Exactly one Devkit updated tab Exactly one Devkit updated tab
Panel identity and state New document/caller; same provider and counter New document/caller; same provider and counter
One renderer action One increment observed by the options peer One increment observed by the options peer
flowchart LR
  Edit[Edit disposable registration module] --> Native[Native Vite/WXT update]
  Native --> Chrome[Chromium retains old tab title]
  Native --> Firefox[Firefox removes extension tab]
  Chrome --> Reopen[Explicit native toolbox reopen]
  Firefox --> Reopen
  Reopen --> Panel[One updated tab and fresh panel caller]
  Panel --> State[Retained background state and one action effect]
Loading

Chromium registration receipt records the registration document timestamps and tab titles. Firefox registration receipt records the visible tab removal/reopening and panel identities. Chromium uses the existing native debugging session to observe the hidden document because normal extension view enumeration omits it. Firefox needs only its existing native toolbox controls; no new internal browser-manager inspection is retained.

The final maintained concurrent test:dev command passed 14 Chromium groups with no captured page errors and 10 Firefox groups, including native browser shutdown. Scoped strict lint, TypeScript 7, formatting and whitespace checks passed. Reproduction and native behavior table. CI for this commit failed in the new Firefox registration sequence: the options peer reached the expected counter, then a separate WebDriver provider-identity read returned null. The local concurrent run passed, and the preceding commit passed CI. The later correction above captures the same failure and proves that the separate reads crossed document replacement. CI stopped the concurrent Chromium task when Firefox failed; that interrupted run is not a pass.

No automatic toolbox reopening, recovery controller, dependency patch or new SDK API was added. This closes the registration-module acceptance gap only. Direct registration-HTML edits, remaining content/page updates, rapid edits, popup/DevTools/sidebar background/config transitions, mixed-backend view HMR and watched production remain open. Existing native behavior decisions are preserved.

Concurrent native cache correction, 2026-09-29

#13 commit b7ad4c9 fixes a reproduced concurrent development failure using Vite's native cacheDir configuration. Full CI passed, including all workspace checks, both production browser suites, and the newly concurrent development suites.

Cause and comparison

The focused probe starts Chromium and Firefox together and performs initial startup plus nine configuration restarts. Both native WXT fixtures share installed dependencies through a node_modules symlink. With the default shared physical optimizer cache, Chromium's first restart received HTTP 504 for @devframes_json-render-ui_renderer.js; its actual panel stayed at Connecting with empty provider/catalog fields. Firefox completed ten starts. This occurs before the application module initializes, so RPC retry or recovery would not fix it.

Changing only the native Vite cache directory to each fixture root made all ten starts pass in both browsers, without captured failures. Failed request and DOM, Chromium control, Firefox control.

Maintained change

vite: ({ browser }) => ({
  cacheDir: resolve(import.meta.dirname, '.wxt', 'vite', browser),
}),

The root separates disposable fixtures; the browser directory also separates normal Chromium and Firefox commands in one checkout. WXT already ignores .wxt for source watching. Vite owns optimization and invalidation. No new dependency patch, reload manager, retry loop or timeout increase was added.

flowchart LR
  Chrome[Chromium development] --> ChromeCache[Its own native Vite cache]
  Firefox[Firefox development] --> FirefoxCache[Its own native Vite cache]
  ChromeCache --> ChromeUI[Renderer module loads]
  FirefoxCache --> FirefoxUI[Renderer module loads]
Loading

The maintained pnpm --filter @devkit/example-webext test:dev uses pnpm regex execution to run both complete development suites concurrently. Both passed locally, along with scoped strict lint, TS7, formatting and all 3 example tests. CI now runs the same concurrent command after installing both browsers. A local sandbox initially denied the test listener; rerunning the unchanged scoped command with local-network access passed.

Diagnosis and frozen reproduction. Earlier startup/restart incidents without network captures cannot conclusively be assigned this cause. This proves the captured optimized-module failure and its correction; it does not complete the remaining reload contract. #13 stays open for the listed background/config host transitions, direct registration changes, content/page updates, rapid edits and watched production.

Actual popup HTML reload verified, 2026-09-29

#13 commit 96ace4a adds the remaining toolbar-popup HTML reload check to both native development suites. It adds tests and documentation only. The extension application and installed patches are unchanged.

flowchart LR
  Edit[Edit copied panel.html] --> WXT[Native WXT HTML rebuild and page reload]
  WXT --> Popup[Actual open toolbar popup / fresh document and caller]
  Popup --> Provider[Same background provider and native counter state]
  Popup --> Click[One JSON-rendered action]
  Click --> Provider
  Provider --> Peer[Options observes one counter increment]
Loading
Assertion Chromium Firefox
Native popup remains open during HTML reload Pass Pass
Document timestamp and Port caller change Pass Pass
Provider identity and native state survive Pass Pass
One renderer button; one action effect reaches the peer Pass Pass, with native pointer input
Surface-local form resets on HTML reload Pass No additional claim
Final native close removes the popup Pass Pass

The checks use action.openPopup() and native popup enumeration, edit a disposable example source copy, and wait for the actual new document content. They do not close/reopen the popup to simulate continuity, substitute a browser tab, or introduce application test hooks. The previous module HMR, pending-action, DevTools/sidebar, background and configuration assertions still run.

Chromium receipt now records 12 development groups; Firefox receipt records 8. Both complete suites pass locally, along with scoped strict Oxlint, TypeScript 7 and formatting. Full CI passed, including all workspace checks and both production/development browser suites.

One initial local run, started alongside Firefox, timed out before reaching the new popup check: the first Chromium panel stayed Connecting for 30 seconds. A separate Chromium rerun passed. Five comparisons against an isolated archive of the preceding commit 8fd14f14 all passed Chromium, so the original failure is not reproduced or classified as pre-existing. One synchronized base Firefox run instead timed out after configuration restart; its targeted rerun passed. Browser cleanup completed. Concurrent startup/restart reliability remains an open investigation; these observations do not establish a cache cause or justify another recovery mechanism. CI runs the two suites sequentially.

Popup/DevTools/sidebar background/config transitions, direct DevTools registration changes, content/page updates, rapid edits and watched-production extension behavior remain open. Cross-provider JSON action routing still needs the separate owner choice. Keep #13 open.

Native DevTools/sidebar live updates verified, 2026-09-29

#13 commit 8fd14f1 extends both maintained development suites through module HMR and HTML reload in actual DevTools panels and browser sidebars. Full CI passed, including all workspace checks and both browsers' production/development suites.

Native interaction Chromium and Firefox evidence
Edit panel TypeScript while host stays open Same document/provider/state, fresh caller, one JSON action effect observed by options
Edit panel HTML while host stays open New document/caller, same provider/state, one JSON action effect observed by options
Close native host Native panel/sidebar disappears; the peer remains usable
Local form Chromium retains it on module HMR and resets it on HTML reload

WXT correction and its scope

The Firefox run exposed a native WXT problem. Its HTML rebuild group includes both panel.html and devtools.html. WXT reloaded both even when only panel output changed. Reloading the unchanged registration document removed Firefox's Devkit tab while leaving the toolbox open. It did not return after 30 seconds. Calling native reloadPage('panel.html') alone retained the tab and replaced its document correctly.

The exact-version pnpm patch now compares emitted HTML before and after a successful native rebuild:

const previousHtml = await snapshotHtml(groups);
await rebuild(groups);
for (const entry of htmlEntries(groups)) {
  const nextHtml = await readHtml(entry);
  const previous = previousHtml.get(entry);
  if (previous && nextHtml && previous.equals(nextHtml)) continue;
  server.reloadPage(entry.outputFilename);
}

This is illustrative pseudocode. The implementation conservatively reloads when either snapshot cannot be read. It compares output, so a shared transform that actually changes devtools.html still reloads that document. Native errors and the native reload API keep their behavior. Vite remains unpatched; there is no SDK reload/recovery controller and no new public API. No WXT upstream PR was opened.

flowchart LR
  Edit[Panel HTML edit] --> WXT[Native WXT rebuild group]
  WXT --> Compare[Compare emitted HTML]
  Compare -->|panel changed| Reload[Native panel page reload]
  Compare -->|registration unchanged| Keep[Keep DevTools registration document]
  Reload --> UI[Fresh panel caller / retained provider state]
  Keep --> Tab[Devkit tab remains available]
Loading

The regression test uses real WXT builds and spies on its actual reloadPage boundary. It verifies panel-only change, changed registration output through a shared HTML transform, and missing previous output. The native Firefox observer also uses WebDriver's default page-load behavior, which fixes sidebar clicks that previously opened the sidebar and then failed inside Firefox's navigation observer.

Validation and remaining gates

Direct edits to the DevTools registration document retain the native lifetime and can still require reopening the toolbox. Popup-specific HTML reload, popup/DevTools/sidebar background/config transitions, content/page updates, rapid edits and watched-production extension behavior remain open. The separate JSON-renderer routing choice still needs the owner's answer. Keep #13 open.

Actual toolbar popup HMR verified, 2026-09-29

#13 commit 2190738 extends the maintained native development suites to actual toolbar popups in Chromium and Firefox. The current application already handles the update through Vite/WXT. This slice adds tests, documentation and recorded receipts; no production implementation, SDK lifecycle API or dependency patch changes.

flowchart LR
  Source[Edit copied panel.ts] --> Vite[Native Vite / WXT update]
  Vite --> Popup[Same actual popup document]
  Popup --> Dispose[Dispose old Port / renderer / listeners]
  Dispose --> Mount[New caller / one renderer]
  Mount --> Provider[Same live background provider and native state]
  Provider --> Peer[Options page observes one action effect]
Loading
Interaction Chromium Firefox
Open native toolbar popup, then edit panel module Actual popup remains open Actual popup remains open
Compare document, provider and caller Same document/provider, fresh caller Same document/provider, fresh caller
Click current JSON-rendered button One button; counter 1→2 in popup and peer One button; native pointer click moves counter 4→5 in popup and peer
Close popup Native view registry becomes empty Native view registry becomes empty; peer remains connected
Surface-local input Form survives module update No additional form-retention claim
Old pending work Completes once; replacement result is unchanged Covered separately by the existing native experiments, not this popup check

The tests identify the popup through action.openPopup() and extension.getViews({ type: 'popup' }). They mutate only a disposable source copy, wait for the actual updated module in both views, and inspect the real DOM. Firefox's pointer input uses measured popup geometry and asserts hover on the popup button, avoiding a click on the options document underneath. No application test hooks or substitute ordinary-tab popup are used.

The existing module, HTML, background, configuration-restart and native-shutdown assertions continue to run. Scoped strict lint, TypeScript 7 and formatting pass. Both complete native development suites pass locally. Chromium receipt, Firefox receipt. Full CI passed, including every workspace gate and both browsers' production/development suites.

This verifies module HMR for actual popups. Popup-specific HTML/background/config transitions, DevTools/sidebar development transitions, content/page updates, rapid changes and watched-production extension behavior remain open. The separate JSON action-routing choice remains pending. Keep #13 open.

Watched configuration restart verified, 2026-09-29

51b0126 moves the shared application manifest factory into the actual watched wxt.config.ts. A native watcher probe showed edits to the imported manifest.ts stayed at version 0.0.1 until the main config changed. Production imports the same factory; this fixes the gap without another watcher or dependency patch.

Maintained tests now edit 0.0.1 to 0.0.2 and prove old-browser exit, replacement manifest/provider, reset ephemeral state, working native JSON actions, and final native shutdown in both Chromium and Firefox. Chromium also checks Developer mode survives restart. The test observes the platform's native preference store and never writes protected values.

Chromium receipt, Firefox receipt, and full CI passed. Scoped lint, strict TS7, both production builds and both bundle tests passed locally. CI also ran all workspace gates and both production/development browser suites.

This satisfies the maintained config-restart proof, not the entire reload contract. Content/page updates, rapid changes, popup/DevTools/sidebar development lifetimes and watched-production extension behavior remain open.


Maintained native development, 2026-09-29

01a477f adds runnable dev and dev:firefox commands to the maintained extension example. WXT owns Vite HMR, the browser and native extension reloads. Production still uses its existing Vite build, sharing the application manifest, HTML and implementations with development. Commands and state boundaries.

The exact WXT/browser declaration and runner-ownership corrections are now root pnpm patches, with removal gates. This supersedes earlier candidate-only installation status below. Strict TS7 checks still use skipLibCheck: false; no SDK watcher, custom browser runner or state-recovery engine was added.

Live maintained tests passed on Chromium 153.0.8010.12 and Firefox 156.0.1. They verify actual native JSON rendering, retained document/provider/state during panel module replacement, full HTML reload with retained provider state, fresh provider/ephemeral state after background reload, and native browser shutdown. Chromium additionally proves an old pending command cannot overwrite replacement UI and records zero page errors. Firefox makes no global page-error claim. Tests edit temporary example copies and do not change the working tree or the normal development profile.

Chromium requires normal one-time Developer mode setup in its dedicated persistent profile. Background reload closes extension pages; reopen the options page for a fresh connection. Normal development uses native browser settings. Linux CI alone passes --no-sandbox to its disposable automated Chromium, matching the existing Playwright test default after Chrome stderr confirmed that the runner had no usable sandbox. The two live development tests now run in full CI, passed on e44cdbc, alongside the existing production suites. Local scoped build, strict lint/types/format and both bundle tests passed.

203e35d excludes the development-only WXT patches from the isolated packed-runtime consumer. 0ed2188 reports native launcher stderr, and e44cdbc applies the confirmed Linux CI launch correction. Full CI includes both existing production suites and both new native development suites.

This remains a partial completion of the broad contract. Content/page updates, repeated rapid changes, toolbar/DevTools/side-panel lifetimes and watched-production extension behavior still need implementation and live coverage. The newer checkpoint above promotes config-restart coverage into maintained tests. The renderer action-routing choice belongs to Renderer and surface contract and is unchanged.

Part of Design a portable contribution SDK and WebExtension runtime.

Extension follow-up, 2026-09-29

ce4cab5 implements native Vite panel disposal for the maintained extension source. A fresh standalone WXT fixture proves actual Chromium JSON-renderer HMR, retained worker state, both old Ports closing, exactly one routed-button listener and successful post-update actions. Full CI passed, including 24 Chromium and 18 Firefox production scenario groups. Reproduction and limits.

The narrow WXT runner-ownership patch also passes the native Firefox config-change path in 937d58c, including actual new manifest adoption, a new background generation and complete browser cleanup. Its dependency corrections remain experimental pnpm patches in standalone fixtures. WXT is not installed in the maintained workspace.

406f996 adds passing Firefox renderer HMR and Chromium HTML/background transitions. The reproduced background failure came from Developer mode being off in the native fresh profile. Enabling the normal setting makes native reload pass: old contexts close and a fresh panel uses a new provider incarnation with reset ephemeral state, without action replay. Full CI passed. A native dedicated-profile check now preserves normal Developer mode setup across browser restart and a subsequent background update after Chrome saves the setting. No protected preference hashes or policy controls are changed. Maintained development-command integration, Firefox background changes, content/page updates, all surface lifetimes and watched-production completion remain open. These slices do not satisfy the complete DoD below.

Current implementation, 2026-09-24

ae2b147 provides maintained independent build:watch/preview scripts and the concurrent pnpm-regex dev:production command, attached to both real native backends. Completed generations remain available after syntax, generateBundle and writeBundle failures, with HTTP build status and terminal diagnostics. The native provider and its state survive asset rebuilds. Follow-up 20248b4 fixes publication ownership before retained-status reads. The maintained example has 24 passing tests and passes strict package build/type/lint/format checks. Full project CI passes on that final head.

See the implementation diagram and remaining gates and runnable commands/API-to-test matrix. Browser automatic refresh/HMR, JSON-rendered diagnostics, remote SDK dispatch, extension reload and backend-state restoration remain open. A source watcher and an HTTP status endpoint do not complete those cells.

Vite draft 23574 addresses an exceptional restart cleanup defect separately. It remains a draft for the owner to take over. The workspace still uses unpatched Vite 8.3.0; normal build/reload ownership remains with Vite and native hosts.

Upstream integration boundary, 2026-09-27

Vite owns dev HMR, build watching and server restarts. Existing adapters bind their registrations to native host lifetimes. The maintained completed-build publication/status helper fills the accepted last-successful-assets requirement only; it is not a generic checkpoint/rollback/restart engine. Vite remains unpatched, and draft 23574 is an independent bug fix.

See the implementation and map review. This narrows implementation mechanisms without dropping the accepted feature or real-host coverage requirements.

Question

What is the exact live-preview and reload contract for contribution authors and host implementers across Vite development and watched production builds? Specify what updates, restarts, or reloads, which state survives, and how clients recover when a generation fails.

Context and current behavior

The requested production workflow is a watched build served through a live preview backend. It is not a requirement to download arbitrary new executable extension code into store installations. Production artifacts must also work without development-only servers or permissions when the live workflow is not enabled.

Current code supplies pieces, but not the complete workflow. In the inspected devframe checkout, packages/vite/src/hub.ts handles development lifecycle and static build output; packages/hub/src/node/initiate.ts serves compiled renderer assets; packages/hub/src/client/renderers.ts caches renderer imports and registrations. In devtools, packages/core/src/node/build-static.ts produces static output, while standalone hosting provides live RPC over compiled assets. Reading a rebuilt file with no-store does not replace an already imported browser module or dispose its old subscriptions.

Inspected revisions: devframe a3f967755af99a0a0d8dd82b7b5d4f3dd4aec0bc; devtools 121656dfc5866b2f8de5baa8e0b6166ec28599c8. Vite HMR and plugin lifecycle provide the source of truth for Vite hooks. Published-package parity is supplied by the upstream audit.

Requirements and scope

  • Support both development and build-watch/live-preview as documented runnable examples.
  • Apply the agreed layer-specific policy: update data/UI where supported; restart a backend/worker or reload a page when required; reconnect and restore explicitly retained state.
  • A preview context must expose the raw primitives it actually owns rather than fabricate a ViteDevServer.
  • Every SDK-owned lifecycle hook must have observable ordering, cleanup semantics, a working example, and automated assertions.
  • Preserve provider and target identity rules from their contracts. Navigation and extension reload may invalidate document-specific handles even when retained settings survive.
  • Define production build behavior independently from the optional development orchestration process.

Options and recommendation

Compare immediate per-file reload, debounced coherent build generations, and replacing the whole running session on every change. Recommend explicit successful build generations with update classification and ownership-based disposal. Determine whether retaining the last working generation is feasible for each affected layer before committing to it.

A complete in-memory state-preserving replacement of arbitrary code is not required. The contract must state each preservation boundary so contribution authors can choose state lifetime deliberately.

Proposed API or experiment

The maintained example listens to native Vite build-watch events. This sketch shows only its accepted completed-assets publication policy; it is not a backend replacement controller:

const watcher = await build({ build: { watch: {} } });
if (!('on' in watcher)) throw new Error('Expected Vite build watcher');
watcher.on('event', (event) => {
  if (event.code === 'ERROR') {
    publication.fail(event.error.message);
    return;
  }
  if (event.code === 'BUNDLE_END') publication.complete();
});
// Actual host shutdown awaits watcher.close().

publication is the existing example-local completed-build helper, not a new SDK export; see examples/vite-hosts/src/production-watch.ts for error handling and owned cleanup. Native backend lifecycle is separate from asset publication. Prove behavior if a provider disappears mid-update, a popup opens during transition, or the inspected document navigates before restoration. Raw local contexts must be rebound to the active generation rather than kept as stale handles.

Scenarios and acceptance criteria

Produce a matrix for changes to JSON view/state, renderer code/styles, shared contribution logic, server adapter code, background code, isolated content scripts, page-world modules, and manifests/permissions. For each development/preview host cell, specify update mechanism, affected surfaces, disposal sequence, retained state, and unavoidable user-visible reload.

Automate at least these examples:

  1. A JSON view update appears without remounting unrelated views; its previous event subscription is not duplicated.
  2. A renderer replacement mounts the new code and disposes the old renderer exactly once.
  3. A backend restart reconnects the UI and restores declared retained settings; ephemeral state follows its specified policy.
  4. A worker suspension or extension reload clears stale ports and document handles before accepting new actions.
  5. A failed build, failed activation, and rapid successive builds produce explicit diagnostics and a deterministically identified active generation.
  6. A production package with live orchestration disabled has no accidental dependency on development URLs or a watcher.

Dependencies

Coordinate the resulting hook identifiers and tests with Examples and API coverage contract. The final portability proof consumes this contract.

Definition of Ready

  • Identify the actual Vite/native hook and missing observable behavior for each additional reload mechanism.

  • Blocking tickets have supplied the selected mechanisms and state/lifecycle contracts.

  • Each changed runtime part has an identified owner and update notification path.

  • Development and watched-preview fixtures can be started with documented package-scoped commands.

  • Failure injections and observable generation markers are specified without adding test-only production API parameters. The maintained watcher/preview slice covers real syntax and output-hook failures; renderer/extension-specific injections remain part of their future implementation.

Definition of Done

  • Mounted browser/renderer and extension reload tests prove the documented boundaries without a second watcher or whole-host recovery controller.

  • The change/host/mode matrix specifies update, reload, disposal, and retention for every supported cell.

  • Preview attachment and generation notification protocols are documented with sequence/pseudocode.

  • Hook ordering, activation failure, stale generation, and reconnect semantics are decided.

  • Example and automated-test requirements cover every public reload hook and relevant failure mode.

  • The user has confirmed the expected authoring and reload experience.

  • Any unproved platform-dependent behavior is a blocking experiment rather than a promised feature.

Resolution record

Record the agreed lifecycle and change matrix, supporting experiments, rejected alternatives, and example/test mappings in the resolution comment. Link the answer from the map after closing the issue.

Activity

  1. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Upstream reuse audit resolution found that live hub serving reads renderer files per request with no-store, while the client registry caches imported modules. New bytes alone do not replace an already mounted renderer. The renderer package's watch script runs tsdown; its full build also runs CSS generation and Vite builds.

    Acceptance: observe a renderer edit through watched production build, changed served bytes and actual mounted-view update. Record module invalidation/remount behavior and restoration separately from development HMR. Specify reload/recovery for UI, renderer, content, worker and server changes. Static snapshots remain a separate mode. Serving code, registry, published build scripts.

  2. dvcolomban commented on Sep 22, 2026

    @dvcolomban
    CollaboratorAuthor

    Research handoff and acceptance gates

    Sources: Toolchain and reload audit and Browser capability audit.

    Accountable owner: dvcol, until reassigned. These observations inform this open contract; they do not resolve it.

    Measured WXT page updates retained document time origin in both stable browsers. Custom Vite retained it in Chromium and replaced the document in Firefox. WXT Chromium content edits caused page replacement; later successful action does not prove duplicate-listener cleanup. A new Firefox background generation was observed during the fallback sequence, but the precise triggering edit remains unresolved. External-runner timeouts are not proof of WXT defects.

    Two raw Firefox results are explicitly INVALID: explicit reload reused an earlier observation, and manifest adoption matched older history. Manifest adoption and complete reconnection remain unverified. The Vite watch/preview probe required explicit page reload; it supplies no automatic compiled-output notification protocol.

    Acceptance protocol:

    const observationCursor = observations.length;
    const expectedGeneration = createUniqueGeneration();
    await applyChange(expectedGeneration);
    const current = await observeAfter(observationCursor, expectedGeneration);
    await assertManifestVersionAndBackendActivation(current);
    await clickThroughLiveSurface();
    await collectOutcomesForUniqueCommandIds();

    Use unique run/candidate/browser/generation identities, settled restorations, an append-only cursor per mutation, atomic command IDs and a bounded duplicate-response window. Observe backend activation and content reconnection independently. Decide retained state per affected layer, then implement explicit watched-preview success/failure notifications and last-good-output behavior. Packaged production code updates remain distinct from development HMR.

  3. dvcolomban commented on Sep 24, 2026

    @dvcolomban
    CollaboratorAuthor

    Failed-restart candidate cleanup reproduced

    Source commit: 8a93ac4. Exact programs, outputs and proposed patch.

    Actual Vite 8.3.0 with each native host prepares a replacement before waiting for the old server's restart cleanup. When that cleanup rejects, the replacement never listens but its watcher remains active. A real file write is observed after rejection; provider readiness remains pending. This is an active-resource leak, not proof of two activated providers.

    The disposable exact-version patch changes the ownership failure path:

    try {
      await server._closeServer('restart');
    } catch (error) {
      try {
        await newServer.close();
      } catch (cleanupError) {
        throw new AggregateError([error, cleanupError], 'Restart and candidate cleanup failed');
      }
      throw error;
    }

    Before/after probes pass for both Devframe and DevTools. Before manual cleanup, patched candidates have a closed watcher, zero watched directories and rejected readiness. A separate double-failure probe preserves both errors. Programs, result files, package source hashes and evidence SHA256s are committed. Script syntax and evidence hashes were checked again before committing.

    The patch was applied only to a disposable package copy. The workspace Vite dependency is unchanged. It introduces no supervisor, automatic restart or operation replay. Public isolated plugin hooks cannot close a candidate they do not receive or receive notification about.

    Remaining gates

    • Owner choice: carry this exact-version dependency fix temporarily, or leave the failed-restart path unsupported until upstream fixes it.
    • If adopted, add the maintained regression and validate the affected host graph after frozen installation.
    • Candidate creation/listen failure, hanging cleanup, browser HMR, preview asset updates and build-watch retention remain separate conformance obligations.

    Issue remains open. No upstream PR has been opened.

  4. dvcolomban commented on Sep 24, 2026

    @dvcolomban
    CollaboratorAuthor

    bee17f7 adds diagrams and clarifies scope after the owner's question about patching Vite.

    The patch is not a prerequisite for ordinary HMR or build watching. Vite owns watchers, transformations, HMR and config restarts. The example's private ProviderLifetime waits for HTTP listening before contribution activation and awaits contribution disposal in closeServer. This enforces the previously accepted cleanup-before-replacement rule; it implements no watcher or restart controller.

    A disposer failure rejects the close hook, exposing Vite's retained candidate watcher. The proposed patch closes the candidate and preserves failure. It neither recovers the old backend nor retries. Keeping stock Vite does not block ordinary development/preview work; the exceptional cleanup-conformance gap remains open until addressed. Patch adoption is still unapproved and no workspace Vite patch is installed.

    Maintained production build:watch/preview/dev:production scripts, watched-output retention and browser refresh remain unimplemented. The separate preview probe establishes backend attachment only. Issue remains open.

  5. dvcolomban commented on Sep 24, 2026

    @dvcolomban
    CollaboratorAuthor

    The owner asked whether a thrown cleanup error would terminate Node and release the watcher, or whether watcher error handling could catch it. f1a2ee4 adds an executed native watched-config reproduction for both actual hosts.

    The probe writes the real config file. It does not call server.restart directly, catch restart in application code, or install process exception handlers. Vite catches and logs the cleanup failure itself. The process continues, and the prepared replacement watcher observes a subsequent client-file edit. Filesystem watcher error listeners receive zero events. Explicit final cleanup closes the watcher and rejects readiness; the probe exits 0.

    Config write → Vite restart → old contribution disposal rejects
      → Vite catches/logs → Node continues → candidate watcher still active
    

    This distinction is trigger-specific: an uncaught programmatic await or another CLI path can have different handling. Actual process termination would release local watchers. A generic SDK cannot assume it owns the embedding process or silently terminate it. Watcher errors report filesystem-watch failures, while this watcher is healthy and the failure came from contribution cleanup.

    Exact source/output files and SHA256s are retained in docs/probes/vite-failed-restart. Syntax, evidence hashes and scoped core lint/types pass. The proposed Vite patch remains unapplied. Normal Vite/preview implementation can continue with this exceptional cleanup case open.

  6. 39 remaining items

  7. dvcolomban commented on Sep 29, 2026

    @dvcolomban
    CollaboratorAuthor

    Native DevTools/sidebar live updates verified, 2026-09-29

    #13 commit 8fd14f1 extends both maintained development suites through module HMR and HTML reload in actual DevTools panels and browser sidebars. Full CI passed, including all workspace checks and both browsers' production/development suites.

    Native interaction Chromium and Firefox evidence
    Edit panel TypeScript while host stays open Same document/provider/state, fresh caller, one JSON action effect observed by options
    Edit panel HTML while host stays open New document/caller, same provider/state, one JSON action effect observed by options
    Close native host Native panel/sidebar disappears; the peer remains usable
    Local form Chromium retains it on module HMR and resets it on HTML reload

    WXT correction and its scope

    The Firefox run exposed a native WXT problem. Its HTML rebuild group includes both panel.html and devtools.html. WXT reloaded both even when only panel output changed. Reloading the unchanged registration document removed Firefox's Devkit tab while leaving the toolbox open. It did not return after 30 seconds. Calling native reloadPage('panel.html') alone retained the tab and replaced its document correctly.

    The exact-version pnpm patch now compares emitted HTML before and after a successful native rebuild:

    const previousHtml = await snapshotHtml(groups);
    await rebuild(groups);
    for (const entry of htmlEntries(groups)) {
      const nextHtml = await readHtml(entry);
      const previous = previousHtml.get(entry);
      if (previous && nextHtml && previous.equals(nextHtml)) continue;
      server.reloadPage(entry.outputFilename);
    }

    This is illustrative pseudocode. The implementation conservatively reloads when either snapshot cannot be read. It compares output, so a shared transform that actually changes devtools.html still reloads that document. Native errors and the native reload API keep their behavior. Vite remains unpatched; there is no SDK reload/recovery controller and no new public API. No WXT upstream PR was opened.

    flowchart LR
      Edit[Panel HTML edit] --> WXT[Native WXT rebuild group]
      WXT --> Compare[Compare emitted HTML]
      Compare -->|panel changed| Reload[Native panel page reload]
      Compare -->|registration unchanged| Keep[Keep DevTools registration document]
      Reload --> UI[Fresh panel caller / retained provider state]
      Keep --> Tab[Devkit tab remains available]
    
    Loading

    The regression test uses real WXT builds and spies on its actual reloadPage boundary. It verifies panel-only change, changed registration output through a shared HTML transform, and missing previous output. The native Firefox observer also uses WebDriver's default page-load behavior, which fixes sidebar clicks that previously opened the sidebar and then failed inside Firefox's navigation observer.

    Validation and remaining gates

    Direct edits to the DevTools registration document retain the native lifetime and can still require reopening the toolbox. Popup-specific HTML reload, popup/DevTools/sidebar background/config transitions, content/page updates, rapid edits and watched-production extension behavior remain open. The separate JSON-renderer routing choice still needs the owner's answer. Keep #13 open.

  8. dvcolomban commented on Sep 29, 2026

    @dvcolomban
    CollaboratorAuthor

    Actual popup HTML reload verified, 2026-09-29

    #13 commit 96ace4a adds the remaining toolbar-popup HTML reload check to both native development suites. It adds tests and documentation only. The extension application and installed patches are unchanged.

    flowchart LR
      Edit[Edit copied panel.html] --> WXT[Native WXT HTML rebuild and page reload]
      WXT --> Popup[Actual open toolbar popup / fresh document and caller]
      Popup --> Provider[Same background provider and native counter state]
      Popup --> Click[One JSON-rendered action]
      Click --> Provider
      Provider --> Peer[Options observes one counter increment]
    
    Loading
    Assertion Chromium Firefox
    Native popup remains open during HTML reload Pass Pass
    Document timestamp and Port caller change Pass Pass
    Provider identity and native state survive Pass Pass
    One renderer button; one action effect reaches the peer Pass Pass, with native pointer input
    Surface-local form resets on HTML reload Pass No additional claim
    Final native close removes the popup Pass Pass

    The checks use action.openPopup() and native popup enumeration, edit a disposable example source copy, and wait for the actual new document content. They do not close/reopen the popup to simulate continuity, substitute a browser tab, or introduce application test hooks. The previous module HMR, pending-action, DevTools/sidebar, background and configuration assertions still run.

    Chromium receipt now records 12 development groups; Firefox receipt records 8. Both complete suites pass locally, along with scoped strict Oxlint, TypeScript 7 and formatting. Full CI passed, including all workspace checks and both production/development browser suites.

    One initial local run, started alongside Firefox, timed out before reaching the new popup check: the first Chromium panel stayed Connecting for 30 seconds. A separate Chromium rerun passed. Five comparisons against an isolated archive of the preceding commit 8fd14f14 all passed Chromium, so the original failure is not reproduced or classified as pre-existing. One synchronized base Firefox run instead timed out after configuration restart; its targeted rerun passed. Browser cleanup completed. Concurrent startup/restart reliability remains an open investigation; these observations do not establish a cache cause or justify another recovery mechanism. CI runs the two suites sequentially.

    Popup/DevTools/sidebar background/config transitions, direct DevTools registration changes, content/page updates, rapid edits and watched-production extension behavior remain open. Cross-provider JSON action routing still needs the separate owner choice. Keep #13 open.

  9. dvcolomban commented on Sep 29, 2026

    @dvcolomban
    CollaboratorAuthor

    Concurrent native cache correction, 2026-09-29

    #13 commit b7ad4c9 fixes a reproduced concurrent development failure using Vite's native cacheDir configuration. Full CI passed, including all workspace checks, both production browser suites, and the newly concurrent development suites.

    Cause and comparison

    The focused probe starts Chromium and Firefox together and performs initial startup plus nine configuration restarts. Both native WXT fixtures share installed dependencies through a node_modules symlink. With the default shared physical optimizer cache, Chromium's first restart received HTTP 504 for @devframes_json-render-ui_renderer.js; its actual panel stayed at Connecting with empty provider/catalog fields. Firefox completed ten starts. This occurs before the application module initializes, so RPC retry or recovery would not fix it.

    Changing only the native Vite cache directory to each fixture root made all ten starts pass in both browsers, without captured failures. Failed request and DOM, Chromium control, Firefox control.

    Maintained change

    vite: ({ browser }) => ({
      cacheDir: resolve(import.meta.dirname, '.wxt', 'vite', browser),
    }),

    The root separates disposable fixtures; the browser directory also separates normal Chromium and Firefox commands in one checkout. WXT already ignores .wxt for source watching. Vite owns optimization and invalidation. No new dependency patch, reload manager, retry loop or timeout increase was added.

    flowchart LR
      Chrome[Chromium development] --> ChromeCache[Its own native Vite cache]
      Firefox[Firefox development] --> FirefoxCache[Its own native Vite cache]
      ChromeCache --> ChromeUI[Renderer module loads]
      FirefoxCache --> FirefoxUI[Renderer module loads]
    
    Loading

    The maintained pnpm --filter @devkit/example-webext test:dev uses pnpm regex execution to run both complete development suites concurrently. Both passed locally, along with scoped strict lint, TS7, formatting and all 3 example tests. CI now runs the same concurrent command after installing both browsers. A local sandbox initially denied the test listener; rerunning the unchanged scoped command with local-network access passed.

    Diagnosis and frozen reproduction. Earlier startup/restart incidents without network captures cannot conclusively be assigned this cause. This proves the captured optimized-module failure and its correction; it does not complete the remaining reload contract. #13 stays open for the listed background/config host transitions, direct registration changes, content/page updates, rapid edits and watched production.

  10. dvcolomban commented on Oct 1, 2026

    @dvcolomban
    CollaboratorAuthor

    Fixed the Firefox DevTools reload observation in commit ce546ce, following the failed full CI run. That run passed the workspace gates and debugger/browser checks before failing in concurrent native development.

    The failure occurred in the existing 30-second HTML-reload poll. WXT had reloaded panel.html; Firefox destroyed the old document's Marionette actor while the test was reading its unique marker and Connected status. panelScript represented that native rejection as an assertion failure, which ended Selenium's poll immediately:

    read old DevTools document -> native HTML reload replaces document
      -> old actor query rejects with the recorded AbortError
    before: poll fails immediately
    now: only that read reports not-ready; same bounded poll observes the successor
      -> unique marker + Connected -> unchanged identity/state/action-count assertions
    

    The code change recognizes only the exact recorded actor-destruction assertion in the read-only reload predicate. Shared panelScript, clicks and other effectful operations are unchanged. Unrelated script/driver errors still reject; a missing replacement still times out. No production code, dependency patch, browser recovery loop or increased native-test deadline was added.

    Five regression cases replay the recorded response at the real Selenium executor boundary. Before the fix, two cases fail and three controls pass. After it, all five pass, including timeout, unrelated errors, driver failure and a single failed effectful execution without retry. The unchanged concurrent native suite passed locally before editing; the original browser timing race was not independently reproduced locally, so this is deterministic reproduction of its recorded driver response, not a claim of repeated browser reproduction.

    The final actual concurrent development command passes: Chromium 153.0.8010.12 completes 15 scenarios with no captured page errors; Firefox 157.0 completes 11 scenarios and confirms native stop closed its browser. Firefox's global page-error capture remains unavailable. The retained native snapshots match the final generated files. Firefox's peer observes counters 7, 8, 9 and 10 exactly; reloaded documents have fresh callers with retained provider/state. All 14 example unit tests, strict lint, TypeScript and changed-file formatting pass.

    Full CI for the correction and native-auth follow-up is running. The broader reload contract remains open; this fixes its test's observation race without changing native reload behavior.

    CI reconciliation, 2026-10-02: Full CI passed at ce546ce68e7df120a1adec6141768a8ca44a24d0, including the combined changes, all production browser suites, concurrent development transitions and the executed API-evidence gate. The Firefox panel observation correction resolves the later actor-destruction failure; the earlier failed run remains historical evidence.

  11. dvcolomban commented on Oct 3, 2026

    @dvcolomban
    CollaboratorAuthor

    Native renderer source/style updates and restored selection, 2026-10-03

    74ff82d adds maintained actual-file-edit tests on both native backends and fixes a small example startup bug.

    Firefox restores the renderer selector to custom after Vite page reload. The example previously registered custom only after a change event, leaving the reference renderer behind a custom selection. A pre-fix native run reproduced the missing updated custom mount. Startup now uses the same existing native registration function as the change handler. Chromium's native reset and Firefox's native restoration remain unchanged.

    Stylesheet edit → Vite CSS HMR → same document + custom mount + counter 1
    One action →2
    Renderer module edit, no accepting boundary → Vite page reload
    Fresh native client → same backend/provider counter 2 + actual selected renderer
    Updated custom mount → one action →3
    

    Definition of Ready: native Vite already owns both update mechanisms; the existing example uses real native clients/renderer registration and can be copied for source edits.

    Definition of Done for this slice:

    • Twelve exact native checks pass across Chromium 153/Firefox 157 and Devframe/DevTools. CSS retains the exact mount/document; module reload creates a fresh document, preserves provider/state and does not replay actions.
    • Existing Chromium 18/Firefox 16 renderer lifecycle checks and8 affected unit cases still pass; build, strict lint, TS7 and formatting pass.
    • New commands, CI steps, generated/retained receipts and matrix references are maintained.
    • Full CI at e05dd25 passes and both new Linux reload receipts are independently verified. The preceding run stopped earlier at the separately corrected declaration-test deadline.

    Reproduction, ownership diagram and retained receipts. This is development CSS HMR and native module page reload. Self-accepting replacement, invalid source recovery, rapid edits, injected rendering and watched-production browser refresh remain separate cells. Firefox WebDriver Classic has no global page-error capture. No SDK reload controller or backend-state policy was added.

    The full CI at e05dd25 passed. Its retained artifacts independently verify all 49 browser references, 38 distinct payloads and 414 unique native checks, plus the original 140 core, 94 runtime and 11 Devframe cases. This validates the changes described in this comment; the subsequent invalid-renderer and cleanup-callback regressions have their own pending CI run.

  12. dvcolomban commented on Oct 3, 2026

    @dvcolomban
    CollaboratorAuthor

    Native invalid-renderer recovery, 2026-10-03

    eb6015c extends the maintained reload fixture and both browser commands. A genuine renderer syntax error creates a new failed document, shows Vite's Oxc error overlay, leaves zero view mounts and disables the renderer selector. The same native backend remains live. Its counter advances from 2 to 3 while frontend startup is broken.

    A valid source edit triggers Vite's existing reload. The fresh client reads 3, removes the overlay and mounts the updated custom renderer once. One rendered action reaches 4. The provider identity stays unchanged. Chromium resets its selector to reference; Firefox restores custom, as recorded by the prior native reload proof.

    await fixture.invalidateRenderer(); // Real file write, native Vite error overlay.
    await fixture.example.host.provider.invoke({
      action: increaseCounterAction,
      input: { amount: 1 },
    }); // Backend remains live while the page cannot initialize.
    await fixture.updateRenderer(); // Real valid edit, native reload and current-state read.

    The interaction diagram, exact commands and limits link both raw receipts. The change adds no production SDK, watcher, retry or recovery controller.

    Definition of Ready:

    • Genuine Devframe/DevTools contexts, maintained copied page and native Vite are available.
    • Native update/permission/state decisions remain unchanged.

    Definition of Done for this slice:

    • Chromium 153.0.8010.12 and Firefox 157.0/geckodriver 0.37.1 pass both host modes, ten exact checks per browser.
    • Receipts preserve the parse-error overlay and failed/recovered documents; Chromium also retains the actual HTTP 500.
    • Eight affected unit cases, TS7, strict Oxlint, Oxfmt and browser conformance references pass locally.
    • Full CI for the latest commits passes and retained recovery observations are independently checked.

    Firefox WebDriver Classic does not capture global page errors or HTTP response status. Self-accepting module replacement, rapid edits, injected rendering, backend replacement and watched-production browser refresh remain separate acceptance cells.

    Downloaded CI artifacts independently pass all 49 browser references across 38 distinct receipt files and 422 referenced native checks. The retained core report has 142 passing cases; runtime has 94 and Devframe has 11. The full report accounts for 814 paths, 460 with linked execution evidence and 38 partial groups. Both native renderer recovery receipts preserve zero failed-document mounts, a live backend advancing 2 to 3, retained provider identity and a recovered UI reaching 4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

wayfinder:grillingDecision requiring discussion with the project owner

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions