Skip to content

feat(vscode): zmodel markdown preview#2506

Merged
ymc9 merged 5 commits intodevfrom
feat/v3-vscode-preview
Mar 24, 2026
Merged

feat(vscode): zmodel markdown preview#2506
ymc9 merged 5 commits intodevfrom
feat/v3-vscode-preview

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Mar 23, 2026

Summary by CodeRabbit

  • New Features

    • Sign-in and logout for ZenStack accounts; preview and save/export ZModel documentation (side-by-side Markdown).
    • Persistent documentation caching with a command to clear cached docs; cache persists across sessions and devices.
  • Documentation

    • Release notes shown on first extension launch (webview).
  • Commands

    • New commands available in the Command Palette and editor title, with keyboard shortcuts where applicable.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 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 VS Code auth provider, persistent documentation cache, ZModel preview/save/clear commands with API interaction and caching, a release-notes webview, telemetry signin events, and an LSP request to gather imported ZModel URIs; activation registers these components and new commands/menus.

Changes

Cohort / File(s) Summary
Manifest / Contributions
packages/ide/vscode/package.json
Added runtime deps (vscode-uri, zod) and new VS Code contributions: commands, keybindings, and menus for preview, save, clear cache, and logout.
Extension bootstrap
packages/ide/vscode/src/extension/main.ts
Reordered activate() to init telemetry/auth, start language client, instantiate DocumentationCache, register ZModelPreview, and register ReleaseNotesManager; subscriptions updated.
Authentication provider
packages/ide/vscode/src/extension/zenstack-auth-provider.ts
New ZenStackAuthenticationProvider: registers auth provider and URI handler, manages sessions in secret storage, implements OAuth flow with timeout/cancellation, decodes JWT claims, calls telemetry.identify, and exposes logout.
Documentation cache
packages/ide/vscode/src/extension/documentation-cache.ts
New DocumentationCache persisted in globalState with 30-day expiry and extension-version validation; deterministic SHA-512 keys derived from model contents; get/set/clear and optional key sync implemented.
ZModel preview & save
packages/ide/vscode/src/extension/zmodel-preview.ts
New ZModelPreview: preview/save/clear commands; collects active + imported .zmodel files via new LSP request, validates payload with zod, calls API with auth token, uses cache, opens Markdown preview or saves file, and updates zenstack.isMarkdownPreview context.
Release notes
packages/ide/vscode/src/extension/release-notes-manager.ts, packages/language/res/zmodel-v3-preview-release-notes.html
New ReleaseNotesManager shows bundled HTML release notes once per version in retained webview; persists shown flag and enables key sync; added static release notes HTML.
Telemetry
packages/ide/vscode/src/extension/vscode-telemetry.ts
Extended TelemetryEvents with signin events and added identify(userId: string) to send Mixpanel identify tying userId to device id.
Language server
packages/ide/vscode/src/language-server/main.ts
Added LSP request zenstack/getAllImportedZModelURIs and helper eagerLoadAllImports to recursively resolve and eagerly load/import ZModel documents, returning imported URIs and syntax-error status.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: preview zmodel doc #2239: Implements the same DocumentationCache, ZModelPreview, ReleaseNotesManager, ZenStackAuthenticationProvider, and the zenstack/getAllImportedZModelURIs LSP handler.

Poem

🐇 I hopped through code and found the key,

Cached the docs and set them free,
Opened previews side by side,
Sent a ping when users signified,
A tiny hop — the docs stay spry!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 pull request title 'feat(vscode): zmodel markdown preview' directly and clearly summarizes the main feature being added: markdown preview functionality for ZModel files in VS Code.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v3-vscode-preview

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.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 23, 2026

Claude finished @ymc9's task —— View job


PR Review: ZModel Markdown Preview Feature

I'll review this PR focusing on code quality, best practices, potential bugs, performance, security, and test coverage.

Todo List:

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security vulnerabilities
  • Assess performance considerations
  • Review best practices and potential bugs
  • Evaluate test coverage
  • Provide comprehensive feedback

Review Summary

This PR adds a comprehensive markdown preview feature for ZModel files in VS Code, including authentication, documentation caching, and release notes management. The implementation is well-structured with good separation of concerns.

✅ Strengths

  1. Clean Architecture: Good separation of concerns with distinct classes for preview, auth, cache, and release notes
  2. Comprehensive Error Handling: Proper try-catch blocks and user feedback throughout
  3. Performance Optimization: Intelligent caching system with content normalization
  4. User Experience: Progress indicators, keyboard shortcuts, and contextual menus
  5. Security: Proper JWT parsing and authentication flow

⚠️ Security & Privacy Concerns

  1. External API Communication (zmodel-preview.ts:238):

    • Sends ZModel content to external API (https://api.zenstack.dev) for processing
    • Consider adding configuration to allow disabling this feature for privacy-sensitive environments
    • Add clear documentation about data being sent externally
  2. Hard-coded URLs (zenstack-auth-provider.ts:12-13):

    • Production URLs are hard-coded without environment configuration
    • Consider allowing configuration overrides for enterprise deployments
  3. Telemetry Token Placeholder (vscode-telemetry.ts:9):

    • Contains placeholder token that should be replaced before release

🐛 Potential Issues

  1. Authentication Race Conditions (zenstack-auth-provider.ts:140-145):

    setTimeout(() => {
        if (this.pendingAuth) {
            delete this.pendingAuth;
        }
        reject(new Error('Timeout'));
    }, 120000);
    • The timeout doesn't check if auth completed successfully
    • Could reject a promise that was already resolved
  2. Missing Null Checks (zmodel-preview.ts:197-205):

    • Should add null checks for uri.path before file operations
  3. Unsafe Array Access (multiple files):

    • machine-id-utils.ts:37: result.split('IOPlatformUUID')[1]! - could be undefined
    • zenstack-auth-provider.ts:205: parts[1]! - could be undefined

🚀 Performance Considerations

  1. Efficient Caching (documentation-cache.ts:56-64):

    • Good normalization strategy removing whitespace for cache keys
    • 30-day cache duration is reasonable
  2. Batch Processing (zmodel-preview.ts:192-207):

    • Efficiently processes multiple imported files in parallel

📝 Code Quality Issues

  1. Magic Numbers:

    • vscode-telemetry.ts:43: UUID namespace should be a named constant
    • documentation-cache.ts:16: Cache duration should be configurable
  2. Error Message Consistency (zmodel-preview.ts:261):

    • Consider standardizing error message format across the extension
  3. Missing JSDoc (machine-id-utils.ts):

    • Core utility functions lack documentation

❌ Test Coverage

Critical Issue: No test files found in the VS Code extension package. For a feature of this complexity, especially one handling authentication and external API calls, comprehensive tests are essential.

Recommended Test Coverage:

  • Unit tests for each class (DocumentationCache, ZModelPreview, etc.)
  • Integration tests for authentication flow
  • Mock tests for external API calls
  • Error handling scenarios

📋 Specific Recommendations

  1. Add Configuration Options:

    // Allow users to disable external API calls
    const config = vscode.workspace.getConfiguration('zenstack');
    const allowExternalAPI = config.get('allowExternalDocumentation', true);
  2. Improve Error Handling (zenstack-auth-provider.ts:140):

    const timeoutId = setTimeout(() => {
        if (this.pendingAuth && !resolved) {
            this.pendingAuth.reject(new Error('Timeout'));
            delete this.pendingAuth;
        }
    }, 120000);
  3. Add Null Checks (machine-id-utils.ts:37):

    const parts = result.split('IOPlatformUUID');
    if (parts.length < 2) throw new Error('Invalid IOPlatformUUID format');
    return parts[1]!.split('\n')[0]!.replace(/=|\s+|"/gi, '').toLowerCase();
  4. Consider Privacy Documentation:

    • Add clear documentation about what data is sent to external services
    • Consider adding opt-in confirmation for first-time users

🎯 Overall Assessment

This is a well-implemented feature that follows VS Code extension best practices. The main concerns are around test coverage and privacy considerations for the external API integration. The code quality is good overall, with room for improvement in error handling and configuration flexibility.

Priority: Address test coverage and privacy documentation before merging.

Copy link
Copy Markdown
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: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ide/vscode/src/extension/documentation-cache.ts`:
- Around line 42-50: The cache is being opted into VS Code Settings Sync by
calling extensionContext.globalState.setKeysForSync for keys starting with
DocumentationCache.CACHE_PREFIX; remove that opt-in so generated documentation
remains local-only: delete or disable the call to setKeysForSync in
enableCacheSync (and any other place that performs the same
filtering+setKeysForSync logic) and ensure no code path later registers
DocumentationCache.CACHE_PREFIX keys for sync via
extensionContext.globalState.setKeysForSync; keep the keys stored in globalState
but do not add them to the sync set.
- Around line 56-63: The cache key generation currently strips all whitespace
via model.replace(/\s/g, ''), which conflates distinct inputs; update
generateCacheKey so each model is normalized more safely (e.g., normalize line
endings, collapse consecutive whitespace to a single space, and trim) instead of
removing all spaces — for example use model.replace(/\s+/g, ' ').trim() when
building normalizedModels, then continue to sort, JSON.stringify and hash as
before (keep the createHash('sha512') usage and
DocumentationCache.CACHE_PREFIX).

In `@packages/ide/vscode/src/extension/release-notes-manager.ts`:
- Around line 25-32: The code currently sets the zmodelPreviewReleaseNoteKey as
"shown" unconditionally after calling showReleaseNotes(); change this so the key
is only persisted after showReleaseNotes() successfully loads/displays the HTML
(i.e., return/resolve a success boolean or throw on failure from
showReleaseNotes), and only then call
extensionContext.globalState.update(this.zmodelPreviewReleaseNoteKey, true) and
setKeysForSync(...). Apply the same pattern to the other similar block (lines
~39-68) so both usages of showReleaseNotes/showReleaseNotesIfFirstTime only mark
the announcement shown when the HTML load/display succeeded.

In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts`:
- Around line 177-188: In createSessionFromAccessToken, stop passing raw PII
(claims.email) to telemetry.identify; instead use a non-PII stable identifier
such as claims.sub (or a one-way hash of a stable claim) and fall back to a
generated id when sub is missing. Locate parseJWTClaims and the
telemetry.identify(...) call and replace the argument with claims.sub ||
hashedValue || fallbackId, ensuring you still set the account.label/account.id
fields for the session but never use claims.email directly as the telemetry
identifier.
- Around line 87-93: In logoutAllSessions, don't rely on this._sessions (which
may be empty) — first await this.getSessions() to load persisted sessions, check
the returned sessions array length, then remove each session by awaiting
removeSession for each id (avoid forEach(async ...) which doesn't wait); either
use a for...of loop awaiting this.removeSession(s.id) or await
Promise.all(sessions.map(s => this.removeSession(s.id))). After all removals
complete, call vscode.window.showInformationMessage('Successfully logged out of
ZenStack.').
- Around line 114-119: The redirectUrl constructed in the extension (variables
appName and redirectUrl) is embedded raw into signInUrl which causes its
internal query parameters to be treated as top-level params; encode redirectUrl
before adding it to the sign-in query (e.g., use encodeURIComponent or build
signInUrl with URL/URLSearchParams and set redirect_url to the encoded
redirectUrl) so signInUrl (created from AUTH_URL and used in signInUrl/with({
query: ... })) carries the full nested URL correctly; update the code that
creates signInUrl to use the encoded redirect_url value instead of the raw
redirectUrl.
- Around line 204-210: The JWT payload decoding currently treats the segment as
plain base64; update the logic in zenstack-auth-provider (the code that reads
parts[1] into payload, constructs paddedPayload, uses atob and JSON.parse) to
first convert base64url to base64 (replace '-' with '+' and '_' with '/'), then
add the '=' padding and decode (or use a base64 decoder that accepts base64url),
before JSON.parse the result so tokens with '-' or '_' decode correctly.

In `@packages/ide/vscode/src/extension/zmodel-preview.ts`:
- Around line 193-198: Inside the async callback passed to importedURIs.map,
stop reconstructing the URI with vscode.Uri.file(...) and reading from
workspace.fs; instead use vscode.workspace.openTextDocument(uri) and call
.getText() on the returned TextDocument so remote schemes and unsaved editor
edits are preserved. Update the logic in the importedURIs.map async function
(the block currently using vscode.Uri.file, workspace.fs.readFile,
Buffer.from(...).toString('utf8')) to open the document via
workspace.openTextDocument(uri), obtain the text via document.getText(), trim
it, and return that as the content while keeping the original uri/path
representation (do not lose the URI scheme).
- Around line 120-125: The fetch inside the vscode.window.withProgress call
needs an AbortSignal timeout so it can't hang indefinitely: create an
AbortController via AbortSignal.timeout(timeoutMs) (e.g., 30_000 ms) and pass
its signal to fetch(), handle aborts by catching the thrown
DOMException/AbortError and cleaning up the progress UI/error reporting, and
ensure any downstream code (the response processing in the same async block)
checks for signal/aborted state; update the code around
vscode.window.withProgress and the fetch(...) call to use the controller's
signal and to handle timeout errors gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc8e4652-7c2f-4ceb-9d75-bf9f757ec65d

📥 Commits

Reviewing files that changed from the base of the PR and between cb49667 and 178a08b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/ide/vscode/package.json
  • packages/ide/vscode/src/extension/documentation-cache.ts
  • packages/ide/vscode/src/extension/main.ts
  • packages/ide/vscode/src/extension/release-notes-manager.ts
  • packages/ide/vscode/src/extension/vscode-telemetry.ts
  • packages/ide/vscode/src/extension/zenstack-auth-provider.ts
  • packages/ide/vscode/src/extension/zmodel-preview.ts

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 24, 2026

Claude finished @ymc9's task —— View job


PR Review: ZModel Markdown Preview Feature

I'll review this PR focusing on code quality, best practices, potential bugs, performance, security, and test coverage.

Todo List:

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security vulnerabilities
  • Assess performance considerations
  • Review best practices and potential bugs
  • Evaluate test coverage
  • Provide comprehensive feedback

Review Summary

This PR adds a comprehensive markdown preview feature for ZModel files in VS Code, including authentication, documentation caching, and release notes management. The implementation is well-structured with good separation of concerns.

✅ Strengths

  1. Clean Architecture: Good separation of concerns with distinct classes for preview, auth, cache, and release notes
  2. Comprehensive Error Handling: Proper try-catch blocks and user feedback throughout
  3. Performance Optimization: Intelligent caching system with content normalization
  4. User Experience: Progress indicators, keyboard shortcuts, and contextual menus
  5. Security: Proper JWT parsing and authentication flow

⚠️ Security & Privacy Concerns

  1. External API Communication (zmodel-preview.ts:238):

    • Sends ZModel content to external API (https://api.zenstack.dev) for processing
    • Consider adding configuration to allow disabling this feature for privacy-sensitive environments
    • Add clear documentation about data being sent externally
  2. Hard-coded URLs (zenstack-auth-provider.ts:12-13):

    • Production URLs are hard-coded without environment configuration
    • Consider allowing configuration overrides for enterprise deployments
  3. Telemetry Token Placeholder (vscode-telemetry.ts:9):

    • Contains placeholder token that should be replaced before release

🐛 Potential Issues

  1. Authentication Race Conditions (zenstack-auth-provider.ts:140-145):

    setTimeout(() => {
        if (this.pendingAuth) {
            delete this.pendingAuth;
        }
        reject(new Error('Timeout'));
    }, 120000);
    • The timeout doesn't check if auth completed successfully
    • Could reject a promise that was already resolved
  2. Missing Null Checks (zmodel-preview.ts:197-205):

    • Should add null checks for uri.path before file operations
  3. Unsafe Array Access (multiple files):

    • machine-id-utils.ts:37: result.split('IOPlatformUUID')[1]! - could be undefined
    • zenstack-auth-provider.ts:205: parts[1]! - could be undefined

🚀 Performance Considerations

  1. Efficient Caching (documentation-cache.ts:56-64):

    • Good normalization strategy removing whitespace for cache keys
    • 30-day cache duration is reasonable
  2. Batch Processing (zmodel-preview.ts:192-207):

    • Efficiently processes multiple imported files in parallel

📝 Code Quality Issues

  1. Magic Numbers:

    • vscode-telemetry.ts:43: UUID namespace should be a named constant
    • documentation-cache.ts:16: Cache duration should be configurable
  2. Error Message Consistency (zmodel-preview.ts:261):

    • Consider standardizing error message format across the extension
  3. Missing JSDoc (machine-id-utils.ts):

    • Core utility functions lack documentation

❌ Test Coverage

Critical Issue: No test files found in the VS Code extension package. For a feature of this complexity, especially one handling authentication and external API calls, comprehensive tests are essential.

Recommended Test Coverage:

  • Unit tests for each class (DocumentationCache, ZModelPreview, etc.)
  • Integration tests for authentication flow
  • Mock tests for external API calls
  • Error handling scenarios

📋 Specific Recommendations

  1. Add Configuration Options:

    // Allow users to disable external API calls
    const config = vscode.workspace.getConfiguration('zenstack');
    const allowExternalAPI = config.get('allowExternalDocumentation', true);
  2. Improve Error Handling (zenstack-auth-provider.ts:140):

    const timeoutId = setTimeout(() => {
        if (this.pendingAuth && !resolved) {
            this.pendingAuth.reject(new Error('Timeout'));
            delete this.pendingAuth;
        }
    }, 120000);
  3. Add Null Checks (machine-id-utils.ts:37):

    const parts = result.split('IOPlatformUUID');
    if (parts.length < 2) throw new Error('Invalid IOPlatformUUID format');
    return parts[1]!.split('\n')[0]!.replace(/=|\s+|"/gi, '').toLowerCase();
  4. Consider Privacy Documentation:

    • Add clear documentation about what data is sent to external services
    • Consider adding opt-in confirmation for first-time users

🎯 Overall Assessment

This is a well-implemented feature that follows VS Code extension best practices. The main concerns are around test coverage and privacy considerations for the external API integration. The code quality is good overall, with room for improvement in error handling and configuration flexibility.

Priority: Address test coverage and privacy documentation before merging.


Copy link
Copy Markdown
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

🧹 Nitpick comments (3)
packages/ide/vscode/src/language-server/main.ts (3)

49-56: Use named constant for diagnostic severity.

The magic number 1 represents DiagnosticSeverity.Error in LSP. Using the named constant improves readability and reduces the chance of errors.

Proposed fix
+import { DiagnosticSeverity } from 'vscode-languageserver/node.js';
             if (
                 doc.parseResult.lexerErrors.length > 0 ||
                 doc.parseResult.parserErrors.length > 0 ||
-                doc.diagnostics?.some((e) => e.severity === 1)
+                doc.diagnostics?.some((e) => e.severity === DiagnosticSeverity.Error)
             ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/language-server/main.ts` around lines 49 - 56, The
code in main.ts checks diagnostics with a magic number `1` to represent error
severity; replace that literal with the LSP named constant (e.g.,
DiagnosticSeverity.Error) to improve readability: import DiagnosticSeverity from
the language server/LSP module used in this project and update the predicate
doc.diagnostics?.some((e) => e.severity === 1) to compare against
DiagnosticSeverity.Error; ensure the import (or fully-qualified enum reference)
is added near other imports so the symbol is available where the check occurs.

46-57: Consider reusing already-fetched documents instead of re-fetching.

Lines 47-48 call getOrCreateDocument again for URIs that were already loaded in importedDocuments (line 37-39). While functionally correct (likely returns cached documents), this is redundant. Consider reusing the already-fetched document collection.

Proposed refactor
+        const allDocuments = [document, ...importedDocuments];
+
         // build the document together with standard library, plugin modules, and imported documents
-        await shared.workspace.DocumentBuilder.build([document, ...importedDocuments], {
+        await shared.workspace.DocumentBuilder.build(allDocuments, {
             validation: true,
         });

         let hasSyntaxErrors = false;
-        for (const docUri of [uri, ...importedURIs]) {
-            const doc = await langiumDocuments.getOrCreateDocument(docUri);
+        for (const doc of allDocuments) {
             if (
                 doc.parseResult.lexerErrors.length > 0 ||
                 doc.parseResult.parserErrors.length > 0 ||
                 doc.diagnostics?.some((e) => e.severity === 1)
             ) {
                 hasSyntaxErrors = true;
                 break;
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/language-server/main.ts` around lines 46 - 57, The
loop that sets hasSyntaxErrors re-fetches documents via
langiumDocuments.getOrCreateDocument for URIs already loaded into
importedDocuments; update the loop to first look up each doc in the existing
importedDocuments collection (by matching uri and entries in importedURIs) and
only call langiumDocuments.getOrCreateDocument for URIs not present, then
perform the same lexer/parser/diagnostic checks on the retrieved document;
reference the variables uri, importedURIs, importedDocuments and the function
langiumDocuments.getOrCreateDocument and ensure the logic still breaks early
when a syntax error is found.

87-89: Use strict equality for string comparison.

Line 88 uses loose equality != for comparing strings. Prefer strict equality !== for consistency and to avoid type coercion surprises.

Proposed fix
     return Array.from(uris)
-        .filter((x) => uriString != x)
+        .filter((x) => uriString !== x)
         .map((e) => URI.parse(e));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/language-server/main.ts` around lines 87 - 89, The
filter uses loose inequality when comparing uriString to each item
(Array.from(uris).filter((x) => uriString != x)), so change the comparison to
strict inequality (uriString !== x) to avoid type-coercion issues; keep the rest
of the expression (the map to URI.parse) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ide/vscode/src/language-server/main.ts`:
- Around line 59-62: The returned importedURIs must preserve the shape the
client expects (an object with a .path) instead of converting to a plain string;
in the code that builds the return (the importedURIs returned from
eagerLoadAllImports in main.ts), map each URI to an object with a path property
(e.g., importedURIs.map(uri => ({ path: uri.path }))) or else keep the original
parsed-URI plain objects and update the TypeScript assertion, so the client code
at zmodel-preview.ts:195 (which calls vscode.Uri.file(uri.path)) continues to
work; update the returned value where importedURIs is returned to use this
mapped shape.

---

Nitpick comments:
In `@packages/ide/vscode/src/language-server/main.ts`:
- Around line 49-56: The code in main.ts checks diagnostics with a magic number
`1` to represent error severity; replace that literal with the LSP named
constant (e.g., DiagnosticSeverity.Error) to improve readability: import
DiagnosticSeverity from the language server/LSP module used in this project and
update the predicate doc.diagnostics?.some((e) => e.severity === 1) to compare
against DiagnosticSeverity.Error; ensure the import (or fully-qualified enum
reference) is added near other imports so the symbol is available where the
check occurs.
- Around line 46-57: The loop that sets hasSyntaxErrors re-fetches documents via
langiumDocuments.getOrCreateDocument for URIs already loaded into
importedDocuments; update the loop to first look up each doc in the existing
importedDocuments collection (by matching uri and entries in importedURIs) and
only call langiumDocuments.getOrCreateDocument for URIs not present, then
perform the same lexer/parser/diagnostic checks on the retrieved document;
reference the variables uri, importedURIs, importedDocuments and the function
langiumDocuments.getOrCreateDocument and ensure the logic still breaks early
when a syntax error is found.
- Around line 87-89: The filter uses loose inequality when comparing uriString
to each item (Array.from(uris).filter((x) => uriString != x)), so change the
comparison to strict inequality (uriString !== x) to avoid type-coercion issues;
keep the rest of the expression (the map to URI.parse) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6fe15bb4-1917-47a1-8819-88a6790f4ef8

📥 Commits

Reviewing files that changed from the base of the PR and between 178a08b and e07b57a.

📒 Files selected for processing (2)
  • packages/ide/vscode/src/extension/zenstack-auth-provider.ts
  • packages/ide/vscode/src/language-server/main.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ide/vscode/src/extension/zenstack-auth-provider.ts

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 24, 2026

Claude finished @ymc9's task —— View job


PR Review: ZModel Markdown Preview Feature

I'll review this PR focusing on code quality, best practices, potential bugs, performance, security, and test coverage.

Todo List:

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security vulnerabilities and privacy concerns
  • Assess performance considerations
  • Review best practices and potential bugs
  • Evaluate test coverage
  • Provide comprehensive feedback

Review Summary

This PR adds a comprehensive markdown preview feature for ZModel files in VS Code, including authentication, documentation caching, and release notes management. The implementation is well-structured with good separation of concerns.

✅ Strengths

  1. Clean Architecture: Good separation of concerns with distinct classes for preview, auth, cache, and release notes
  2. Comprehensive Error Handling: Proper try-catch blocks and user feedback throughout
  3. Performance Optimization: Intelligent caching system with content normalization
  4. User Experience: Progress indicators, keyboard shortcuts, and contextual menus
  5. Security: Proper JWT parsing and authentication flow

⚠️ Security & Privacy Concerns

  1. External API Communication (zmodel-preview.ts:238):

    • Sends ZModel content to external API (https://api.zenstack.dev) for processing
    • Consider adding configuration to allow disabling this feature for privacy-sensitive environments
    • Add clear documentation about data being sent externally
  2. Hard-coded URLs (zenstack-auth-provider.ts:12-13):

    • Production URLs are hard-coded without environment configuration
    • Consider allowing configuration overrides for enterprise deployments
  3. Telemetry Token Placeholder (vscode-telemetry.ts:9):

    • Contains placeholder token that should be replaced before release

🐛 Potential Issues

  1. Authentication Race Conditions (zenstack-auth-provider.ts:140-145):

    setTimeout(() => {
        if (this.pendingAuth) {
            delete this.pendingAuth;
        }
        reject(new Error('Timeout'));
    }, 120000);
    • The timeout doesn't check if auth completed successfully
    • Could reject a promise that was already resolved
  2. Missing Null Checks (zmodel-preview.ts:197-205):

    • Should add null checks for uri.path before file operations
  3. Unsafe Array Access (multiple files):

    • machine-id-utils.ts:37: result.split('IOPlatformUUID')[1]! - could be undefined
    • zenstack-auth-provider.ts:205: parts[1]! - could be undefined

🚀 Performance Considerations

  1. Efficient Caching (documentation-cache.ts:56-64):

    • Good normalization strategy removing whitespace for cache keys
    • 30-day cache duration is reasonable
  2. Batch Processing (zmodel-preview.ts:192-207):

    • Efficiently processes multiple imported files in parallel

📝 Code Quality Issues

  1. Magic Numbers:

    • vscode-telemetry.ts:43: UUID namespace should be a named constant
    • documentation-cache.ts:16: Cache duration should be configurable
  2. Error Message Consistency (zmodel-preview.ts:261):

    • Consider standardizing error message format across the extension
  3. Missing JSDoc (machine-id-utils.ts):

    • Core utility functions lack documentation

❌ Test Coverage

Critical Issue: No test files found in the VS Code extension package. For a feature of this complexity, especially one handling authentication and external API calls, comprehensive tests are essential.

Recommended Test Coverage:

  • Unit tests for each class (DocumentationCache, ZModelPreview, etc.)
  • Integration tests for authentication flow
  • Mock tests for external API calls
  • Error handling scenarios

📋 Specific Recommendations

  1. Add Configuration Options:

    // Allow users to disable external API calls
    const config = vscode.workspace.getConfiguration('zenstack');
    const allowExternalAPI = config.get('allowExternalDocumentation', true);
  2. Improve Error Handling (zenstack-auth-provider.ts:140):

    const timeoutId = setTimeout(() => {
        if (this.pendingAuth && !resolved) {
            this.pendingAuth.reject(new Error('Timeout'));
            delete this.pendingAuth;
        }
    }, 120000);
  3. Add Null Checks (machine-id-utils.ts:37):

    const parts = result.split('IOPlatformUUID');
    if (parts.length < 2) throw new Error('Invalid IOPlatformUUID format');
    return parts[1]!.split('\n')[0]!.replace(/=|\s+|"/gi, '').toLowerCase();
  4. Consider Privacy Documentation:

    • Add clear documentation about what data is sent to external services
    • Consider adding opt-in confirmation for first-time users

🎯 Overall Assessment

This is a well-implemented feature that follows VS Code extension best practices. The main concerns are around test coverage and privacy considerations for the external API integration. The code quality is good overall, with room for improvement in error handling and configuration flexibility.

Priority: Address test coverage and privacy documentation before merging.


Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
packages/ide/vscode/src/extension/release-notes-manager.ts (1)

25-33: ⚠️ Potential issue | 🟡 Minor

Only mark the announcement as shown after the HTML loads successfully.

Since showReleaseNotes() catches errors internally and doesn't propagate failure, the flag on Lines 30-32 will be set even when showing the release notes fails. This prevents users from ever seeing the announcement if the first attempt fails.

,

🛡️ Proposed fix: return success status from showReleaseNotes
     async showReleaseNotesIfFirstTime(): Promise<void> {
         // Show release notes if this is the first time activating this version
         if (!this.extensionContext.globalState.get(this.zmodelPreviewReleaseNoteKey)) {
-            await this.showReleaseNotes();
-            // Update the stored version to prevent showing again
-            await this.extensionContext.globalState.update(this.zmodelPreviewReleaseNoteKey, true);
-            // Add this key to sync keys for cross-machine synchronization
-            this.extensionContext.globalState.setKeysForSync([this.zmodelPreviewReleaseNoteKey]);
+            const success = await this.showReleaseNotes();
+            if (success) {
+                // Update the stored version to prevent showing again
+                await this.extensionContext.globalState.update(this.zmodelPreviewReleaseNoteKey, true);
+                // Add this key to sync keys for cross-machine synchronization
+                this.extensionContext.globalState.setKeysForSync([this.zmodelPreviewReleaseNoteKey]);
+            }
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/release-notes-manager.ts` around lines 25 -
33, The current flow in showReleaseNotesIfFirstTime sets the
zmodelPreviewReleaseNoteKey flag unconditionally after calling
showReleaseNotes(), which can hide the announcement if showReleaseNotes() failed
internally; change the call so that showReleaseNotes() returns a success boolean
(or rethrow errors) and only call extensionContext.globalState.update(...) and
setKeysForSync(...) when that success is true (i.e., after the HTML has loaded
successfully), updating showReleaseNotes implementation to resolve true on
success and false or throw on failure so showReleaseNotesIfFirstTime can rely on
its result.
🧹 Nitpick comments (3)
packages/ide/vscode/src/extension/release-notes-manager.ts (3)

54-57: Consider tracking panel for proper disposal.

The webview panel isn't stored, so it can't be disposed when the extension deactivates. Also, retainContextWhenHidden: true keeps the webview in memory even when hidden—this may be unnecessary for a static release notes page.

♻️ Proposed fix
 export class ReleaseNotesManager implements vscode.Disposable {
     private extensionContext: vscode.ExtensionContext;
     private readonly zmodelPreviewReleaseNoteKey = 'zmodel-v3-preview-release-note-shown';
+    private panel: vscode.WebviewPanel | undefined;

In showReleaseNotes():

-            const panel = vscode.window.createWebviewPanel(
+            this.panel = vscode.window.createWebviewPanel(
                 'ZenstackReleaseNotes',
                 'ZenStack - New Feature Announcement!',
                 vscode.ViewColumn.One,
                 {
                     enableScripts: true,
-                    retainContextWhenHidden: true,
                 },
             );

-            panel.webview.html = htmlContent;
+            this.panel.webview.html = htmlContent;

             // Optional: Close the panel when user clicks outside or after some time
-            panel.onDidDispose(() => {
-                // Panel disposed
+            this.panel.onDidDispose(() => {
+                this.panel = undefined;
             });

In dispose():

     dispose(): void {
-        // Any cleanup if needed
+        this.panel?.dispose();
     }

Also applies to: 74-76

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

In `@packages/ide/vscode/src/extension/release-notes-manager.ts` around lines 54 -
57, The webview panel created in showReleaseNotes() isn't stored and
retainContextWhenHidden is true; update showReleaseNotes() to assign the created
WebviewPanel to a module/class-level variable (e.g., this.panel or
releaseNotesPanel) and set retainContextWhenHidden to false for a static release
notes page, then implement/augment dispose() to check and call panel.dispose()
and null out the reference so the panel is properly cleaned up when the
extension deactivates; apply the same pattern to the other panel creation at
lines 74-76.

39-68: Return success status to enable conditional flag-setting.

The method catches errors and logs them but returns void, making it impossible for callers to know if the operation succeeded. Return a boolean to allow showReleaseNotesIfFirstTime() to conditionally persist the "shown" flag.

♻️ Proposed fix
-    async showReleaseNotes(): Promise<void> {
+    async showReleaseNotes(): Promise<boolean> {
         try {
             // Read the release notes HTML file
             const releaseNotesPath = vscode.Uri.joinPath(
                 this.extensionContext.extensionUri,
                 'res/zmodel-v3-preview-release-notes.html',
             );

             const htmlBytes = await vscode.workspace.fs.readFile(releaseNotesPath);
             const htmlContent = Buffer.from(htmlBytes).toString('utf8');
             // Create and show the release notes webview
             const panel = vscode.window.createWebviewPanel(
                 'ZenstackReleaseNotes',
                 'ZenStack - New Feature Announcement!',
                 vscode.ViewColumn.One,
                 {
                     enableScripts: true,
                     retainContextWhenHidden: true,
                 },
             );

             panel.webview.html = htmlContent;

             // Optional: Close the panel when user clicks outside or after some time
             panel.onDidDispose(() => {
                 // Panel disposed
             });
+            return true;
         } catch (error) {
             console.error('Error showing release notes:', error);
+            return false;
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/release-notes-manager.ts` around lines 39 -
68, The showReleaseNotes method currently returns void so callers cannot know if
it succeeded; change its signature to async showReleaseNotes():
Promise<boolean>, return true after successfully creating and displaying the
webview (e.g., after panel.webview.html = htmlContent) and return false in the
catch block (or rethrow if desired), and update callers such as
showReleaseNotesIfFirstTime to await the boolean result and only persist the
"shown" flag when true; ensure references to this.extensionContext and panel
lifecycle remain unchanged.

18-20: Floating promise: async method called without awaiting or error handling.

showReleaseNotesIfFirstTime() is async but called without await or .catch(). Any unhandled rejection will be silently lost. Consider adding explicit error handling.

♻️ Proposed fix
-    initialize(): void {
-        this.showReleaseNotesIfFirstTime();
+    initialize(): void {
+        this.showReleaseNotesIfFirstTime().catch((error) => {
+            console.error('Failed to show release notes:', error);
+        });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/release-notes-manager.ts` around lines 18 -
20, The initialize() method calls the async showReleaseNotesIfFirstTime()
without awaiting or handling rejections; update initialize() to either be async
and await showReleaseNotesIfFirstTime() or explicitly attach .catch(...) to the
returned promise to handle errors (e.g., log via the extension logger or
vscode.window.showErrorMessage). Ensure you reference initialize() and
showReleaseNotesIfFirstTime() so the change is applied where the call occurs and
that any thrown errors are surfaced instead of becoming unhandled rejections.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/ide/vscode/src/extension/release-notes-manager.ts`:
- Around line 25-33: The current flow in showReleaseNotesIfFirstTime sets the
zmodelPreviewReleaseNoteKey flag unconditionally after calling
showReleaseNotes(), which can hide the announcement if showReleaseNotes() failed
internally; change the call so that showReleaseNotes() returns a success boolean
(or rethrow errors) and only call extensionContext.globalState.update(...) and
setKeysForSync(...) when that success is true (i.e., after the HTML has loaded
successfully), updating showReleaseNotes implementation to resolve true on
success and false or throw on failure so showReleaseNotesIfFirstTime can rely on
its result.

---

Nitpick comments:
In `@packages/ide/vscode/src/extension/release-notes-manager.ts`:
- Around line 54-57: The webview panel created in showReleaseNotes() isn't
stored and retainContextWhenHidden is true; update showReleaseNotes() to assign
the created WebviewPanel to a module/class-level variable (e.g., this.panel or
releaseNotesPanel) and set retainContextWhenHidden to false for a static release
notes page, then implement/augment dispose() to check and call panel.dispose()
and null out the reference so the panel is properly cleaned up when the
extension deactivates; apply the same pattern to the other panel creation at
lines 74-76.
- Around line 39-68: The showReleaseNotes method currently returns void so
callers cannot know if it succeeded; change its signature to async
showReleaseNotes(): Promise<boolean>, return true after successfully creating
and displaying the webview (e.g., after panel.webview.html = htmlContent) and
return false in the catch block (or rethrow if desired), and update callers such
as showReleaseNotesIfFirstTime to await the boolean result and only persist the
"shown" flag when true; ensure references to this.extensionContext and panel
lifecycle remain unchanged.
- Around line 18-20: The initialize() method calls the async
showReleaseNotesIfFirstTime() without awaiting or handling rejections; update
initialize() to either be async and await showReleaseNotesIfFirstTime() or
explicitly attach .catch(...) to the returned promise to handle errors (e.g.,
log via the extension logger or vscode.window.showErrorMessage). Ensure you
reference initialize() and showReleaseNotesIfFirstTime() so the change is
applied where the call occurs and that any thrown errors are surfaced instead of
becoming unhandled rejections.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ee3232e-3430-4f7e-b237-e084228435a0

📥 Commits

Reviewing files that changed from the base of the PR and between e07b57a and bc64be7.

📒 Files selected for processing (2)
  • packages/ide/vscode/src/extension/release-notes-manager.ts
  • packages/language/res/zmodel-v3-preview-release-notes.html
✅ Files skipped from review due to trivial changes (1)
  • packages/language/res/zmodel-v3-preview-release-notes.html

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 24, 2026

Claude finished @ymc9's task —— View job


PR Review: ZModel Markdown Preview Feature

I'll review this PR focusing on code quality, best practices, potential bugs, performance, security, and test coverage.

Todo List:

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security vulnerabilities and privacy concerns
  • Assess performance considerations
  • Review best practices and potential bugs
  • Evaluate test coverage
  • Provide comprehensive feedback

Review Summary

This PR adds a comprehensive markdown preview feature for ZModel files in VS Code, including authentication, documentation caching, and release notes management. The implementation is well-structured with good separation of concerns.

✅ Strengths

  1. Clean Architecture: Good separation of concerns with distinct classes for preview, auth, cache, and release notes
  2. Comprehensive Error Handling: Proper try-catch blocks and user feedback throughout
  3. Performance Optimization: Intelligent caching system with content normalization
  4. User Experience: Progress indicators, keyboard shortcuts, and contextual menus
  5. Security: Proper JWT parsing and authentication flow

⚠️ Security & Privacy Concerns

  1. External API Communication (zmodel-preview.ts:238):

    • Sends ZModel content to external API (https://api.zenstack.dev) for processing
    • Consider adding configuration to allow disabling this feature for privacy-sensitive environments
    • Add clear documentation about data being sent externally
  2. Hard-coded URLs (zenstack-auth-provider.ts:12-13):

    • Production URLs are hard-coded without environment configuration
    • Consider allowing configuration overrides for enterprise deployments
  3. Telemetry Token Placeholder (vscode-telemetry.ts:9):

    • Contains placeholder token that should be replaced before release

🐛 Potential Issues

  1. Authentication Race Conditions (zenstack-auth-provider.ts:140-145):

    setTimeout(() => {
        if (this.pendingAuth) {
            delete this.pendingAuth;
        }
        reject(new Error('Timeout'));
    }, 120000);
    • The timeout doesn't check if auth completed successfully
    • Could reject a promise that was already resolved
  2. Missing Null Checks (zmodel-preview.ts:197-205):

    • Should add null checks for uri.path before file operations
  3. Unsafe Array Access (multiple files):

    • machine-id-utils.ts:37: result.split('IOPlatformUUID')[1]! - could be undefined
    • zenstack-auth-provider.ts:205: parts[1]! - could be undefined

🚀 Performance Considerations

  1. Efficient Caching (documentation-cache.ts:56-64):

    • Good normalization strategy removing whitespace for cache keys
    • 30-day cache duration is reasonable
  2. Batch Processing (zmodel-preview.ts:192-207):

    • Efficiently processes multiple imported files in parallel

📝 Code Quality Issues

  1. Magic Numbers:

    • vscode-telemetry.ts:43: UUID namespace should be a named constant
    • documentation-cache.ts:16: Cache duration should be configurable
  2. Error Message Consistency (zmodel-preview.ts:261):

    • Consider standardizing error message format across the extension
  3. Missing JSDoc (machine-id-utils.ts):

    • Core utility functions lack documentation

❌ Test Coverage

Critical Issue: No test files found in the VS Code extension package. For a feature of this complexity, especially one handling authentication and external API calls, comprehensive tests are essential.

Recommended Test Coverage:

  • Unit tests for each class (DocumentationCache, ZModelPreview, etc.)
  • Integration tests for authentication flow
  • Mock tests for external API calls
  • Error handling scenarios

📋 Specific Recommendations

  1. Add Configuration Options:

    // Allow users to disable external API calls
    const config = vscode.workspace.getConfiguration('zenstack');
    const allowExternalAPI = config.get('allowExternalDocumentation', true);
  2. Improve Error Handling (zenstack-auth-provider.ts:140):

    const timeoutId = setTimeout(() => {
        if (this.pendingAuth && !resolved) {
            this.pendingAuth.reject(new Error('Timeout'));
            delete this.pendingAuth;
        }
    }, 120000);
  3. Add Null Checks (machine-id-utils.ts:37):

    const parts = result.split('IOPlatformUUID');
    if (parts.length < 2) throw new Error('Invalid IOPlatformUUID format');
    return parts[1]!.split('\n')[0]!.replace(/=|\s+|"/gi, '').toLowerCase();
  4. Consider Privacy Documentation:

    • Add clear documentation about what data is sent to external services
    • Consider adding opt-in confirmation for first-time users

🎯 Overall Assessment

This is a well-implemented feature that follows VS Code extension best practices. The main concerns are around test coverage and privacy considerations for the external API integration. The code quality is good overall, with room for improvement in error handling and configuration flexibility.

Priority: Address test coverage and privacy documentation before merging.


Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
packages/ide/vscode/src/language-server/main.ts (1)

58-61: ⚠️ Potential issue | 🔴 Critical

URI serialization will break client code that expects .path property.

The importedURIs array contains Langium URI objects. When serialized over JSON-RPC, vscode-uri's toJSON() returns the URI as a string (e.g., "file:///path/to/file.zmodel"), not an object. The client at zmodel-preview.ts:195 calls vscode.Uri.file(uri.path), but strings have no .path property, resulting in undefined.

Explicitly map to objects with the shape the client expects:

🐛 Proposed fix
         return {
             hasSyntaxErrors,
-            importedURIs,
+            importedURIs: importedURIs.map((uri) => ({ path: uri.path })),
         };

,

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

In `@packages/ide/vscode/src/language-server/main.ts` around lines 58 - 61, The
returned importedURIs currently contains Langium URI objects which serialize to
strings and break client code that expects a .path property (see client use in
zmodel-preview.ts:195); change the return to map importedURIs to plain objects
with the shape the client expects (e.g., importedURIs.map(u => ({ path: u.path
}))) so the JSON-RPC payload includes a .path string; update the return in the
function that returns { hasSyntaxErrors, importedURIs } to return the mapped
array instead.
🧹 Nitpick comments (1)
packages/ide/vscode/src/language-server/main.ts (1)

87-87: Prefer strict equality (!==) over loose equality (!=).

Both operands are strings so this is functionally correct, but strict equality is the idiomatic choice in TypeScript.

-        .filter((x) => uriString != x)
+        .filter((x) => uriString !== x)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/language-server/main.ts` at line 87, The filter
callback currently uses loose inequality (uriString != x); change it to strict
inequality (uriString !== x) to use TypeScript's idiomatic strict
comparison—update the arrow function passed to filter (the expression using
uriString and x) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/ide/vscode/src/language-server/main.ts`:
- Around line 58-61: The returned importedURIs currently contains Langium URI
objects which serialize to strings and break client code that expects a .path
property (see client use in zmodel-preview.ts:195); change the return to map
importedURIs to plain objects with the shape the client expects (e.g.,
importedURIs.map(u => ({ path: u.path }))) so the JSON-RPC payload includes a
.path string; update the return in the function that returns { hasSyntaxErrors,
importedURIs } to return the mapped array instead.

---

Nitpick comments:
In `@packages/ide/vscode/src/language-server/main.ts`:
- Line 87: The filter callback currently uses loose inequality (uriString != x);
change it to strict inequality (uriString !== x) to use TypeScript's idiomatic
strict comparison—update the arrow function passed to filter (the expression
using uriString and x) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 791c3ce0-bd49-4fc6-9cda-8e55f4767d77

📥 Commits

Reviewing files that changed from the base of the PR and between bc64be7 and 6707a71.

📒 Files selected for processing (1)
  • packages/ide/vscode/src/language-server/main.ts

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 24, 2026

Claude finished @ymc9's task —— View job


PR Review: ZModel Markdown Preview Feature

I'll review this PR focusing on code quality, best practices, potential bugs, performance, security, and test coverage.

Todo List:

  • Read and analyze all changed files
  • Review code quality and architecture
  • Check for security vulnerabilities and privacy concerns
  • Assess performance considerations
  • Review best practices and potential bugs
  • Evaluate test coverage
  • Provide comprehensive feedback

Review Summary

This PR adds a comprehensive markdown preview feature for ZModel files in VS Code, including authentication, documentation caching, and release notes management. The implementation is well-structured with good separation of concerns.

✅ Strengths

  1. Clean Architecture: Good separation of concerns with distinct classes for preview, auth, cache, and release notes
  2. Comprehensive Error Handling: Proper try-catch blocks and user feedback throughout
  3. Performance Optimization: Intelligent caching system with content normalization
  4. User Experience: Progress indicators, keyboard shortcuts, and contextual menus
  5. Security: Proper JWT parsing and authentication flow

⚠️ Security & Privacy Concerns

  1. External API Communication (zmodel-preview.ts:238):

    • Sends ZModel content to external API (https://api.zenstack.dev) for processing
    • Consider adding configuration to allow disabling this feature for privacy-sensitive environments
    • Add clear documentation about data being sent externally
  2. Hard-coded URLs (zenstack-auth-provider.ts:12-13):

    • Production URLs are hard-coded without environment configuration
    • Consider allowing configuration overrides for enterprise deployments
  3. Telemetry Token Placeholder (vscode-telemetry.ts:9):

    • Contains placeholder token that should be replaced before release

🐛 Potential Issues

  1. Authentication Race Conditions (zenstack-auth-provider.ts:140-145):

    setTimeout(() => {
        if (this.pendingAuth) {
            delete this.pendingAuth;
        }
        reject(new Error('Timeout'));
    }, 120000);
    • The timeout doesn't check if auth completed successfully
    • Could reject a promise that was already resolved
  2. Missing Null Checks (zmodel-preview.ts:197-205):

    • Should add null checks for uri.path before file operations
  3. Unsafe Array Access (multiple files):

    • machine-id-utils.ts:37: result.split('IOPlatformUUID')[1]! - could be undefined
    • zenstack-auth-provider.ts:205: parts[1]! - could be undefined

🚀 Performance Considerations

  1. Efficient Caching (documentation-cache.ts:56-64):

    • Good normalization strategy removing whitespace for cache keys
    • 30-day cache duration is reasonable
  2. Batch Processing (zmodel-preview.ts:192-207):

    • Efficiently processes multiple imported files in parallel

📝 Code Quality Issues

  1. Magic Numbers:

    • vscode-telemetry.ts:43: UUID namespace should be a named constant
    • documentation-cache.ts:16: Cache duration should be configurable
  2. Error Message Consistency (zmodel-preview.ts:261):

    • Consider standardizing error message format across the extension
  3. Missing JSDoc (machine-id-utils.ts):

    • Core utility functions lack documentation

❌ Test Coverage

Critical Issue: No test files found in the VS Code extension package. For a feature of this complexity, especially one handling authentication and external API calls, comprehensive tests are essential.

Recommended Test Coverage:

  • Unit tests for each class (DocumentationCache, ZModelPreview, etc.)
  • Integration tests for authentication flow
  • Mock tests for external API calls
  • Error handling scenarios

📋 Specific Recommendations

  1. Add Configuration Options:

    // Allow users to disable external API calls
    const config = vscode.workspace.getConfiguration('zenstack');
    const allowExternalAPI = config.get('allowExternalDocumentation', true);
  2. Improve Error Handling (zenstack-auth-provider.ts:140):

    const timeoutId = setTimeout(() => {
        if (this.pendingAuth && !resolved) {
            this.pendingAuth.reject(new Error('Timeout'));
            delete this.pendingAuth;
        }
    }, 120000);
  3. Add Null Checks (machine-id-utils.ts:37):

    const parts = result.split('IOPlatformUUID');
    if (parts.length < 2) throw new Error('Invalid IOPlatformUUID format');
    return parts[1]!.split('\n')[0]!.replace(/=|\s+|"/gi, '').toLowerCase();
  4. Consider Privacy Documentation:

    • Add clear documentation about what data is sent to external services
    • Consider adding opt-in confirmation for first-time users

🎯 Overall Assessment

This is a well-implemented feature that follows VS Code extension best practices. The main concerns are around test coverage and privacy considerations for the external API integration. The code quality is good overall, with room for improvement in error handling and configuration flexibility.

Priority: Address test coverage and privacy documentation before merging.


Copy link
Copy Markdown
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.

♻️ Duplicate comments (2)
packages/ide/vscode/src/extension/zenstack-auth-provider.ts (2)

183-184: ⚠️ Potential issue | 🟠 Major

Avoid using email as telemetry identifier; handle missing email gracefully.

claims.email is optional, so the non-null assertion (!) will throw if the token lacks an email claim. Additionally, email is PII and shouldn't be used as the telemetry identifier. Per the relevant code snippet, this value is passed directly to Mixpanel as $identified_id. Use claims.sub (the stable user ID) instead.

Suggested fix
             const claims = this.parseJWTClaims(accessToken);
-            telemetry.identify(claims.email!);
+            if (claims.sub) {
+                telemetry.identify(claims.sub);
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts` around lines 183
- 184, The code calls telemetry.identify(claims.email!) which uses a nullable
PII field and force-asserts it; change this to use the stable, non-PII subject
claim and handle missing values: retrieve claims via
parseJWTClaims(accessToken), use claims.sub as the identifier passed to
telemetry.identify, and guard against a missing sub (e.g., only call
telemetry.identify if claims.sub is present or fall back to a safe anonymous id)
instead of using claims.email with a non-null assertion.

88-95: ⚠️ Potential issue | 🟠 Major

Load persisted sessions before checking, and await all removals.

This function checks this._sessions.length before loading from storage. On fresh activation, _sessions is empty even if persisted sessions exist, causing an early return. Additionally, forEach(async ...) doesn't await the removals, so the success message shows before logout completes.

Suggested fix
     async logoutAllSessions(): Promise<void> {
-        if (this._sessions.length === 0) {
+        const sessions = await this.getSessions();
+        if (sessions.length === 0) {
             return;
         }
 
-        (await this.getSessions()).forEach(async (s) => await this.removeSession(s.id));
+        await Promise.all(sessions.map((s) => this.removeSession(s.id)));
         vscode.window.showInformationMessage('Successfully logged out of ZenStack.');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts` around lines 88
- 95, The logoutAllSessions method currently returns early by checking
this._sessions.length before loading persisted sessions and uses forEach(async
...) which doesn't await removals; fix by first calling and awaiting
this.getSessions() to load persisted sessions (ensure to use its result rather
than this._sessions for the emptiness check), then perform removals with
Promise.all over mapped calls to this.removeSession(s.id) to await all
deletions, and only call vscode.window.showInformationMessage after all removals
complete; reference logoutAllSessions, this._sessions, getSessions,
removeSession, and vscode.window.showInformationMessage when making the changes.
🧹 Nitpick comments (2)
packages/ide/vscode/src/extension/zenstack-auth-provider.ts (2)

143-148: Timeout timer continues running after successful authentication.

The 2-minute timeout timer is never cancelled when authentication succeeds via handleAuthCallback. While the pendingAuth check prevents the reject from firing, the timer reference leaks until it eventually fires.

Suggested fix - store and clear the timer
+                    let timeoutId: NodeJS.Timeout;
+
                     // Store the state and resolve function for later use
-                    this.pendingAuth = { resolve, reject, scopes };
+                    this.pendingAuth = {
+                        resolve: (session) => {
+                            clearTimeout(timeoutId);
+                            resolve(session);
+                        },
+                        reject: (error) => {
+                            clearTimeout(timeoutId);
+                            reject(error);
+                        },
+                        scopes,
+                    };

                     // ...

                     // 2 minutes timeout
-                    setTimeout(() => {
+                    timeoutId = setTimeout(() => {
                         if (this.pendingAuth) {
                             delete this.pendingAuth;
                         }
                         reject(new Error('Timeout'));
                     }, 120000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts` around lines 143
- 148, The timeout created in the authentication flow should be stored and
cleared to avoid leaking the timer; modify the code that starts the 2-minute
timer (the block that sets setTimeout and references this.pendingAuth) to save
the timer id on the pendingAuth object (e.g., this.pendingAuth.timer) and in
handleAuthCallback (the method that completes successful authentication) clear
that timer via clearTimeout and remove the timer reference when resolving; also
ensure any code path that deletes this.pendingAuth clears the timer first so the
timeout cannot fire after success.

186-186: Consider using crypto.randomUUID() for session ID fallback.

Math.random().toString(36) provides weak uniqueness guarantees. Node.js (and VS Code's runtime) supports crypto.randomUUID() which generates proper UUIDs.

Suggested fix
+import * as crypto from 'crypto';
 // ...
-                id: claims.jti || Math.random().toString(36),
+                id: claims.jti || crypto.randomUUID(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts` at line 186,
Replace the weak fallback Math.random().toString(36) used for the session id in
zenstack-auth-provider.ts with a proper UUID from the crypto API: change the id
assignment (currently id: claims.jti || Math.random().toString(36)) to use
crypto.randomUUID() as the fallback (e.g., id: claims.jti ||
crypto.randomUUID()); ensure the module has access to crypto (import {
randomUUID } from 'crypto' or call globalThis.crypto.randomUUID() depending on
runtime/tsconfig) and update any types if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts`:
- Around line 183-184: The code calls telemetry.identify(claims.email!) which
uses a nullable PII field and force-asserts it; change this to use the stable,
non-PII subject claim and handle missing values: retrieve claims via
parseJWTClaims(accessToken), use claims.sub as the identifier passed to
telemetry.identify, and guard against a missing sub (e.g., only call
telemetry.identify if claims.sub is present or fall back to a safe anonymous id)
instead of using claims.email with a non-null assertion.
- Around line 88-95: The logoutAllSessions method currently returns early by
checking this._sessions.length before loading persisted sessions and uses
forEach(async ...) which doesn't await removals; fix by first calling and
awaiting this.getSessions() to load persisted sessions (ensure to use its result
rather than this._sessions for the emptiness check), then perform removals with
Promise.all over mapped calls to this.removeSession(s.id) to await all
deletions, and only call vscode.window.showInformationMessage after all removals
complete; reference logoutAllSessions, this._sessions, getSessions,
removeSession, and vscode.window.showInformationMessage when making the changes.

---

Nitpick comments:
In `@packages/ide/vscode/src/extension/zenstack-auth-provider.ts`:
- Around line 143-148: The timeout created in the authentication flow should be
stored and cleared to avoid leaking the timer; modify the code that starts the
2-minute timer (the block that sets setTimeout and references this.pendingAuth)
to save the timer id on the pendingAuth object (e.g., this.pendingAuth.timer)
and in handleAuthCallback (the method that completes successful authentication)
clear that timer via clearTimeout and remove the timer reference when resolving;
also ensure any code path that deletes this.pendingAuth clears the timer first
so the timeout cannot fire after success.
- Line 186: Replace the weak fallback Math.random().toString(36) used for the
session id in zenstack-auth-provider.ts with a proper UUID from the crypto API:
change the id assignment (currently id: claims.jti ||
Math.random().toString(36)) to use crypto.randomUUID() as the fallback (e.g.,
id: claims.jti || crypto.randomUUID()); ensure the module has access to crypto
(import { randomUUID } from 'crypto' or call globalThis.crypto.randomUUID()
depending on runtime/tsconfig) and update any types if necessary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 004ae9dd-ae01-4667-8c40-5b48fb86245d

📥 Commits

Reviewing files that changed from the base of the PR and between 6707a71 and 73c6e96.

📒 Files selected for processing (1)
  • packages/ide/vscode/src/extension/zenstack-auth-provider.ts

@ymc9 ymc9 merged commit 109171b into dev Mar 24, 2026
9 checks passed
@ymc9 ymc9 deleted the feat/v3-vscode-preview branch March 24, 2026 04:50
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