Skip to content

fix(devloop): ask the dev server whether a frontend change compiles - #25466

Open
tltv wants to merge 2 commits into
mainfrom
fix/devloop-vite-compile-err-passes-as-stable
Open

fix(devloop): ask the dev server whether a frontend change compiles#25466
tltv wants to merge 2 commits into
mainfrom
fix/devloop-vite-compile-err-passes-as-stable

Conversation

@tltv

@tltv tltv commented Sep 3, 2026

Copy link
Copy Markdown
Member

Vite compiles a module when something requests it, not on save and not on apply. So whether its complaint lands in the daemon's watch window depended on whether a browser happened to re-fetch during those few hundred milliseconds - and a page already showing the error overlay does not re-fetch at all. The report then sat in app.log from an earlier window, behind Watch.mark(), the new window was silent, and every other signal said the change went fine: apply reported Stable over a module the page could not load, then no_changes on the next apply, because Vite mode marked the file applied regardless.

The frontend leg now asks instead of overhearing. FRONTEND_CHECK has the connector fetch each changed frontend file through DevModeHandler.prepareConnection, as the browser would and on the base Vite was launched with, so a context-path app works too. A 500 is a refusal and carries Vite's own message; a 200 means the module compiles; a 404 is not a refusal at all.

The answer settles it in both directions, which is the load-bearing part. devServerAsked tells "served every file" apart from "nobody could be asked": a clean answer overrules the log, because an error left in it describes the version the edit just replaced - the daemon's own request for the broken one among them - and only an unaskable dev server falls back to the log. A refusal also leaves the file unmarked, so a repeat apply asks again rather than going quiet. The log line and the quoted errors follow suit: no claim that Vite applied a file it refused, and no stale parse error under Stable.

Verified by hand in Vite mode per the README sequence, which now covers the broken-then-fixed case. Vite mode has no IT for the reason the README gives.

Vite compiles a module when something requests it, not on save and not on
apply. So whether its complaint lands in the daemon's watch window depended
on whether a browser happened to re-fetch during those few hundred
milliseconds - and a page already showing the error overlay does not
re-fetch at all. The report then sat in app.log from an earlier window,
behind Watch.mark(), the new window was silent, and every other signal said
the change went fine: apply reported Stable over a module the page could not
load, then no_changes on the next apply, because Vite mode marked the file
applied regardless.

The frontend leg now asks instead of overhearing. FRONTEND_CHECK has the
connector fetch each changed frontend file through
DevModeHandler.prepareConnection, as the browser would and on the base Vite
was launched with, so a context-path app works too. A 500 is a refusal and
carries Vite's own message; a 200 means the module compiles; a 404 is not a
refusal at all.

The answer settles it in both directions, which is the load-bearing part.
devServerAsked tells "served every file" apart from "nobody could be asked":
a clean answer overrules the log, because an error left in it describes the
version the edit just replaced - the daemon's own request for the broken one
among them - and only an unaskable dev server falls back to the log. A
refusal also leaves the file unmarked, so a repeat apply asks again rather
than going quiet. The log line and the quoted errors follow suit: no claim
that Vite applied a file it refused, and no stale parse error under Stable.

Verified by hand in Vite mode per the README sequence, which now covers the
broken-then-fixed case. Vite mode has no IT for the reason the README gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the +0.0.1 label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Test Results

 1 433 files   - 1   1 517 suites   - 1   1h 40m 27s ⏱️ + 6m 17s
11 868 tests +3  11 801 ✅ +3  67 💤 ±0  0 ❌ ±0 
12 187 runs  +3  12 119 ✅ +3  68 💤 ±0  0 ❌ ±0 

Results for commit 35f42fc. ± Comparison against base commit af559a8.

♻️ This comment has been updated with latest results.

Three gaps in how a Vite-mode frontend failure reached the developer.

A type error was missed entirely. Fetching a module answers whether it can
be served, and types are stripped without being checked - so a type error,
and a stray ">" in JSX that oxc tolerates and tsc does not, come back 200.
Only vite-plugin-checker knows, and it logs at INFO where the level says
nothing; its errors are now matched separately (CHECKER_ERROR), carried
across Watch.mark() like the dev server's since it type-checks within a
moment of the save, and fatal when the change-set touched a frontend file.

The checker is read as a verdict, not as errors sighted in the window,
because it announces a clean project too (CHECKER_CLEAN). Both halves are
load-bearing: without the error half a broken .tsx passed as Stable, and
without the clean half breaking a file and putting it back failed the apply
that repaired it - the report was still in the log and nothing said it had
been superseded. The verdict outlives a window on purpose: it is the state
of the project, and the checker re-announces only when it changes.

The error itself was cut at 160 characters, which spent the budget on an
absolute path and dropped the diagnosis. The connector now joins the
report's lines with the separator instead of flattening them to spaces, so
the daemon can compact a whole report by the same rule it uses on one read
line by line (AppLog.report), and a verdict's reason is wrapped rather than
truncated (reasonRows, detail).
@tltv
tltv marked this pull request as ready for review September 4, 2026 11:57
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

The frontend verdict now asks the dev server instead of overhearing its log

flowchart LR
    subgraph Before
        direction TB
        B1["TransactionEngine.apply()"] -->|reads log, only if a browser re-fetched| B2["AppLog.Watch window"]
        B2 -->|carriedLogErrors, filter devServerError| B3["devServerFailure() verdict"]
        B3 -.->|silent window: Stable over a broken module| B4["Stable / Failed"]
    end
    subgraph After
        direction TB
        A1["TransactionEngine.askDevServer() (new)"]:::changed -->|FRONTEND_CHECK command| A2["DevLoopRedefiner.frontendCheck() (new)"]:::changed
        A2 -->|per changed file| A3["ViteHandler.prepareConnection() GET"]
        A3 -->|500 refused / 200 compiles| A4["Vite dev server"]
        A4 -->|devServerRefusal, overrules log| A5["devServerFailure() verdict"]:::changed
    end
    Before ~~~ After
    classDef changed stroke:#c9a227,stroke-width:3px
Loading

The figure shows where TransactionEngine gets its frontend verdict. Before, it filtered AppLog.Watch errors by devServerError — a race, since Vite only compiles a module on request, so a silent window let a broken file pass as Stable. After, askDevServer() sends FRONTEND_CHECK to the in-app connector DevLoopRedefiner.frontendCheck(), which fetches each changed file through ViteHandler.prepareConnection; a 500 is a refusal that devServerFailure() uses to overrule the log. Mechanism per the PR description and commit aa4784a.

Diagram Bot draws the mechanism this pull request touches; it does not review the change. Verify it against the diff.

Generated by Diagram Bot for issue #25466 ·

@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
29.4% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant