diff --git a/docs/workflows/2026-07-21-clip-scintilla-margin.md b/docs/workflows/2026-07-21-clip-scintilla-margin.md deleted file mode 100644 index 385ac64..0000000 --- a/docs/workflows/2026-07-21-clip-scintilla-margin.md +++ /dev/null @@ -1,65 +0,0 @@ -# Clip Scintilla margin painting to the editor viewport - -## Product intent - -Keep the existing pixel-smooth macOS editor scrolling while preventing line -numbers from being painted below the editor into the bottom panel. - -## Scope - -- Artifact type: Lynxtron GO showcase with a macOS native Scintilla extension. -- Distribution type: workspace source build used by `pnpm preview`. -- Runtime path: `lynxtron-go/scintilla-extension` on macOS Cocoa. -- Restrict the Scintilla margin dirty rectangle and graphics context to the - visible content viewport before painting line numbers. -- Preserve partially visible first and last lines introduced by the existing - Lynxtron-owned smooth-scroll content view. -- Do not alter the Windows implementation. -- Preserve all unrelated user changes in the dirty worktree. - -## Root cause - -`SCIMarginView` is a separate `NSRulerView`. Its Cocoa drawing path expands a -partial invalidation to the full ruler bounds when the ruler is taller than the -content viewport. Scintilla then paints the last partial line with an unclipped -text operation, so its line number can cross the editor's lower boundary. - -## Acceptance criteria - -1. Margin painting cannot draw below `scrollView.contentView.bounds`. -2. Pixel-level vertical scrolling remains enabled and partial lines remain - supported. -3. The legacy oversized ruler invalidation cannot expand the Scintilla margin - paint rectangle beyond the visible content viewport. -4. The macOS Scintilla extension builds successfully. -5. A running Lynxtron GO editor is smoke-tested by scrolling a long file near - the editor's lower edge; no line-number glyph appears in the bottom panel. -6. Windows code and unrelated worktree changes remain untouched. - -## Verification notes - -- Run `pnpm --dir lynxtron-go run build:scintilla-extension`. -- Relaunch the preview so it loads the rebuilt native extension. -- Capture or inspect the editor after a partial-line vertical scroll. -- Record any runtime automation limitation explicitly. - -## Verification result - -- `pnpm --dir lynxtron-go run build:scintilla-extension` passed and rebuilt - `ScintillaView.mm`, `libscintilla.a`, and `lynx_scintilla_module.node`. -- `pnpm --dir lynxtron-go run build` passed; Rspeedy reported only the - pre-existing unsupported-CSS warnings and Rspack reported the two existing - dynamic-require warnings. -- Relaunched `dist/desktop` after quitting the stale singleton instance, so the - running app loaded the rebuilt native module. -- Scrolled the long `src/app/App.css` editor and inspected the native macOS - window: line numbers stayed inside the editor boundary and no glyph appeared - in the Console panel. -- Lynx DevTool remained useful for session discovery, while Computer Use was - required for the final visual check because DevTool screenshots do not - reliably include native Scintilla subviews. -- `pnpm --dir lynxtron-go test` completed with 183/184 tests passing. The one - failure is the unrelated, repeatable - `TypeScriptLanguageService > treats lynxtron-go desktop host sources as a - Node environment` assertion, which currently receives three diagnostics at - `src/extension-host/__tests__/typescript.test.ts:393`. diff --git a/docs/workflows/2026-07-21-disable-scintilla-line-scroll-snap.md b/docs/workflows/2026-07-21-disable-scintilla-line-scroll-snap.md deleted file mode 100644 index 740988c..0000000 --- a/docs/workflows/2026-07-21-disable-scintilla-line-scroll-snap.md +++ /dev/null @@ -1,36 +0,0 @@ -# Disable Scintilla vertical line scroll snapping - -## Product intent - -Make trackpad and mouse-wheel scrolling in Lynxtron GO code editors feel continuous. A partially visible first or last line is acceptable; the viewport must not jump to the nearest whole line. - -## Scope - -- Artifact type: Lynxtron GO showcase with a macOS native Scintilla extension. -- Distribution type: workspace source build used by `pnpm preview`. -- Runtime path: `lynxtron-go/scintilla-extension` on macOS Cocoa. -- Disable only vertical whole-line snapping performed by `SCIContentView.adjustScroll:`. -- Preserve horizontal whole-point rounding used to avoid Retina drawing debris. -- Prefer a Lynxtron-owned subclass/adapter in `module/scintilla_view.mm`; do not modify vendored files under `scintilla-extension/scintilla/` when the existing `contentViewClass` extension point is sufficient. -- Do not change Windows behavior. - -## Root cause - -Vendored Cocoa `SCIContentView.adjustScroll:` rounds `proposedVisibleRect.origin.y` to the nearest multiple of `TextHeight`. This converts smooth pixel scrolling into line-sized jumps and feels sticky. - -## Acceptance criteria - -1. macOS vertical scrolling accepts fractional/partial-line `origin.y` values without rounding to line height. -2. Horizontal scrolling retains whole-point rounding for interior document positions. -3. Existing Scintilla content, styling, selection, and editor registration behavior remains unchanged. -4. No generated output, build directory, or `node_modules` change is committed. -5. `pnpm --dir lynxtron-go run build:scintilla-extension` succeeds. -6. The narrowest practical Lynxtron GO build or runtime smoke check succeeds and is reported. - -## Verification notes - -- `pnpm --dir lynxtron-go run build:scintilla-extension` passed. -- `pnpm --dir lynxtron-go exec rspack build` passed with the two existing dynamic-`require` warnings. -- `pnpm preview` rebuilt the native module and launched Lynxtron GO successfully; the host registered the Scintilla editor extension. -- The built module contains `LynxtronSCIContentView` / `LynxtronScintillaView`, and the build output matches the copy under `dist/desktop` by SHA-256. -- Runtime smoke test used 5-pixel scroll-wheel events on a file longer than one viewport. The viewport retained partial-line offsets instead of snapping to a whole line. diff --git a/docs/workflows/2026-07-21-match-scintilla-bounce-background.md b/docs/workflows/2026-07-21-match-scintilla-bounce-background.md deleted file mode 100644 index 3567bda..0000000 --- a/docs/workflows/2026-07-21-match-scintilla-bounce-background.md +++ /dev/null @@ -1,38 +0,0 @@ -# Match Scintilla overscroll background to the editor - -## Product intent - -Keep the macOS editor visually continuous while the user pulls past the top edge. The temporary rubber-band area must use the active editor theme background instead of the native scroll view's default background. - -## Scope - -- Artifact type: Lynxtron GO showcase with a macOS native Scintilla extension. -- Distribution type: workspace source build used by `pnpm preview`. -- Runtime path: `lynxtron-go/scintilla-extension` on macOS Cocoa. -- Fix the background exposed by vertical elastic overscroll. -- Keep the color synchronized with both the compiled-in default theme and `ApplyTheme` dark/light changes. -- Implement in the Lynxtron-owned adapter; do not add this product-specific behavior to vendored Scintilla. -- Do not change Windows behavior or disable native rubber-band scrolling. - -## Root cause - -Scintilla paints the document background itself, but elastic overscroll temporarily exposes the enclosing `NSScrollView` / `NSClipView`. Those native views retain their default AppKit background instead of the Scintilla `STYLE_DEFAULT` color, so the pulled-open area does not match the editor. - -## Acceptance criteria - -1. Pulling down past the first line reveals the same color as the active editor content background. -2. The background follows both dark and light theme changes. -3. Continuous partial-line scrolling from the preceding fix remains enabled. -4. No vendored Scintilla or Windows source is changed by this task. -5. The native extension and Lynxtron GO desktop bundle build successfully. -6. A runtime smoke check confirms the Scintilla extension registers and the editor remains usable. - -## Verification notes - -- `pnpm --dir lynxtron-go run build:scintilla-extension` passed. -- `pnpm --dir lynxtron-go exec rspack build` passed with the two existing dynamic-`require` warnings. -- The built module contains `-[LynxtronScintillaView syncBounceBackgroundWithStyleDefault]`, and the build output matches the copy under `dist/desktop` by SHA-256. -- `run-dev` restarted successfully and registered the Scintilla editor extension. -- Switching Settings → Appearance from Dark to Light and back updated every native editor immediately, exercising both `ApplyTheme` background synchronization branches; the original Dark setting was restored afterwards. -- AppKit did not enter physical trackpad rubber-band state from synthesized `CGEvent` scroll phases, so the exact peak-frame visual assertion still requires a real trackpad gesture. The native container colors are nevertheless derived from the same live `STYLE_DEFAULT` value exposed during that state. -- For parallel GUI verification, launch with `LYNXTRON_ALLOW_MULTI=1` to skip the singleton lock and avoid interfering with another Lynxtron GO instance. diff --git a/docs/workflows/2026-07-21-prevent-scintilla-first-frame-flash.md b/docs/workflows/2026-07-21-prevent-scintilla-first-frame-flash.md deleted file mode 100644 index 618c168..0000000 --- a/docs/workflows/2026-07-21-prevent-scintilla-first-frame-flash.md +++ /dev/null @@ -1,45 +0,0 @@ -# Prevent Scintilla first-frame theme flash - -## Scope - -- Artifact type: Lynxtron GO showcase with a macOS native Scintilla extension. -- Distribution type: workspace source build consumed through `dist/desktop` / `pnpm preview`. -- Runtime path: `lynxtron-go/scintilla-extension/module/scintilla_view.mm` on macOS Cocoa. -- Keep the native editor on its configured background and font size from its first visible frame. -- Do not change Windows or vendored Scintilla sources. - -## Reproduction - -Hide a visible Fiddle editor pane, start a 60 fps screen recording, then reopen the same file from the sidebar. Before the fix, recorded frames show this sequence: - -1. the pane toolbar appears over a black native canvas; -2. the first line number appears on the black canvas at the compiled default size; -3. the canvas switches to the Fiddle theme background and configured 13pt size. - -## Root cause - -The macOS constructor enqueues compiled default styling with `dispatch_async`. If properties arrive on the main thread, `ApplyTheme` runs first, then the queued constructor defaults overwrite it with the 14pt fallback. The editor can attach and paint that intermediate state until the JS post-mount theme call corrects it. Off-main `ApplyTheme` is also asynchronous, so layout attachment is not guaranteed to wait for theme application. - -## Implementation - -- Run the constructor's Scintilla default setup inline on the main thread, or synchronously dispatch it there when construction starts off-main. -- Apply `theme-dark` and `font-size` synchronously on the main thread before returning from `ApplyTheme`. -- Leave the existing palette, continuous scrolling, and overscroll background behavior unchanged. - -## Verification - -- Captured the failure before the change at 60 fps: the new Preload pane painted a black canvas, then a large line number, and only afterward changed to the configured background and font size. -- Rebuilt `lynxtron-scintilla-editor`, then rebuilt the desktop bundle. The source build and copied `dist/desktop` native modules both have SHA-256 `412e18115ea2cec90c88381aaeaad8301644aefd0cb7a134e4e13fee0a3d030f`. -- Restarted the source runtime with `LYNXTRON_ALLOW_MULTI=1` and captured the same close/reopen interaction at 120 fps. The Preload pane's first visible frame already has the configured background and 13pt text; subsequent frames show no background or font-size transition. - -## Windows follow-up - -Windows remains to be fixed and verified on a Windows runtime. Its implementation has a different ordering bug with the same likely visual result: `OnPropertiesChanged` may receive `theme-dark` and `font-size` before the Scintilla `HWND` exists, but `ApplyTheme` returns without caching them. `OnLayoutChanged` later creates a visible control with the hard-coded dark 14pt defaults, so a later JS theme call can still cause a background and font-size jump. The Windows fix should cache theme properties before the `HWND` exists, create the control hidden, apply the cached theme after `ConfigureScintilla`, and only then reveal it. - -## Acceptance criteria - -1. Constructor defaults are fully installed before the native element can receive theme properties. -2. `theme-dark` and `font-size` property application completes before `OnPropertiesChanged` returns. -3. Reopening a pane shows no black frame and no visible font-size jump. -4. Existing continuous scrolling and overscroll background synchronization remain unchanged. -5. The native extension and desktop bundle build successfully. diff --git a/lynxtron-go/src/extension-host/__tests__/typescript.test.ts b/lynxtron-go/src/extension-host/__tests__/typescript.test.ts index 6af4f43..38f9204 100644 --- a/lynxtron-go/src/extension-host/__tests__/typescript.test.ts +++ b/lynxtron-go/src/extension-host/__tests__/typescript.test.ts @@ -304,6 +304,9 @@ import { useState } from '@lynx-js/react'; export function App() { const [count] = useState(1); + const interval = setInterval(() => console.log(count), 1000); + const timeout = setTimeout(() => clearInterval(interval), 1000); + clearTimeout(timeout); return {count}; } `); @@ -320,6 +323,11 @@ root.render(); expect(markers.some(m => m.code === 2875)).toBe(false); expect(markers.some(m => m.code === 2307)).toBe(false); + expect(markers.some(m => m.message.includes("Cannot find name 'setInterval'"))).toBe(false); + expect(markers.some(m => m.message.includes("Cannot find name 'clearInterval'"))).toBe(false); + expect(markers.some(m => m.message.includes("Cannot find name 'setTimeout'"))).toBe(false); + expect(markers.some(m => m.message.includes("Cannot find name 'clearTimeout'"))).toBe(false); + expect(markers.some(m => m.message.includes("Cannot find name 'console'"))).toBe(false); expect(markers.filter(m => m.severity === 'error')).toHaveLength(0); } finally { fs.rmSync(tempRoot, { recursive: true, force: true }); diff --git a/lynxtron-go/src/extension-host/language-server/typescript.ts b/lynxtron-go/src/extension-host/language-server/typescript.ts index e65b8d6..b00a2c4 100644 --- a/lynxtron-go/src/extension-host/language-server/typescript.ts +++ b/lynxtron-go/src/extension-host/language-server/typescript.ts @@ -10,6 +10,31 @@ interface FileEntry { content: string; } +const LYNX_GLOBALS_WORKAROUND_FILE = '.lynxtron-go-lynx-globals.d.ts'; +const LYNX_GLOBALS_WORKAROUND_SOURCE = ` +export {}; + +declare global { + interface Console { + debug(...args: any[]): void; + error(...args: any[]): void; + group(label?: string): void; + groupEnd(): void; + info(...args: any[]): void; + log(...args: any[]): void; + alog(...args: any[]): void; + warn(...args: any[]): void; + } + + var console: Console; + + function setTimeout(callback: (...args: unknown[]) => unknown, delay: number): number; + function setInterval(callback: (...args: unknown[]) => unknown, delay: number): number; + function clearTimeout(timeoutId: number): void; + function clearInterval(timeoutId: number): void; +} +`; + interface ProjectConfig { key: string; projectRoot: string; @@ -236,6 +261,16 @@ function shouldUseBundledModuleFallback( return /\.(?:[cm]?js|jsx)$/.test(resolved.resolvedFileName); } +function needsLynxGlobalsWorkaround(options: ts.CompilerOptions): boolean { + if (!options.types?.includes('@lynx-js/types')) return false; + + // An omitted lib list uses TypeScript's defaults, which already include DOM. + // Lynx app configs explicitly choose an ES-only lib to avoid browser globals. + if (!options.lib) return false; + + return !options.lib.some((libPath) => path.basename(libPath).toLowerCase() === 'lib.dom.d.ts'); +} + class LanguageServiceHost implements ts.LanguageServiceHost { private files = new Map(); private rootFiles: string[]; @@ -253,6 +288,16 @@ class LanguageServiceHost implements ts.LanguageServiceHost { this.options = options; this.rootFiles = [...new Set(rootFiles)]; this.bundledFallback = bundledFallback; + + // @lynx-js/types@3.8 declares the timer functions outside declare global + // and exports Console without declaring the global console variable. Keep + // Lynx projects free of DOM types while making the runtime globals visible. + if (needsLynxGlobalsWorkaround(options)) { + this.files.set(path.join(projectRoot, LYNX_GLOBALS_WORKAROUND_FILE), { + version: 0, + content: LYNX_GLOBALS_WORKAROUND_SOURCE, + }); + } } updateFile(filePath: string, content: string, version: number) { @@ -294,10 +339,13 @@ class LanguageServiceHost implements ts.LanguageServiceHost { } fileExists(path: string): boolean { + if (this.files.has(path)) return true; return ts.sys.fileExists(path); } readFile(path: string): string | undefined { + const entry = this.files.get(path); + if (entry) return entry.content; return ts.sys.readFile(path); }