diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15f9099acd..dda421ec5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `codegraph_explore` and `codegraph context` now find your project's busiest file, and favor the code around it, even when twenty or more test or generated files are busier still, such as a package of protobuf stubs. Before, the search looked only at the 20 busiest files and then set the tests and generated ones aside, so it could find no file at all, or judge one far ahead of the next when it was not. `getTopRouteFile()` in the library counts every file's routes the same way. Thanks @inth3shadows for the report. (#2578)
- In JavaScript and TypeScript, a call written as the body of an `if`, `while`, `for` or `do` without braces, like `if (e.key === "Escape") setOpen(false);`, no longer links to a same-named property or method in another file when the name is the file's own, such as a `setOpen` from `useState` (#2585).
- `codegraph sync` no longer fails with "FOREIGN KEY constraint failed" in a project whose code lazy-loads a workspace package, like Supabase Studio's `dynamic(() => import('icons').then((mod) => mod.EdgeFunctions))` or vben's `() => import('@vben/layouts').then((m) => m.IFrameView)`. Before, every sync after an edit in such a project rolled back, so the index stopped following your changes until a full re-index. Thanks @bompus for the report and @inth3shadows. (#2565)
+- In Vue, a `
+`;
+
+// TypeScript types and JSX together: only the TSX grammar reads this cleanly,
+// and the extra declarations keep the "no symbols" warning from firing either way.
+const TYPES_AND_JSX = `
+
+`;
+
+/** The script body of a single-script SFC, as the viewer locates it. */
+function scriptRegion(source: string) {
+ const regions = syntaxRegionsFor(source, 'vue');
+ expect(regions).toHaveLength(1);
+ const region = regions![0]!;
+ return { language: region.language, body: source.slice(region.start, region.end) };
+}
+
+describe('Vue \n`;
+ expect(scriptRegion(ts).language).toBe('typescript');
+ expect(extractFromSource('C.vue', ts).nodes.some((n) => n.kind === 'interface' && n.name === 'P')).toBe(true);
+ });
+
+ it('still reads lang="ts" as TypeScript and no lang as JavaScript', () => {
+ const ts = `\n`;
+ const js = `\n`;
+ expect(scriptRegion(ts).language).toBe('typescript');
+ expect(scriptRegion(js).language).toBe('javascript');
+ expect(extractFromSource('A.vue', ts).nodes.some((n) => n.name === 'n')).toBe(true);
+ expect(extractFromSource('B.vue', js).nodes.some((n) => n.name === 'n')).toBe(true);
+ });
+});
diff --git a/src/extraction/syntax-tokens.ts b/src/extraction/syntax-tokens.ts
index 67c0d1b710..bfee678f74 100644
--- a/src/extraction/syntax-tokens.ts
+++ b/src/extraction/syntax-tokens.ts
@@ -362,6 +362,8 @@ export interface SyntaxRegion {
const SCRIPT_BLOCK = /