Skip to content

feat: Stac VS Code Plugin#448

Merged
divyanshub024 merged 18 commits intodevfrom
dv/stac-vscode-plugin
Mar 6, 2026
Merged

feat: Stac VS Code Plugin#448
divyanshub024 merged 18 commits intodevfrom
dv/stac-vscode-plugin

Conversation

@divyanshub024
Copy link
Member

@divyanshub024 divyanshub024 commented Feb 13, 2026

Description

This PR introduces the initial implementation of the Stac VS Code extension, enabling developers to build and preview Server-Driven UI screens using the Stac framework directly inside VS Code.

Key features included in this PR:

Live Preview

  • Adds side-by-side preview support for @StacScreen annotated screens.
  • Supports platform simulation for Android, iOS, and Web.
  • Enables theme selection using @StacThemeRef annotations.
  • Automatically refreshes preview on save.
  • Implements fast JSON generation using runner strategy with build fallback.
  • Handles preview host lifecycle, including automatic port recovery.

Wrap Quick Fixes

  • Adds quick-fix actions (Cmd+.) to wrap widgets with Stac components.

  • Includes preset wrappers such as:

    • StacContainer
    • StacPadding
    • StacCenter
    • StacAlign
    • StacSizedBox
    • StacExpanded
  • Supports wrapping with any Stac widget using dynamic catalog generation.

Snippets

  • Adds context-aware snippets:

    • stac screen — create new Stac screen template
    • stac theme — create new Stac theme template
  • Snippets are only shown in Stac DSL contexts.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix
  • Breaking change
  • Code refactor
  • Documentation
  • Build configuration change
  • Chore

Summary by CodeRabbit

  • New Features

    • Adds a full VS Code extension: Flutter-backed live preview with device/theme controls, host process and webview UI, asset serving and font loading, JSON generation strategies (runner + build fallback), wrap quick-fixes, snippet completions, and preview commands.
  • Documentation

    • Adds README, CHANGELOG, and LICENSE.
  • Tests

    • Adds unit/integration tests covering preview, asset serving, font discovery, wrap/remove utilities, and providers.
  • Chores

    • Adds editor/workspace configs, build/test tasks, packaging ignores, and extension manifest.

Co-authored-by: Codex
- Introduced two new snippets: `stac screen` for creating a new Stac screen template and `stac theme` for creating a new Stac theme template.
- Updated the extension description and documentation to reflect the new snippets.
- Removed the old snippet catalog as it is no longer needed with the new implementation.
- Adjusted the snippet completion provider to support the new snippet triggers.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new stac-vscode VS Code extension under tools/stac-vscode: extension manifest and docs, workspace dev tooling, a Flutter web preview host, asset server, preview orchestration (host process, webview, JSON pipeline), wrap/snippet providers, utilities, tests, ESLint, and license.

Changes

Cohort / File(s) Summary
Workspace & dev tooling
tools/stac-vscode/.vscode/extensions.json, tools/stac-vscode/.vscode/launch.json, tools/stac-vscode/.vscode/settings.json, tools/stac-vscode/.vscode/tasks.json, tools/stac-vscode/.vscode-test.mjs, tools/stac-vscode/.vscodeignore
Adds VS Code workspace configs, launch/task/settings, test CLI config, and packaging ignore patterns.
Metadata & docs
tools/stac-vscode/package.json, tools/stac-vscode/README.md, tools/stac-vscode/CHANGELOG.md, tools/stac-vscode/LICENSE
New extension manifest, README, changelog, and MIT license.
Preview host (Flutter)
tools/stac-vscode/preview_host/lib/main.dart, tools/stac-vscode/preview_host/pubspec.yaml
Adds a Flutter web preview host application and pubspec for rendering STAC payloads and communicating with the extension.
Preview orchestration & runtime
tools/stac-vscode/src/preview/...
assetServer.ts, previewHostProcess.ts, previewManager.ts, previewPanel.ts, jsonTransformer.ts, fontDiscovery.ts, buildFallback.ts, jsonGeneration.ts, utils.ts, types.ts
Implements preview lifecycle: static asset server, host process management, webview panel and messaging protocol, JSON generation and build fallback, asset rewriting, font discovery, port utilities, and related types.
Extension core
tools/stac-vscode/src/extension.ts, tools/stac-vscode/src/core/constants.ts
Adds extension activation, command registration (wrap presets, snippets, catalog regeneration), and central constants.
Wrap & remove utilities
tools/stac-vscode/src/wrap/...
applyWrapEdit.ts, removeWidget.ts, stacWrapCodeActionProvider.ts, wrapperTemplates.ts
Adds wrapping/removal logic, wrapper templates, workspace-edit builders, and a Dart code-action provider for wrap/remove quick fixes.
Snippets & completion
tools/stac-vscode/src/snippets/stacSnippetCompletionProvider.ts
Adds a snippet completion provider for STAC DSL snippets.
Lint & config
tools/stac-vscode/eslint.config.mjs
Adds ESLint configuration for TypeScript files.
Tests
tools/stac-vscode/src/test/*, tools/stac-vscode/.vscode-test.mjs
Adds unit tests for font discovery, asset server and jsonTransformer, providers, wrap/remove utilities, and test harness configuration.
Repository ignore updates
.gitignore
Adds ignores for nested node_modules and stac-vscode build/out/test artifacts.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Ext as "VS Code Extension\n(PreviewManager)"
    participant Asset as "AssetServer"
    participant Host as "PreviewHostProcess\n(Flutter host)"
    participant Web as "Webview / iframe"

    User->>Ext: open preview / edit / save
    Ext->>Asset: start(workspaceRoot) -> returns port
    Ext->>Host: ensureStarted() -> returns hostUrl/port
    Ext->>Web: postMessage(render payload { json, themes, fonts, assetPort })
    Web->>Host: forward stac.preview.render (iframe -> host)
    Host-->>Web: stac.preview.rendered / stac.preview.error
    Web->>Ext: relay rendered / ready / error events
    Ext->>User: update status / show messages
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • Potatomonsta

Poem

🐰 I hopped through code both near and far,
served fonts and assets from a tiny jar,
stitched previews, snippets, wrappers with care,
messages bounced through iframe air,
I nibble bugs and leave a carrot-star. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Stac VS Code Plugin' clearly describes the main change—introducing a new VS Code extension for the Stac framework with comprehensive features for live preview, wrap quick fixes, and snippets.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dv/stac-vscode-plugin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tools/stac-vscode/eslint.config.mjs`:
- Around line 3-27: The second config block (the object containing plugins,
languageOptions, and rules) lacks a files filter so its TypeScript-specific
parser and rules apply to all files; fix by merging the two objects or adding
files: ["**/*.ts"] to that second object so the TypeScript parser and rules
(plugins/@typescript-eslint, languageOptions.parser, and rules like
"@typescript-eslint/naming-convention", curly, eqeqeq, no-throw-literal, semi)
only apply to .ts files.
🧹 Nitpick comments (3)
tools/stac-vscode/.vscodeignore (1)

1-11: Consider excluding test output from the packaged extension.

The compiled test files under out/test/ will be included in the VSIX package since only src/** (source) is excluded but not the compiled test output. Adding an exclusion prevents unnecessary bloat.

Suggested addition
 **/.vscode-test.*
+out/test/**
tools/stac-vscode/README.md (1)

39-42: Dart version requirement is valid—consider if 3.9.2+ is the true minimum.

Dart 3.9.2 is a legitimate, stable release (August 2025). However, if the extension works with earlier Dart 3.x versions, consider lowering the minimum requirement to improve adoption and compatibility for users on stable versions.

tools/stac-vscode/CHANGELOG.md (1)

5-5: Consider adding a release date to the version heading.

Changelogs conventionally include a date next to the version number (e.g., ## [0.1.0] - 2026-02-13) per Keep a Changelog conventions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Fix all issues with AI agents
In `@tools/stac-vscode/preview_host/lib/main.dart`:
- Around line 202-209: The _fetchFont function currently calls http.get without
a timeout which can hang and block _loadFonts (which uses Future.wait); wrap the
http.get(Uri.parse(url)) call with a timeout (e.g., .timeout(Duration(seconds:
X))) and catch TimeoutException (and other errors) to throw a clear Exception
like "Timed out loading font from $url" so callers don't wait indefinitely;
update _fetchFont to apply the timeout and convert timeout/errors into
descriptive exceptions so _loadFonts won't hang.
- Line 35: The event listener leak is caused by calling _onMessage.toJS inline
(in web.window.addEventListener and removeEventListener) which creates distinct
JS wrapper functions each time; fix it by creating and storing a single
JS-wrapped callback field (e.g. _onMessageJs) when the listener is first
attached and then reuse that same field for both
web.window.addEventListener(_onMessageJs) and
web.window.removeEventListener(_onMessageJs) (ensure the wrapper is created
once, cleared on dispose() and referenced in the code paths that currently call
_onMessage.toJS).

In `@tools/stac-vscode/src/extension.ts`:
- Around line 128-132: The guard `if (!edit)` is ineffective because
createRemoveWidgetEdit always returns a WorkspaceEdit; replace it with a check
for no-op edits by testing edit.size === 0 (or another WorkspaceEdit emptiness
check) so the early return only triggers when there are no actual edits; update
the code around the variables document/target and the call to
createRemoveWidgetEdit to use this emptiness check instead of truthiness.
- Around line 85-138: The wrapWithStacWidget command disposable
(customDisposable) is never pushed to context.subscriptions causing a leak;
after registering the command via
vscode.commands.registerCommand(COMMANDS.wrapWithStacWidget, ...) add
context.subscriptions.push(customDisposable) (i.e., push the customDisposable
the same way removeDisposable and other disposables are pushed) so the command
is disposed when the extension deactivates.

In `@tools/stac-vscode/src/preview/assetServer.ts`:
- Around line 80-81: The createReadStream call in assetServer.ts can emit an
error if the file is deleted after statSync; attach an 'error' handler to the
stream returned by fs.createReadStream (the variable stream) that handles stream
errors by setting the response status (e.g., 404 or 500 as appropriate), logging
the error, and ending the response (res.end()) to avoid an unhandled exception
and leaking the response; also ensure you remove or ignore further piping after
an error and do not rely solely on stream.pipe(res) without the error handler.
- Around line 89-96: The Promise in AssetServer.start (assetServer.ts) registers
the 'error' handler after calling this.server!.listen, which can miss
synchronous listen errors; move the this.server!.on('error', ...) registration
to before this.server!.listen (and optionally remove the handler once listening
succeeds) and resolve only after the listen callback or 'listening' event fires
so the error handler is always attached before attempting to bind.
- Around line 48-50: The current regex-based safePath is fragile; replace it by
resolving the requested path against workspaceRoot (use path.resolve or
path.join + path.resolve) using url.pathname (ensure you prepend a '.' or strip
leading slashes appropriately), then compute the resolved file path (replace the
existing safePath/filePath logic) and verify the resolved path is inside
workspaceRoot (use path.relative(workspaceRoot, resolvedPath) and deny if it
starts with '..' or use a startsWith check after ensuring both paths end with
the same separator). If the check fails, return a 403/deny instead of serving
the file. Update the code around the safePath and filePath variables (references
to path.normalize, safePath, filePath, workspaceRoot, and url.pathname)
accordingly.

In `@tools/stac-vscode/src/preview/jsonTransformer.ts`:
- Around line 14-28: The early return for matched StacImage objects (the if
checking json.type === 'image' && json.imageType === 'asset' && typeof json.src
=== 'string') does a shallow spread and misses nested image objects; instead,
before returning, recursively transform the remaining properties so nested
fields (e.g., child, error) are processed. Update the branch to build a new
object that sets imageType to 'network' and src to
`http://127.0.0.1:${assetServerPort}/${json.src.replace(/^\//, '')}` but runs
the same transformer over all other property values (recursing into
objects/arrays) rather than returning the shallow ...json as-is. Ensure you
reference the same asset URL construction and preserve other fields while
applying the recursive transform.

In `@tools/stac-vscode/src/preview/previewHostProcess.ts`:
- Around line 97-105: When canBindPort(this.port) is false and
findAvailablePort(...) returns undefined, the code currently continues and will
attempt to spawn Flutter on a busy port; update the block in the preview host
startup (the method using this.port, canBindPort, and findAvailablePort) to
handle the undefined case by writing a clear error/warning to this.outputChannel
(e.g. appendLine indicating no free ports found) and then aborting startup
(throw an Error or return early) instead of continuing with the original port so
the caller doesn't waste time trying to start Flutter on a known-busy port.

In `@tools/stac-vscode/src/preview/previewManager.ts`:
- Around line 408-413: The asset server isn't restarted when the workspace root
changes because AssetServer.start() is a no-op if a server already exists;
modify previewManager to track the last project root used to start the server
(e.g. add a lastAssetRoot field) and before calling
AssetServer.start(projectRoot) compare projectRoot to lastAssetRoot—if
different, call this.assetServer.stop() (or instantiate a new AssetServer) then
start it with the new projectRoot and update lastAssetRoot; reference the
existing this.assetServer, AssetServer.start and projectRoot in the change so
the server resets whenever the workspace root changes.

In `@tools/stac-vscode/src/preview/previewPanel.ts`:
- Around line 395-406: startDelivery currently starts an unbounded setInterval
that reposts latestRenderMessage and only stops when latestRenderMessage becomes
null or hostReady changes; add a safety limit (e.g., maxAttempts or a TTL
timestamp) inside startDelivery to count attempts or compare elapsed time and
call stopDelivery plus surface an error state/notification when exceeded. Update
references: modify startDelivery to initialize an attempts counter (or deadline)
and increment/compare it inside the interval that calls
postRenderToFrame(latestRenderMessage); when the limit is reached clear
deliveryTimer and invoke stopDelivery and a handler to mark the preview as
failed (or show an error to the user) so deliveryTimer cannot run indefinitely.
Ensure deliveryTimer and latestRenderMessage semantics remain unchanged
elsewhere.

In `@tools/stac-vscode/src/test/preview_assets.test.ts`:
- Around line 46-52: The Promise wrapping the http.get call (the one that
creates response via http.get(`http://127.0.0.1:${port}/test.png`, ...`) can
hang if the request errors; update that Promise to attach an .on('error', ...)
handler to the request so the Promise rejects (or resolves with an error status)
when a connection error occurs, and do the same change for the analogous font
test Promise (the other http.get wrapper around the font request). Ensure you
reference the existing variables (port, response) and keep the original data and
end handlers while adding error handling to prevent the test from hanging.
🧹 Nitpick comments (15)
tools/stac-vscode/preview_host/pubspec.yaml (1)

14-15: Consider pinning web dependency instead of any.

While publish_to: none limits the blast radius, web: any accepts any major version — including future breaking releases. A caret constraint (e.g., ^1.1.0) would prevent silent breakage when the web package ships a major bump.

tools/stac-vscode/preview_host/lib/main.dart (1)

92-94: Theme parsing errors are silently swallowed.

If StacTheme.fromJson or stacTheme.parse throws, the user gets no feedback about why the theme didn't apply. Consider logging the error via _log so it surfaces in the preview output channel.

Proposed fix
-    } catch (_) {
+    } catch (e) {
+      _log('Failed to parse theme: $e');
       return null;
     }
tools/stac-vscode/src/test/font_discovery.test.ts (1)

8-8: Consider using os.tmpdir() instead of __dirname for the temp test directory.

Using __dirname places test artifacts inside the compiled output directory, which could be fragile if the output dir is cleaned between runs or used by other tooling. os.tmpdir() + a unique suffix (e.g., via mkdtempSync) is the conventional approach for test fixtures.

tools/stac-vscode/src/preview/utils.ts (1)

14-25: canBindPort may hang indefinitely if the TCP stack never responds.

If listen neither succeeds nor errors (e.g., on an unusual network configuration), the returned promise will never settle, stalling findAvailablePort. Consider adding a timeout to guard against this.

⏱️ Proposed timeout guard
 export function canBindPort(port: number): Promise<boolean> {
     return new Promise((resolve) => {
         const server = net.createServer();
+        const timeout = setTimeout(() => {
+            server.close();
+            resolve(false);
+        }, 1000);
         server.once('error', () => {
+            clearTimeout(timeout);
             resolve(false);
         });
         server.listen(port, '127.0.0.1', () => {
+            clearTimeout(timeout);
             server.close(() => {
                 resolve(true);
             });
         });
     });
 }
tools/stac-vscode/src/test/remove.test.ts (1)

6-48: Duplicate createMockDocument helper across test files.

This mock is nearly identical to the one in wrap.test.ts. Consider extracting it into a shared test utility to avoid duplication.

Also applies to: 9-50

tools/stac-vscode/src/wrap/removeWidget.ts (4)

4-4: Unused constant MATCH_CHILD_PROP.

This regex is defined but never referenced in the file. Remove it to avoid confusion.

🧹 Proposed fix
-const MATCH_CHILD_PROP = /child:\s*/;
-

76-76: substr is deprecated — use substring or slice instead.

String.prototype.substr is a legacy method. Replace with substring(i, i + childKey.length).

♻️ Proposed fix
-            if (expression.substr(i, childKey.length) === childKey) {
+            if (expression.substring(i, i + childKey.length) === childKey) {

161-185: Excessive design-note comments in production code.

These read as stream-of-consciousness notes from the implementation phase. Consider trimming to a concise summary of the approach.


14-148: Duplicated state machine for comment/string/nesting tracking.

The lexer logic (lines 24–62, 106–126) is repeated almost verbatim. Extracting a shared tokenizer or helper would reduce maintenance burden, but the current approach is functional.

tools/stac-vscode/src/wrap/stacWrapCodeActionProvider.ts (1)

75-76: Nit: extra blank lines.

Two consecutive blank lines between the custom action block and the remove action block. One blank line is sufficient for readability.

tools/stac-vscode/src/preview/previewPanel.ts (1)

95-104: Type assertion on postThemes message can be avoided.

The as PreviewThemesMessage & { selectedThemeName: string | null } cast suggests PreviewThemesMessage doesn't include selectedThemeName. Consider extending the type definition in types.ts to include the field, which would eliminate the need for the cast and keep the contract explicit.

tools/stac-vscode/src/extension.ts (1)

46-53: Duplicate command-to-preset mapping.

This commandByPreset object duplicates the WRAP_COMMANDS record in stacWrapCodeActionProvider.ts (lines 7-14). Consider extracting the mapping to constants.ts or reusing the one from stacWrapCodeActionProvider.ts to keep them in sync.

tools/stac-vscode/src/wrap/applyWrapEdit.ts (1)

57-68: appendCommaToLastLine mutates input array in-place.

The function modifies lines[last] directly and also returns the array. Current callers always pass freshly constructed arrays, so this is safe today. But the mutate-and-return pattern can be surprising. A brief doc comment noting the mutation would help future callers.

tools/stac-vscode/src/preview/previewManager.ts (2)

256-290: async keyword is unnecessary on handlers that don't await.

handleDidChangeActiveEditor and handleDidChangeSelection are declared async but contain no await expressions. While harmless, the async keyword creates a microtask wrapper and can be misleading since the callers already use void this.handle...(). Consider removing async from these methods.

Also applies to: 292-337


652-674: resolveProjectRoot uses synchronous existsSync on each ancestor directory.

This is blocking I/O on the extension host thread. For typical local filesystems and shallow directory depths this is fine, but could cause hitches on remote/networked workspaces (e.g., VS Code Remote - SSH). If remote workspace support is a goal, consider an async variant using vscode.workspace.fs.stat.

…d UI updates

- Removed the old VSIX file for the extension.
- Added a request ID to manage state updates in the preview host.
- Updated the loading behavior to hide the progress indicator when the webview is ready.
- Improved theme handling and caching logic.
- Enhanced error logging for better debugging during message processing.
- Introduced a retry button in the UI for refreshing the preview.
- Introduced a zoom control feature for the mobile frame in the preview panel, allowing users to zoom in and out.
- Enhanced the mobile frame's responsiveness by adjusting its width and height based on the editor pane size.
- Updated the CSS styles to improve layout and user experience, including new styles for the zoom control buttons and their interactions.
- Implemented JavaScript functionality to manage zoom levels and update the display accordingly.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tools/stac-vscode/src/preview/buildFallback.ts (1)

56-86: Shell command has no timeout — a hung build process blocks the preview indefinitely.

runShellCommand spawns a process without any timeout. If the build command hangs or takes excessively long, this promise never resolves and blocks the preview pipeline.

Proposed fix: add a configurable timeout
 function runShellCommand(
   command: string,
   cwd: string,
   outputChannel: vscode.OutputChannel,
+  timeoutMs = 120_000,
 ): Promise<ShellCommandResult> {
   return new Promise((resolve, reject) => {
     const child = spawn(command, {
       cwd,
       shell: true,
       env: process.env,
     });
+
+    const timer = setTimeout(() => {
+      child.kill();
+      outputChannel.appendLine(`[preview] Build command timed out after ${timeoutMs}ms`);
+      resolve({ exitCode: 124 });
+    }, timeoutMs);

     child.stdout.on('data', (chunk: Buffer | string) => {
       outputChannel.append(chunk.toString());
     });

     child.stderr.on('data', (chunk: Buffer | string) => {
       outputChannel.append(chunk.toString());
     });

     child.on('error', (error) => {
+      clearTimeout(timer);
       reject(error);
     });

     child.on('close', (code) => {
+      clearTimeout(timer);
       resolve({
         exitCode: code ?? 1,
       });
     });
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/src/preview/buildFallback.ts` around lines 56 - 86,
runShellCommand currently spawns a child process with no timeout so a hung build
blocks forever; update runShellCommand to accept a configurable timeout (e.g.
timeoutMs: number | undefined) and start a timer after spawn that, when fired,
kills the child (child.kill()) and resolves/rejects the Promise with a
ShellCommandResult indicating a timeout (non-zero exitCode and a timedOut flag
or error), and ensure you clear the timer on normal 'close' and 'error' events
to avoid leaks; update callers to pass a sensible default timeout or undefined
to preserve behaviour.
tools/stac-vscode/src/preview/jsonGeneration.ts (1)

120-151: Shell command has no timeout — consistent with buildFallback.ts.

Same issue as in buildFallback.ts: the runCommand helper has no timeout. If the Dart runner hangs, the preview pipeline blocks indefinitely. Consider adding a timeout similar to the one in previewManager.ts's runDartCommand.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/src/preview/jsonGeneration.ts` around lines 120 - 151, The
runCommand function lacks a timeout and can hang; update runCommand to accept or
implement a timeout (like previewManager's runDartCommand) so the spawned child
is killed and the promise is rejected if the command exceeds the timeout.
Specifically, in runCommand (in jsonGeneration.ts) start a timer after spawn
that calls child.kill() (or child.kill('SIGKILL') on non-Windows), clears
listeners, rejects with a TimeoutError, and ensure the timer is cleared on child
'close'/'error' to avoid leaks; mirror the same timeout behavior and error
handling used by previewManager.runDartCommand (and consistent with
buildFallback.ts) so callers get a deterministic exit on hangs.
tools/stac-vscode/preview_host/lib/main.dart (2)

101-104: Theme cache comparison uses reference equality, which may miss cache hits.

_themeJson == _lastThemeJson compares Map references, not contents. If the same theme data arrives in a new Map instance (e.g., from a fresh message), the cache is bypassed and the theme is re-parsed. This isn't incorrect but suboptimal.

Consider using mapEquals from package:flutter/foundation.dart or caching by theme name instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/preview_host/lib/main.dart` around lines 101 - 104, The
cache comparison currently uses reference equality between _themeJson and
_lastThemeJson (and _targetPlatform/_lastTargetPlatform) so identical Map
contents in new instances miss cache hits; update the check to compare Map
contents using mapEquals from package:flutter/foundation.dart (or swap to a
name-based cache key) and import that package, i.e., replace the `_themeJson ==
_lastThemeJson` test with `mapEquals(_themeJson, _lastThemeJson)` (keeping the
platform comparison) so `_cachedThemeData` is reused when contents match.

123-125: Silent exception swallowing hides theme parsing errors.

The catch block discards the exception without logging. If StacTheme.fromJson or stacTheme.parse fails, the user gets no feedback about why theming isn't working.

Proposed fix: log the error
     } catch (e) {
+      _log('Failed to build theme: $e');
       return null;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/preview_host/lib/main.dart` around lines 123 - 125, The
catch block currently swallows exceptions when parsing themes (around
StacTheme.fromJson and stacTheme.parse) causing silent failures; update the
catch to log the caught exception (including stack trace or message) via the
existing logger or console to provide diagnostic information, e.g., capture the
exception variable in the catch and call the project's logging facility (or
print) with a clear message that theme parsing failed and include the error
details and stack trace so users/devs can see why theming didn't apply.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/stac-vscode/preview_host/lib/main.dart`:
- Around line 101-104: The cache comparison currently uses reference equality
between _themeJson and _lastThemeJson (and _targetPlatform/_lastTargetPlatform)
so identical Map contents in new instances miss cache hits; update the check to
compare Map contents using mapEquals from package:flutter/foundation.dart (or
swap to a name-based cache key) and import that package, i.e., replace the
`_themeJson == _lastThemeJson` test with `mapEquals(_themeJson, _lastThemeJson)`
(keeping the platform comparison) so `_cachedThemeData` is reused when contents
match.
- Around line 123-125: The catch block currently swallows exceptions when
parsing themes (around StacTheme.fromJson and stacTheme.parse) causing silent
failures; update the catch to log the caught exception (including stack trace or
message) via the existing logger or console to provide diagnostic information,
e.g., capture the exception variable in the catch and call the project's logging
facility (or print) with a clear message that theme parsing failed and include
the error details and stack trace so users/devs can see why theming didn't
apply.

In `@tools/stac-vscode/src/preview/buildFallback.ts`:
- Around line 56-86: runShellCommand currently spawns a child process with no
timeout so a hung build blocks forever; update runShellCommand to accept a
configurable timeout (e.g. timeoutMs: number | undefined) and start a timer
after spawn that, when fired, kills the child (child.kill()) and
resolves/rejects the Promise with a ShellCommandResult indicating a timeout
(non-zero exitCode and a timedOut flag or error), and ensure you clear the timer
on normal 'close' and 'error' events to avoid leaks; update callers to pass a
sensible default timeout or undefined to preserve behaviour.

In `@tools/stac-vscode/src/preview/jsonGeneration.ts`:
- Around line 120-151: The runCommand function lacks a timeout and can hang;
update runCommand to accept or implement a timeout (like previewManager's
runDartCommand) so the spawned child is killed and the promise is rejected if
the command exceeds the timeout. Specifically, in runCommand (in
jsonGeneration.ts) start a timer after spawn that calls child.kill() (or
child.kill('SIGKILL') on non-Windows), clears listeners, rejects with a
TimeoutError, and ensure the timer is cleared on child 'close'/'error' to avoid
leaks; mirror the same timeout behavior and error handling used by
previewManager.runDartCommand (and consistent with buildFallback.ts) so callers
get a deterministic exit on hangs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a21ed3b-51c4-4025-8dcb-ff4eb9eff084

📥 Commits

Reviewing files that changed from the base of the PR and between 60bde9b and 58b1889.

📒 Files selected for processing (8)
  • .gitignore
  • tools/stac-vscode/preview_host/lib/main.dart
  • tools/stac-vscode/src/extension.ts
  • tools/stac-vscode/src/preview/buildFallback.ts
  • tools/stac-vscode/src/preview/jsonGeneration.ts
  • tools/stac-vscode/src/preview/previewHostProcess.ts
  • tools/stac-vscode/src/preview/previewManager.ts
  • tools/stac-vscode/src/preview/previewPanel.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore

}
}

Map<String, dynamic>? _normalize(dynamic raw) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name can be better

…entation, and example app configurations, while adding new parser and network service tests.
@divyanshub024
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
tools/stac-vscode/preview_host/lib/main.dart (1)

279-284: Consider validating parent window existence pattern.

The _post method correctly checks parent != null before posting. However, web.window.parent can return the window itself when there's no parent frame. Consider also checking parent != web.window for robustness in edge cases.

💡 Suggested improvement
   void _post(Map<String, dynamic> payload) {
     final parent = web.window.parent;
-    if (parent != null) {
+    if (parent != null && parent != web.window) {
       parent.postMessage(jsonEncode(payload).toJS, '*'.toJS);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/preview_host/lib/main.dart` around lines 279 - 284, The
_post method currently only checks parent != null before calling
parent.postMessage; update it to also ensure the parent is not the same window
(check parent != web.window) to avoid posting to self when no frame exists.
Locate the _post function and change the guard to something like if (parent !=
null && parent != web.window) before calling
parent.postMessage(jsonEncode(payload).toJS, '*'.toJS) so postMessage is only
invoked on a true parent frame.
tools/stac-vscode/src/preview/previewPanel.ts (1)

466-474: Using '*' for postMessage targetOrigin is intentional but worth documenting.

The '*' targetOrigin is necessary because VS Code's webview port-mapping proxies the iframe through an internal origin that differs from the original URL. The inline comment on line 516-518 explains this for postRenderToFrame, but consider adding a similar brief comment here for consistency.

💡 Suggested documentation
       if (frame.contentWindow) {
         try {
+          // Use '*' because VS Code's webview port-mapping proxies the
+          // iframe through an internal origin.
           frame.contentWindow.postMessage(
             { type: 'stac.preview.setPlatform', platform: platformMap[device] || 'web' },
             '*',
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/src/preview/previewPanel.ts` around lines 466 - 474, Add a
brief inline comment above the postMessage call in previewPanel.ts (around the
platformMap/frame.contentWindow block) explaining that using '*' for the
targetOrigin is intentional because VS Code's webview port-mapping proxies the
iframe through an internal origin differing from the original URL, so a specific
origin cannot be used; reference the postMessage call and the
'stac.preview.setPlatform' message to make the intent clear for future readers.
tools/stac-vscode/src/preview/previewHostProcess.ts (1)

149-160: Consider clearing startupPromise on process error.

If the process emits an error event before waitForHostHealthy resolves/rejects, this.startupPromise may remain set while this.process becomes undefined, potentially causing state inconsistency on the next ensureStarted() call.

💡 Potential improvement

The startupPromise is already cleared in the finally block of ensureStarted() (lines 63-65), so this is handled. However, the error event handler could log more context about the startup phase:

       this.process.on('error', (error) => {
-        this.outputChannel.appendLine(`[preview] Flutter preview host error: ${String(error)}`);
+        this.outputChannel.appendLine(`[preview] Flutter preview host error during startup: ${String(error)}`);
         this.process = undefined;
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/stac-vscode/src/preview/previewHostProcess.ts` around lines 149 - 160,
The process 'error' handler can leave this.startupPromise set if the error
happens before waitForHostHealthy resolves; update the error handler (the
function attached to this.process.on('error')) to clear this.startupPromise (set
to undefined) when an error occurs and enhance the log to include that the error
happened during startup (mentioning waitForHostHealthy/ensureStarted) so callers
know startup failed; keep existing this.process = undefined behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tools/stac-vscode/preview_host/lib/main.dart`:
- Around line 279-284: The _post method currently only checks parent != null
before calling parent.postMessage; update it to also ensure the parent is not
the same window (check parent != web.window) to avoid posting to self when no
frame exists. Locate the _post function and change the guard to something like
if (parent != null && parent != web.window) before calling
parent.postMessage(jsonEncode(payload).toJS, '*'.toJS) so postMessage is only
invoked on a true parent frame.

In `@tools/stac-vscode/src/preview/previewHostProcess.ts`:
- Around line 149-160: The process 'error' handler can leave this.startupPromise
set if the error happens before waitForHostHealthy resolves; update the error
handler (the function attached to this.process.on('error')) to clear
this.startupPromise (set to undefined) when an error occurs and enhance the log
to include that the error happened during startup (mentioning
waitForHostHealthy/ensureStarted) so callers know startup failed; keep existing
this.process = undefined behavior intact.

In `@tools/stac-vscode/src/preview/previewPanel.ts`:
- Around line 466-474: Add a brief inline comment above the postMessage call in
previewPanel.ts (around the platformMap/frame.contentWindow block) explaining
that using '*' for the targetOrigin is intentional because VS Code's webview
port-mapping proxies the iframe through an internal origin differing from the
original URL, so a specific origin cannot be used; reference the postMessage
call and the 'stac.preview.setPlatform' message to make the intent clear for
future readers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cf97ba3-e5e6-40dd-a8f6-f80d0af7a3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 58b1889 and 33fb084.

⛔ Files ignored due to path filters (1)
  • tools/stac-vscode/preview_host/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • tools/stac-vscode/preview_host/lib/main.dart
  • tools/stac-vscode/src/preview/assetServer.ts
  • tools/stac-vscode/src/preview/jsonTransformer.ts
  • tools/stac-vscode/src/preview/previewHostProcess.ts
  • tools/stac-vscode/src/preview/previewManager.ts
  • tools/stac-vscode/src/preview/previewPanel.ts
  • tools/stac-vscode/src/test/preview_assets.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/stac-vscode/src/preview/assetServer.ts

@divyanshub024 divyanshub024 merged commit ea6fadc into dev Mar 6, 2026
8 of 9 checks passed
@divyanshub024 divyanshub024 deleted the dv/stac-vscode-plugin branch March 6, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants