Skip to content

Feature/authentication#173

Open
jasir99 wants to merge 54 commits intodevfrom
feature/authentication
Open

Feature/authentication#173
jasir99 wants to merge 54 commits intodevfrom
feature/authentication

Conversation

@jasir99
Copy link
Contributor

@jasir99 jasir99 commented Jan 6, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Rosetta Cloud authentication integration with OAuth login flow
    • Added Cloud Settings panel for managing API keys and account details
    • Added user profile display with role information
    • Added environment mode switching between Local and Cloud execution
    • Enhanced cloud project deployment with environment variables and secrets management
    • Added Git repository status monitoring
  • Bug Fixes

    • Removed legacy project-to-cloud endpoint to streamline deployment flow

Nuri1977 and others added 30 commits October 22, 2025 13:09
Implement complete profile system with JWT auth, caching, React Query
hooks, ProfileCard component, settings page, and menu avatar with
user initials. Includes real-time updates and error handling.
# Conflicts:
#	src/main/utils/constants.ts
Nuri1977 and others added 17 commits November 6, 2025 17:04
…port

- Add environment prop ('local' | 'cloud') to ModelSplitButton and ProjectDbtSplitButton
- Filter menu items based on environment:
  - Cloud mode: hide local-only commands (Rosetta layers, debug, docs generation/serving, compile, preview)
  - Local mode: show all commands (existing behavior)
- Integrate PushToCloudModal for cloud execution:
  - ModelSplitButton: populate dbt arguments with model selection patterns (--select model, +model, model+, +model+)
  - ProjectDbtSplitButton: use existing cloud modal integration
- Update PushToCloudModal to accept initialDbtArguments prop
- Pass environment setting from ProjectDetails to both split buttons
- Maintain backward compatibility with default 'local' environment

Commands available in cloud mode:
- Production dbt: run, test, build, compile, clean, deps, seed
- Model operations: all run/test/build variants with dependency selection

Commands hidden in cloud mode:
- Rosetta layer generation (raw, staging, incremental, business)
- Local development tools (debug, serve docs, generate docs, compile, preview)
# Conflicts:
#	src/main/services/index.ts
#	src/renderer/config/constants.ts
# Conflicts:
#	src/renderer/components/dbtModelButtons/ProjectDbtSplitButton.tsx
#	src/renderer/components/settings/GeneralSettings.tsx
# Conflicts:
#	src/main/ipcHandlers/index.ts
#	src/main/ipcSetup.ts
#	src/main/main.ts
#	src/main/services/index.ts
#	src/renderer/App.tsx
#	src/renderer/components/settings/GeneralSettings.tsx
#	src/renderer/controllers/index.ts
#	src/renderer/screens/settings/index.tsx
#	src/renderer/screens/settings/settingsElements.tsx
#	src/renderer/services/index.ts
#	src/types/backend.ts
#	src/types/ipc.ts
# Conflicts:
#	src/main/ipcHandlers/git.ipcHandlers.ts
#	src/main/services/git.service.ts
#	src/renderer/components/menu/index.tsx
#	src/renderer/config/constants.ts
#	src/renderer/screens/projectDetails/index.tsx
#	src/renderer/services/git.service.ts
#	src/types/ipc.ts
@jasir99 jasir99 requested review from Nuri1977 and nbesimi January 6, 2026 12:48
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

This pull request introduces comprehensive Rosetta Cloud integration with API key-based authentication, cloud execution support for DBT commands, environment switching (local/cloud), and new git status inquiry methods. It replaces the previous cloud deployment flow with a modular RosettaCloudService and refactors UI components to support environment-aware execution paths.

Changes

Cohort / File(s) Summary
Asset Exports
assets/index.ts
Added rosettaIcon import and export for use in UI components.
Git Service Enhancements
src/main/services/git.service.ts, src/renderer/services/git.service.ts, src/renderer/controllers/git.controller.ts
Added six new methods to GitService: hasUntrackedChanges, hasUncommittedChanges, hasUnpushedChanges, hasLocalChanges, getLocalChangesStatus, and getRepoInfo. Exposed via IPC handlers and new React Query hooks in renderer.
Rosetta Cloud Service Layer
src/main/services/rosettaCloud.service.ts, src/renderer/services/rosettaCloud.service.ts, src/renderer/controllers/rosettaCloud.controller.ts
Introduced new RosettaCloudService with API key management, profile fetching, project deployment, secrets management, and authentication flows. Created corresponding renderer service and React Query hooks for cloud operations.
IPC Handler Registration
src/main/ipcHandlers/git.ipcHandlers.ts, src/main/ipcHandlers/rosettaCloud.ipcHandlers.ts, src/main/ipcHandlers/projects.ipcHandlers.ts, src/main/ipcHandlers/index.ts
Added git IPC handlers for local changes and repo info; created new rosettaCloud IPC handler module with 11 channels; removed project:pushToCloud handler and moved logic to dedicated service.
IPC Setup and Main Process
src/main/ipcSetup.ts, src/main/main.ts
Integrated RosettaCloudService import and IPC handler registration; added deep-link handling via custom rosetta:// protocol with token extraction, API key storage, and cross-platform support (macOS open-url, Windows/Linux ready events).
Projects Service Refactoring
src/main/services/projects.service.ts, src/renderer/services/projects.service.ts, src/renderer/controllers/projects.controller.ts
Removed pushProjectToCloud method and moved cloud deployment responsibility to RosettaCloudService; removed related type and constant imports.
Type System Extensions
src/types/backend.ts, src/types/ipc.ts, src/types/apiKey.ts, src/types/profile.ts, src/types/frontend.ts
Added GitChangesRes, RepoInfoRes, Secret types; expanded CloudDeploymentPayload (removed apiKey, added id/secrets/CUSTOM_DBT_COMMANDS); added SettingsType.env field; created new API key and profile type hierarchies; updated IPC channel definitions.
Constants and Utilities
src/main/utils/constants.ts, src/main/utils/fileHelper.ts, src/renderer/hooks/useSecureStorage.ts, src/renderer/config/constants.ts
Updated ROSETTA_CLOUD_BASE_URL to production domain; added CLOUD_DASHBOARD_API_KEY constant; removed cloudWorkspaceUrl/cloudWorkspaceLastSyncedAt from default settings; added Git and Cloud query keys; replaced hardcoded storage keys with constants.
Cloud Execution UI Components
src/renderer/components/dbtModelButtons/ModelSplitButton.tsx, src/renderer/components/dbtModelButtons/ProjectDbtSplitButton.tsx, src/renderer/screens/projectDetails/index.tsx
Added environment prop (local|cloud) to button components; wired PushToCloudModal trigger for cloud execution; filtered local-only menu items when in cloud mode; updated executeCommand routing to support cloud paths.
Menu and Settings UI
src/renderer/components/menu/index.tsx, src/renderer/components/menu/styles.ts, src/renderer/screens/settings/index.tsx, src/renderer/screens/settings/settingsElements.tsx
Added API key authentication state, login/logout flows, and environment switching in menu; created styled components for environment switch and auth buttons; added "Rosetta Cloud" settings section with CloudIcon.
Profile and Cloud Settings Components
src/renderer/components/profile/ProfileCard.tsx, src/renderer/components/profile/index.ts, src/renderer/components/settings/CloudSettings.tsx, src/renderer/components/settings/ProfileSettings.tsx, src/renderer/components/settings/index.ts
Introduced ProfileCard component for displaying user profile; created CloudSettings component for API key input and OAuth login; created ProfileSettings orchestrator combining cloud settings and profile display with refresh capability.
Push to Cloud Modal Rework
src/renderer/components/modals/pushToCloudModal/index.tsx
Substantially refactored modal to support environment variables management, git credentials input, local changes status, repository info validation, blocking error states, and dynamic secrets submission with edited tracking.
App Context and Data Fetching
src/renderer/context/AppProvider.tsx, src/renderer/hooks/useDbt.ts, src/renderer/hooks/useApiKeySync.ts
Extended AppContext with env (local|cloud) and authenticatedUser fields; updated AppProvider to fetch settings and profile; modified useDbt hook to support cloud callback routing; created useApiKeySync hook for auth state invalidation.
Manifest and Workflow Updates
package.json, release/app/package.json, .github/workflows/publish.yml
Bumped version to "1.3.1-auth-internal"; added feature/authentication branch to publish workflow trigger.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as App/MainProcess
    participant DeepLink as Deep Link Handler
    participant RCS as RosettaCloudService
    participant SecureStorage as Secure Storage
    participant Renderer as Renderer/UI

    User->>App: Open rosetta://auth?token=xyz
    App->>DeepLink: Handle custom protocol
    DeepLink->>DeepLink: Parse token from URL
    DeepLink->>RCS: Call storeApiKey(token)
    RCS->>SecureStorage: Store API key
    SecureStorage-->>RCS: Stored
    DeepLink->>Renderer: Send rosettaCloud:authSuccess event
    Renderer->>Renderer: Invalidate auth cache
    Renderer->>RCS: Fetch profile with stored API key
    RCS-->>Renderer: User profile
    Renderer->>Renderer: Update UI with authenticated state
Loading
sequenceDiagram
    participant User
    participant UI as DBT Component
    participant Renderer as App Context
    participant RCS as RosettaCloudService
    participant Modal as PushToCloudModal
    participant MainSvc as Main/RosettaCloudService
    participant Cloud as Rosetta Cloud API

    User->>UI: Click "Run in Cloud" (env=cloud)
    UI->>Renderer: Check environment from AppContext
    Renderer-->>UI: env='cloud'
    UI->>Modal: Open with command & args
    Modal->>RCS: Fetch repo info & local changes
    RCS-->>Modal: gitUrl, branch, changes status
    Modal->>Modal: Display validation errors if needed
    User->>Modal: Configure env vars & confirm
    Modal->>MainSvc: Call pushProjectToCloud
    MainSvc->>Cloud: Create/update project
    Cloud-->>MainSvc: Project created (externalId)
    MainSvc->>Cloud: Add secrets
    Cloud-->>MainSvc: Secrets added
    MainSvc->>Cloud: Trigger run with CUSTOM_DBT_COMMANDS
    Cloud-->>MainSvc: Run initiated
    MainSvc-->>Modal: Success
    Modal->>UI: Close with toast notification
    UI->>Renderer: Clear modal state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement

Suggested reviewers

  • nbesimi
  • Nuri1977

Poem

🐰 A cloud so high, a token so bright,
Deep links arrive with OAuth's might,
Profiles and secrets securely stashed,
DBT commands in clouds are splashed,
Local or cloud—the choice is yours tonight! ☁️✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/authentication' is overly vague and uses a generic format; it describes the feature branch name rather than summarizing the actual changes made in the pull request. Replace with a more specific, descriptive title that highlights the main change, e.g., 'Add Rosetta Cloud authentication and API key management' or 'Integrate cloud authentication with API key support'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/authentication

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

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/hooks/useDbt.ts (1)

269-278: Missing dependencies in useCallback.

env and cloudRunCb are used inside executeCommand but are not listed in the dependency array, which can cause stale closure issues.

🔎 Proposed fix
     [
       isRunning,
       connections,
       setupConnectionEnv,
       buildCommand,
       runCommand,
       successCallback,
       settings?.dbtPath,
+      env,
+      cloudRunCb,
     ],
src/main/ipcHandlers/git.ipcHandlers.ts (1)

13-35: Missing new IPC channels in handler cleanup array.

The new git:getLocalChanges and git:repoInfo handlers (lines 206-224) are not included in the handlerChannels array. This means removeGitIpcHandlers() won't clean them up, potentially causing "handler already registered" errors if registerGitHandlers() is called multiple times.

🔎 Proposed fix
 const handlerChannels = [
   'git:init',
   'git:clone',
   'git:listBranches',
   'git:checkout',
   'git:addRemote',
   'git:isInitialized',
   'git:getRemotes',
   'git:add',
   'git:unstage',
   'git:stageAll',
   'git:unstageAll',
   'git:discardChanges',
   'git:commit',
   'git:pull',
   'git:push',
   'git:fileDiff',
   'git:fileStatusList',
   'git:aheadBehind',
   'git:createBranch',
   'git:deleteBranch',
   'git:renameBranch',
+  'git:getLocalChanges',
+  'git:repoInfo',
 ];
🤖 Fix all issues with AI Agents
In @src/main/services/git.service.ts:
- Around line 1144-1180: getRepoInfo currently forces ".git" on remoteUrl
(variable remoteUrl inside getRepoInfo), which can produce incorrect addresses
for some remotes; remove the conditional that appends ".git" (or make it
conditional on a new optional flag like normalizeRemoteUrl) so the function
returns the original fetch URL from remotes.find(...).refs.fetch without
modification, and adjust callers if they relied on the forced ".git" suffix.

In @src/main/services/rosettaCloud.service.ts:
- Around line 28-41: The postJson helper (function postJson) currently returns
response.json() without checking response.ok; update it to check response.ok
after fetch and, if false, read and include the error body (prefer JSON if
possible, fallback to text) along with response.status/statusText in a thrown
Error (or custom error) so callers can handle failures; keep the Authorization
logic using this.getApiKey() as-is and ensure successful responses still return
the parsed JSON.

In @src/renderer/components/dbtModelButtons/ModelSplitButton.tsx:
- Around line 52-79: The component and the hook are using different environment
sources; align them by reading the environment from AppContext in this component
(like useDbt does) instead of using the environment prop. Import and call
useContext(AppContext) to obtain the same env value/profile fallback, replace
all uses of the environment prop in executeCommand and handlers (e.g.,
executeCommand, handleRunModel) with that context-derived env, and remove
reliance on props?.env so the component and useDbt share the exact same
environment decision logic.

In @src/renderer/components/menu/index.tsx:
- Around line 244-247: The code uses a non-null assertion on settings when
calling updateSettings({...settings!, env: newEnv}), which can crash if settings
is undefined; change the call to guard against undefined by either
early-returning when settings is falsy or merging with a safe default (e.g.,
updateSettings({ ...(settings ?? {}), env: newEnv })) and ensure this logic
lives where updateSettings, settings (from useGetSettings) and newEnv are
referenced so the switch toggle cannot dereference undefined.

In @src/renderer/components/modals/pushToCloudModal/index.tsx:
- Around line 883-893: The key TextField is incorrectly wired to handleEnvFocus
and handleEnvBlur which clear and mark the value as edited; remove the onFocus
and onBlur props from the key TextField (the instance using value={env.key} and
updateEnvironmentVariable(env.id, e.target.value, env.value)) and ensure those
focus/blur handlers remain only on the value TextField (the one that edits
env.value) so focus behavior only affects the value field.

In @src/renderer/controllers/git.controller.ts:
- Around line 117-149: Both hooks use a static queryKey which omits the path,
causing cached responses to bleed across repositories; update useGetLocalChanges
and useGetRepoInfo to include the path in their queryKey (e.g., append the path
to QUERY_KEYS.GIT_LOCAL_CHANGES and QUERY_KEYS.GIT_REPO_INFO) so the queryKey
uniquely identifies the repo, leaving the rest of the useQuery config and
queryFn (gitServices.getLocalChanges(path) / gitServices.getRepoInfo(path))
unchanged.

In @src/renderer/controllers/rosettaCloud.controller.ts:
- Around line 76-85: The query key for useGetSecrets must include the projectId
to avoid cross-project cache collisions; update the queryKey in the
useGetSecrets function (currently using QUERY_KEYS.CLOUD_SECRETS) to include the
projectId (e.g., [QUERY_KEYS.CLOUD_SECRETS, projectId ?? '']) so each project
has an isolated cache entry while keeping the existing queryFn that calls
rosettaCloudServices.getSecrets(projectId ?? '') and preserving passed options.

In @src/renderer/hooks/useDbt.ts:
- Around line 227-230: When env === 'cloud' the early return prevents the
finally block from clearing activeCommand, leaving the state stuck; update the
cloud branch in the run flow (in useDbt) to ensure setActiveCommand(null) is
invoked before returning — either call setActiveCommand(null) immediately after
cloudRunCb?.(command) or refactor so the finally block always runs (e.g., avoid
returning early in the try and instead short-circuit after performing cleanup).
Target symbols: activeCommand, setActiveCommand, cloudRunCb, and the run
function in useDbt to apply the fix.

In @src/types/profile.ts:
- Around line 16-18: UserProfile declares emailVerified, createdAt and updatedAt
as Date but RosettaCloudService.getProfile() returns raw JSON ISO strings; fix
by parsing those ISO strings into Date objects inside
RosettaCloudService.getProfile() (convert emailVerified nullable ISO to
Date|null and createdAt/updatedAt to Date) before returning, or alternatively
change UserProfile types to string (and emailVerified to string|null) and update
all callers that use Date methods; reference UserProfile and
RosettaCloudService.getProfile() when making the change so callers like
components expecting .getTime() or .toLocaleDateString() are updated
accordingly.
🧹 Nitpick comments (10)
src/renderer/components/profile/ProfileCard.tsx (1)

41-50: Consider reusing the existing getInitials utility and add a guard for empty email.

A more robust getInitials helper already exists in src/renderer/helpers/utils.ts that handles edge cases like underscores in names. Additionally, email[0] will throw if the email is an empty string.

🔎 Proposed fix
+import { getInitials as getNameInitials } from '../../helpers/utils';
+
 export const ProfileCard: React.FC = () => {
   const { data: profile, isLoading, error } = useProfile();
   // ... loading/error states ...

-  const getInitials = (name: string | null, email: string) => {
-    if (name) {
-      return name
-        .split(' ')
-        .map((n) => n[0])
-        .join('')
-        .toUpperCase();
-    }
-    return email[0].toUpperCase();
-  };
+  const getInitials = (name: string | null, email: string) => {
+    if (name) {
+      return getNameInitials(name);
+    }
+    return email?.length ? email[0].toUpperCase() : '?';
+  };
src/main/main.ts (1)

63-67: Consider not passing apiKey in the authSuccess payload.

The API key is already stored securely via RosettaCloudService.storeApiKey. Sending it to the renderer in the authSuccess event payload may be unnecessary if the renderer only needs to know authentication succeeded (to trigger profile/API key refetch).

🔎 Proposed change
           windowManager
             ?.getMainWindow()
-            ?.webContents.send('rosettaCloud:authSuccess', {
-              apiKey,
-            });
+            ?.webContents.send('rosettaCloud:authSuccess');
src/renderer/controllers/profile.controller.ts (1)

28-31: Fragile error check based on message string.

Checking error?.message?.includes('401') relies on error message format which could change. Consider checking for a more structured error property (e.g., status code) if available in CustomError.

     retry: (failureCount, error) => {
       // Don't retry on auth errors
-      if (error?.message?.includes('401')) return false;
+      if (error?.code === '401' || error?.message?.includes('401')) return false;
       return failureCount < 3;
     },
src/renderer/components/menu/index.tsx (1)

76-76: Unused useAuthLogout hook.

Only isLoading is destructured, but without calling mutate, logoutLoading will never be true. If logout is handled elsewhere, consider removing this hook or moving the logout UI to this component.

src/main/services/git.service.ts (2)

1009-1055: Consider making fetch optional or caching its result.

This method calls git.fetch() on every invocation, which is a network operation that can be slow or fail. When hasLocalChanges calls this alongside other methods, multiple fetches may occur. Consider:

  1. Adding an optional parameter to skip fetch
  2. Caching fetch results with a TTL
  3. Documenting that callers should use getLocalChangesStatus instead for combined queries

1057-1072: Potential for multiple git fetch operations.

hasLocalChanges calls hasUnpushedChanges which performs a git.fetch(). If the caller also needs detailed info from getLocalChangesStatus, another fetch occurs. Consider using getLocalChangesStatus internally or refactoring to share fetch results.

src/types/apiKey.ts (1)

20-22: Convert empty interface to a type alias.

The static analysis tool (Biome) correctly flags that an empty interface is equivalent to {}. For a void/empty payload, use a type alias instead.

🔎 Proposed fix
-export interface ApiKeyUpdatePayload {
-  // Void type - no payload data needed for API key updates
-}
+// Void type - no payload data needed for API key updates
+export type ApiKeyUpdatePayload = Record<string, never>;
src/main/services/rosettaCloud.service.ts (2)

178-196: Clarify error handling behavior for non-401 failures.

Lines 185 throws an error for non-401/non-OK responses, which is then caught at line 191 and silently returns the cached profile. This may hide server errors (e.g., 500) from the user. Consider differentiating between network errors (where cache fallback is appropriate) and server errors (where explicit failure may be preferred).


220-234: Consider removing success log for API key storage.

Logging "API key stored successfully" (line 228) is not a security issue per se, but it's generally a good practice to avoid logging about credential operations in production. The error logging on failure is appropriate.

src/renderer/components/modals/pushToCloudModal/index.tsx (1)

347-353: Consider using a more robust ID generator.

Using Date.now().toString() for IDs works for client-side state but could theoretically produce duplicates if addEnvironmentVariable is called multiple times within the same millisecond. Consider using a counter or UUID for more robustness.

🔎 Proposed alternative
+import { v4 as uuidv4 } from 'uuid';
+// or use a simple counter:
+// let envVarIdCounter = 0;

     const newEnv: EnvironmentVariable = {
-      id: Date.now().toString(),
+      id: uuidv4(),
+      // or: id: `env-${++envVarIdCounter}`,
       key: trimmedKey,
       value: trimmedValue,
       originalValue: trimmedValue,
       isEdited: true,
     };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb5ee80 and f079aab.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (48)
  • .gitignore
  • assets/index.ts
  • src/main/ipcHandlers/git.ipcHandlers.ts
  • src/main/ipcHandlers/index.ts
  • src/main/ipcHandlers/projects.ipcHandlers.ts
  • src/main/ipcHandlers/rosettaCloud.ipcHandlers.ts
  • src/main/ipcSetup.ts
  • src/main/main.ts
  • src/main/services/git.service.ts
  • src/main/services/index.ts
  • src/main/services/projects.service.ts
  • src/main/services/rosettaCloud.service.ts
  • src/main/utils/constants.ts
  • src/main/utils/fileHelper.ts
  • src/renderer/App.tsx
  • src/renderer/components/dbtModelButtons/ModelSplitButton.tsx
  • src/renderer/components/dbtModelButtons/ProjectDbtSplitButton.tsx
  • src/renderer/components/menu/index.tsx
  • src/renderer/components/menu/styles.ts
  • src/renderer/components/modals/pushToCloudModal/index.tsx
  • src/renderer/components/profile/ProfileCard.tsx
  • src/renderer/components/profile/index.ts
  • src/renderer/components/settings/CloudSettings.tsx
  • src/renderer/components/settings/ProfileSettings.tsx
  • src/renderer/components/settings/index.ts
  • src/renderer/config/constants.ts
  • src/renderer/context/AppProvider.tsx
  • src/renderer/controllers/git.controller.ts
  • src/renderer/controllers/index.ts
  • src/renderer/controllers/profile.controller.ts
  • src/renderer/controllers/projects.controller.ts
  • src/renderer/controllers/rosettaCloud.controller.ts
  • src/renderer/hooks/useApiKeySync.ts
  • src/renderer/hooks/useDbt.ts
  • src/renderer/hooks/useSecureStorage.ts
  • src/renderer/screens/projectDetails/index.tsx
  • src/renderer/screens/settings/index.tsx
  • src/renderer/screens/settings/settingsElements.tsx
  • src/renderer/services/git.service.ts
  • src/renderer/services/index.ts
  • src/renderer/services/profile.service.ts
  • src/renderer/services/projects.service.ts
  • src/renderer/services/rosettaCloud.service.ts
  • src/types/apiKey.ts
  • src/types/backend.ts
  • src/types/frontend.ts
  • src/types/ipc.ts
  • src/types/profile.ts
💤 Files with no reviewable changes (5)
  • src/renderer/services/projects.service.ts
  • src/main/ipcHandlers/projects.ipcHandlers.ts
  • src/main/services/projects.service.ts
  • src/main/utils/fileHelper.ts
  • src/renderer/controllers/projects.controller.ts
🧰 Additional context used
🧬 Code graph analysis (22)
src/renderer/components/profile/ProfileCard.tsx (3)
src/renderer/components/profile/index.ts (1)
  • ProfileCard (1-1)
src/renderer/controllers/profile.controller.ts (1)
  • useProfile (17-35)
src/renderer/helpers/utils.ts (1)
  • getInitials (71-76)
src/renderer/hooks/useSecureStorage.ts (2)
src/renderer/services/secureStorage.service.ts (1)
  • secureStorageService (4-29)
src/main/utils/constants.ts (1)
  • CLOUD_DASHBOARD_API_KEY (26-26)
src/main/ipcHandlers/rosettaCloud.ipcHandlers.ts (3)
src/main/ipcHandlers/index.ts (1)
  • registerRosettaCloudIpcHandlers (27-27)
src/types/backend.ts (1)
  • CloudDeploymentPayload (205-214)
src/main/services/rosettaCloud.service.ts (1)
  • RosettaCloudService (11-303)
src/renderer/components/settings/CloudSettings.tsx (3)
src/renderer/controllers/rosettaCloud.controller.ts (3)
  • useApiKey (49-64)
  • useValidateApiKey (66-71)
  • useAuthLogin (87-100)
src/main/services/rosettaCloud.service.ts (1)
  • validateApiKey (266-302)
src/renderer/hooks/useApiKeySync.ts (1)
  • useApiKeySync (10-25)
src/renderer/components/settings/ProfileSettings.tsx (3)
src/renderer/controllers/profile.controller.ts (3)
  • useProfile (17-35)
  • useRefreshProfile (37-55)
  • useProfileSubscription (57-112)
src/renderer/components/settings/CloudSettings.tsx (1)
  • CloudSettings (29-346)
src/renderer/components/profile/ProfileCard.tsx (1)
  • ProfileCard (14-77)
src/renderer/components/dbtModelButtons/ModelSplitButton.tsx (3)
src/types/backend.ts (1)
  • DbtCommandType (673-684)
src/renderer/helpers/utils.ts (1)
  • extractModelNameFromPath (271-284)
src/renderer/components/modals/pushToCloudModal/index.tsx (1)
  • PushToCloudModal (61-1107)
src/renderer/hooks/useApiKeySync.ts (2)
src/renderer/controllers/rosettaCloud.controller.ts (1)
  • useAuthSubscription (133-173)
src/renderer/config/constants.ts (1)
  • QUERY_KEYS (46-88)
src/renderer/controllers/git.controller.ts (2)
src/types/backend.ts (3)
  • GitChangesRes (734-741)
  • CustomError (342-344)
  • RepoInfoRes (743-747)
src/renderer/config/constants.ts (1)
  • QUERY_KEYS (46-88)
src/types/frontend.ts (1)
src/types/profile.ts (1)
  • UserProfile (11-22)
src/renderer/controllers/profile.controller.ts (3)
src/types/profile.ts (1)
  • UserProfile (11-22)
src/types/backend.ts (1)
  • CustomError (342-344)
src/renderer/services/profile.service.ts (1)
  • profileService (25-29)
src/renderer/screens/settings/index.tsx (1)
src/renderer/components/settings/ProfileSettings.tsx (1)
  • ProfileSettings (21-107)
src/main/services/git.service.ts (1)
src/types/backend.ts (2)
  • GitChangesRes (734-741)
  • RepoInfoRes (743-747)
src/main/ipcSetup.ts (1)
src/main/ipcHandlers/index.ts (1)
  • registerRosettaCloudIpcHandlers (27-27)
src/renderer/App.tsx (1)
src/renderer/screens/index.ts (1)
  • Settings (13-13)
src/renderer/components/menu/index.tsx (5)
src/renderer/services/settings.services.ts (1)
  • updateSettings (21-23)
src/renderer/controllers/rosettaCloud.controller.ts (4)
  • useApiKey (49-64)
  • useAuthLogin (87-100)
  • useAuthLogout (102-131)
  • useAuthSubscription (133-173)
src/renderer/controllers/profile.controller.ts (2)
  • useProfileSubscription (57-112)
  • useProfile (17-35)
assets/index.ts (1)
  • rosettaIcon (5-5)
src/main/utils/constants.ts (1)
  • ROSETTA_CLOUD_BASE_URL (28-28)
src/renderer/context/AppProvider.tsx (3)
src/renderer/controllers/settings.controller.ts (1)
  • useGetSettings (20-30)
src/renderer/controllers/aiProviders.controller.ts (1)
  • useGetActiveAIProvider (89-103)
src/renderer/controllers/profile.controller.ts (1)
  • useProfile (17-35)
src/main/ipcHandlers/git.ipcHandlers.ts (1)
src/types/backend.ts (2)
  • GitChangesRes (734-741)
  • RepoInfoRes (743-747)
src/renderer/screens/settings/settingsElements.tsx (1)
src/renderer/components/sidebar/icons.tsx (1)
  • CloudIcon (56-56)
src/renderer/components/modals/pushToCloudModal/index.tsx (3)
src/types/backend.ts (2)
  • Project (184-203)
  • DbtCommandType (673-684)
src/renderer/controllers/git.controller.ts (2)
  • useGetLocalChanges (117-132)
  • useGetRepoInfo (134-149)
src/renderer/controllers/rosettaCloud.controller.ts (2)
  • usePushProjectToCloud (25-47)
  • useGetSecrets (76-85)
src/main/main.ts (4)
src/main/utils/iconUtils.ts (1)
  • setupApplicationIcon (39-50)
src/main/windows/index.ts (1)
  • WindowManager (11-136)
src/main/services/index.ts (1)
  • RosettaCloudService (31-31)
src/main/services/rosettaCloud.service.ts (1)
  • RosettaCloudService (11-303)
src/renderer/hooks/useDbt.ts (3)
src/renderer/hooks/index.ts (2)
  • useDbt (24-24)
  • useAppContext (19-19)
src/types/backend.ts (1)
  • DbtCommandType (673-684)
src/renderer/controllers/settings.controller.ts (1)
  • useGetSettings (20-30)
src/main/services/rosettaCloud.service.ts (4)
src/types/profile.ts (1)
  • UserProfile (11-22)
src/types/backend.ts (2)
  • CloudDeploymentPayload (205-214)
  • Secret (749-753)
src/main/services/projects.service.ts (1)
  • ProjectsService (44-1162)
src/main/utils/constants.ts (1)
  • ROSETTA_CLOUD_BASE_URL (28-28)
🪛 Biome (2.1.2)
src/types/apiKey.ts

[error] 20-22: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

🔇 Additional comments (76)
.gitignore (1)

36-36: No issues detected. This appears to be a minor cosmetic adjustment with no functional impact on the ignore patterns.

src/renderer/screens/settings/settingsElements.tsx (1)

4-4: LGTM!

The new CloudIcon import and "Rosetta Cloud" sidebar entry follow the established pattern in this file. The path /app/settings/profile correctly aligns with the route added in App.tsx.

Also applies to: 69-73

src/renderer/components/profile/index.ts (1)

1-1: LGTM!

Standard barrel export following the project's module structure conventions.

src/renderer/App.tsx (1)

57-57: LGTM!

The new route for settings/profile follows the established pattern for settings sub-routes, correctly rendering the Settings component.

src/renderer/components/settings/index.ts (1)

2-2: LGTM!

Clean addition of the ProfileSettings re-export following the existing pattern.

src/renderer/services/git.service.ts (1)

137-151: LGTM!

The new getLocalChanges and getRepoInfo functions follow the established IPC communication pattern in this service, with proper typing and nullable return types for graceful failure handling.

src/renderer/screens/settings/index.tsx (1)

27-27: LGTM!

The ProfileSettings integration is clean:

  • Import added appropriately
  • Section title correctly mapped to "Rosetta Cloud"
  • Switch case follows the existing pattern with the prop-less ProfileSettings component

Also applies to: 105-105, 127-128

src/main/ipcHandlers/index.ts (1)

12-12: LGTM! Clean integration of Rosetta Cloud IPC handlers.

The new handler registration follows the established pattern and is properly integrated into the module exports.

Also applies to: 27-27

src/renderer/services/index.ts (1)

10-10: LGTM! Rosetta Cloud service properly exposed.

The service integration follows the established module export pattern.

Also applies to: 23-23

assets/index.ts (1)

3-3: LGTM! Asset export addition is clean.

The Rosetta icon is properly imported and exposed alongside existing assets.

Also applies to: 5-5

src/renderer/controllers/index.ts (1)

8-9: LGTM! Controller exports properly added.

The new profile and Rosetta Cloud controllers are integrated following the established export pattern.

src/types/frontend.ts (1)

4-4: The env property is already supplied in all AppContext creation locations.

The env property on AppContextType (required, non-optional) is correctly provided both in the default context value (line 20 with 'local') and in the actual provider value (line 171 with profile ? (settings?.env ?? 'local') : 'local'). No additional updates are needed.

src/main/services/index.ts (1)

12-12: LGTM! Clean service registration.

The RosettaCloudService is properly imported and exported, maintaining consistency with the existing service registration pattern.

Also applies to: 31-31

src/main/ipcSetup.ts (1)

14-14: LGTM! IPC handler registration follows established pattern.

The Rosetta Cloud IPC handlers are properly wired into the registration sequence alongside existing handlers.

Also applies to: 30-30

src/renderer/hooks/useSecureStorage.ts (1)

2-2: LGTM! Good refactor to eliminate magic strings.

Replacing the hard-coded 'cloud-api-key' string with the CLOUD_DASHBOARD_API_KEY constant centralizes the key name and reduces the risk of typos across the codebase.

Also applies to: 150-150, 154-154, 158-158

src/renderer/hooks/useApiKeySync.ts (1)

1-25: LGTM! Clean authentication state management.

The hook properly subscribes to auth events and provides a well-structured mechanism to refresh authentication-related queries. The use of useCallback for memoization is appropriate.

src/renderer/components/settings/ProfileSettings.tsx (1)

21-107: LGTM! Well-structured profile management component.

The component effectively handles:

  • Loading states with appropriate UI feedback
  • Conditional rendering based on authentication status
  • Real-time profile updates via subscription
  • Error states with user-friendly alerts

The pattern of calling useProfile() here for loading/error state while ProfileCard also calls it internally is a standard React Query practice—the library efficiently deduplicates and caches the queries.

src/renderer/config/constants.ts (1)

65-66: LGTM!

The new query keys follow the existing naming conventions and align with the new IPC channels and controller hooks introduced in this PR for git repository status and Rosetta Cloud authentication features.

Also applies to: 85-87

src/renderer/screens/projectDetails/index.tsx (1)

796-797: LGTM!

The environment prop is correctly propagated from settings to both button components, enabling environment-aware behavior (local vs. cloud). Optional chaining handles the case where settings may still be loading.

Also applies to: 807-808

src/types/ipc.ts (2)

55-69: LGTM!

The new RosettaCloudChannels type cleanly defines all IPC channels for the Rosetta Cloud integration, including request/response handlers (push, profile, API key management, secrets) and event channels for the OAuth deep-link flow (authSuccess, authError, apiKeyUpdated).


204-205: LGTM!

New git IPC channels align with the corresponding hooks (useGetLocalChanges, useGetRepoInfo) and backend service methods.

src/renderer/components/menu/styles.ts (2)

29-90: LGTM!

The environment switch styling is well-structured with proper MUI theme integration. The SwitchIcon component uses a class-based approach (.checked/.unchecked) for positioning, which works correctly when the parent component toggles classes based on switch state.


92-108: LGTM!

Clean styled components for the authentication button content with appropriate theme token usage for consistent styling.

src/renderer/components/profile/ProfileCard.tsx (1)

52-76: LGTM!

The profile card UI is well-structured with appropriate MUI components. The conditional role chip coloring (primary for ADMIN, default for USER) provides clear visual distinction.

src/main/ipcHandlers/rosettaCloud.ipcHandlers.ts (1)

5-61: LGTM!

The IPC handlers correctly delegate to RosettaCloudService methods. The event-based channels (rosettaCloud:authSuccess, rosettaCloud:authError, and rosettaCloud:apiKeyUpdated) are properly sent from the main process via webContents.send() in response to the OAuth deep-link flow and API key updates, and are listened to by the renderer via ipcRenderer.on().

src/main/utils/constants.ts (1)

26-28: Verify URL endpoint for production readiness.

The CLOUD_DASHBOARD_API_KEY constant name is clear as a storage key identifier. However, ROSETTA_CLOUD_BASE_URL points to dashboard.tolstudios.net which appears to be a staging or development environment rather than a production Rosetta Cloud domain.

Ensure this URL is intentional for the target branch (dev) and that there's a plan to update it for production releases.

src/main/ipcHandlers/git.ipcHandlers.ts (1)

206-224: LGTM!

The new git:getLocalChanges and git:repoInfo handlers follow the established patterns in this file with proper typing and consistent structure.

src/renderer/context/AppProvider.tsx (1)

169-170: LGTM - Good security pattern.

The conditional env derivation correctly ensures cloud environment is only available when a user profile exists, preventing unauthorized cloud access. The fallback to 'local' is appropriate.

src/main/main.ts (1)

35-41: LGTM!

The rosetta:// protocol is correctly registered with secure: true, which is appropriate for handling authentication callbacks.

src/types/profile.ts (1)

1-31: LGTM!

The profile types are well-structured with appropriate nullable fields and clear role definitions.

src/renderer/controllers/profile.controller.ts (1)

57-111: LGTM!

The IPC subscription hook properly sets up and cleans up all event listeners, with appropriate query invalidation on auth events.

src/renderer/components/menu/index.tsx (2)

160-231: LGTM!

The conditional rendering of auth/dashboard buttons based on apiKey state is clean, with informative tooltips and consistent styling.


234-290: LGTM!

The environment switch is correctly gated behind profile authentication and provides clear visual feedback with appropriate icons and labels.

src/renderer/components/settings/CloudSettings.tsx (5)

1-28: LGTM!

The imports are well-organized and include all necessary MUI components, icons, and custom hooks for the cloud settings functionality.


29-66: LGTM!

Component initialization is clean. The hooks are properly configured with callbacks for OAuth login success/error handling, and the useEffect correctly clears input state when an API key is received (e.g., from OAuth flow).


117-134: LGTM!

The handleRemoveApiKey function properly handles async cleanup with appropriate error handling and state reset in the finally block.


153-345: LGTM!

The JSX structure is well-organized with clear conditional rendering for OAuth login, connection status, and API key management sections. The show/hide toggle for API keys is a good security practice.


76-115: isSaving not reset when validation fails.

When validation.valid is false at line 96-98, the function returns early without reaching the finally block, leaving isSaving as true. This will disable the UI indefinitely after a failed validation.

🔎 Proposed fix
       if (!validation.valid) {
         setApiKeyError(validation.error || 'Invalid API key');
+        setIsSaving(false);
         return;
       }

Likely an incorrect or invalid review comment.

src/types/backend.ts (4)

201-202: LGTM!

The new optional properties externalId and lastRun extend the Project type appropriately for cloud integration.


205-214: LGTM!

CloudDeploymentPayload is well-structured with the required id and secrets fields for cloud deployment. The optional CUSTOM_DBT_COMMANDS provides flexibility for custom dbt command configuration.


280-281: LGTM!

The env property with 'local' | 'cloud' union type provides clear environment switching capability.


733-753: LGTM!

The new GitChangesRes, RepoInfoRes, and Secret types are well-defined and align with the git service implementations.

src/renderer/services/profile.service.ts (1)

1-31: LGTM!

Clean service implementation with consistent structure. The functions properly wrap IPC calls and return typed responses. Error handling is appropriately delegated to the consuming code (controllers/hooks).

src/renderer/components/dbtModelButtons/ProjectDbtSplitButton.tsx (6)

7-19: LGTM!

The imports are properly extended to include cloud-related types and components.


104-202: LGTM!

The Rosetta layer generation commands (Raw Layer, Staging Layer, Incremental/Enhanced Layer, Business Layer) are correctly marked as localOnly: true since they require local Rosetta CLI execution.


203-348: LGTM!

The dbt commands are appropriately categorized - production commands like Run, Test, Build, Compile, Clean, Deps, Seed are available in both environments, while Debug, Generate Docs, and Serve Docs are correctly marked as local-only development tools.


350-374: LGTM!

The filtering logic correctly excludes localOnly items in cloud mode while showing all items in local mode. The eslint disable comment for the unused localOnly destructuring is acceptable.


440-447: LGTM!

The PushToCloudModal is properly integrated with appropriate props for project and command context.


49-64: Clarify the purpose of the useDbt callback in local mode.

The callback (command) => { setRunInCloudModal(command); } is passed to useDbt, but this will trigger the cloud modal even when environment === 'local'. Is this intentional? If the modal should only appear in cloud mode, the callback should check the environment first.

#!/bin/bash
# Check how useDbt uses the callback parameter
ast-grep --pattern $'export function useDbt($_, $_) {
  $$$
}'

ast-grep --pattern $'export const useDbt = ($_, $_) => {
  $$$
}'

rg -n -A 20 'export.*useDbt' --type ts
src/main/services/git.service.ts (3)

6-11: LGTM!

Import additions for the new Git-related types are correct.


976-1007: LGTM!

The hasUntrackedChanges and hasUncommittedChanges methods are straightforward implementations that check the git status correctly.


1074-1142: LGTM!

The getLocalChangesStatus method provides a comprehensive view of repository changes. The error handling for fetch failures is appropriate - silently returning false for unpushed status when network is unavailable is reasonable behavior.

src/renderer/components/dbtModelButtons/ModelSplitButton.tsx (4)

11-18: LGTM!

Cloud-related imports are properly added.


226-253: LGTM!

The handleRunModel function correctly routes execution based on environment. The model name extraction happens before the environment check, which is appropriate since both paths need it.


587-702: LGTM!

The allMenuItems array is well-structured with appropriate localOnly flags. Compile and Preview are correctly marked as local-only since they require direct database connectivity and local dbt execution.


749-761: LGTM!

The PushToCloudModal is properly integrated with initialDbtArguments passed through, and the onClose handler correctly clears both the modal state and the arguments.

src/renderer/controllers/rosettaCloud.controller.ts (5)

1-23: LGTM!

Imports are properly organized with React Query hooks and type definitions.


25-47: LGTM!

The usePushProjectToCloud mutation properly invalidates the selected project query on success and forwards custom callbacks.


49-71: LGTM!

The useApiKey and useValidateApiKey hooks are well-implemented with appropriate defaults and retry configuration.


87-131: LGTM!

The useAuthLogin and useAuthLogout hooks are properly implemented. The logout hook correctly invalidates both API key and user profile caches and provides user feedback via toasts.


133-173: LGTM!

The useAuthSubscription hook properly subscribes to authentication events and cleans up subscriptions on unmount. The query invalidation on auth state changes ensures the UI stays in sync.

src/types/apiKey.ts (1)

1-73: Well-structured type definitions for the new API key authentication system.

The types are well-organized and clearly define the contract for API key operations, event payloads, and React Query hooks. The separation between state types, payload types, and hook result types is clean.

src/renderer/services/rosettaCloud.service.ts (3)

36-55: Subscription cleanup function correctly implemented.

The pattern of returning a cleanup function that removes the listener is correct and prevents memory leaks. The validation for apiKey presence before invoking the callback is a good defensive measure.


57-88: Consistent subscription patterns with proper cleanup.

The subscription functions follow a consistent pattern and handle edge cases well (e.g., providing a fallback error message in subscribeToAuthError).


96-102: IPC handler alignment is correct.

The getSecrets function properly aligns with the IPC handler in rosettaCloud.ipcHandlers.ts. Both the client call and the handler expect projectId as a string parameter, which is correctly passed through to RosettaCloudService.getSecrets(projectId).

src/main/services/rosettaCloud.service.ts (3)

57-89: Project deployment logic is well-structured.

The branching logic correctly handles both existing projects (with externalId) and new projects. The flow of create → add secrets → run is appropriate.


91-155: Good error handling in getSecrets and deleteSecret.

Both methods properly validate preconditions (project exists, has externalId) and check response.ok before processing. This is consistent and robust.


266-302: Comprehensive API key validation with proper status handling.

The validateApiKey method handles various response scenarios (200 OK, 401, 404, other errors, and network failures) with appropriate error messages. This provides good feedback to users during authentication.

src/renderer/components/modals/pushToCloudModal/index.tsx (8)

43-49: Well-defined local interface for environment variable tracking.

The EnvironmentVariable interface with isEdited and originalValue tracking is a clean solution for managing edit state and enabling reversion.


69-76: Data fetching hooks are well-integrated.

The use of separate hooks for local changes, repo info, and secrets provides clean separation of concerns and enables individual loading states.


136-168: Proper separation of Git credentials from environment variables.

The effect correctly filters out reserved keys (ROSETTA_GIT_USER, ROSETTA_GIT_PASSWORD) from general environment variables and handles them separately, preventing duplication.


170-197: Comprehensive blocking error detection for deployment prerequisites.

The validation checks for repository info availability, remote URL configuration, and branch existence on remote provide clear, actionable error messages to users.


305-306: Verify intended behavior for Git credentials in run mode.

In run mode (isRunMode), githubUsername and githubPassword are set to undefined (lines 305-306). However, lines 289-294 still add them to reducedSecrets if edited. Confirm this is the intended behavior—it appears Git credentials are only submitted as secrets in run mode, not as top-level fields.


394-421: Smart focus/blur handlers for secret editing.

The pattern of clearing on focus (for non-edited values) and reverting on blur (if empty or unchanged) provides a good UX for editing masked secret values while preventing accidental data loss.


556-593: Git URL and Branch fields appropriately disabled.

These fields are auto-filled from repository configuration and correctly disabled to prevent user modification, ensuring consistency with the actual repository state.


1000-1106: Modal structure and submission gating look correct.

The modal properly prevents closing during push operation (isPushing), and the submit button is appropriately disabled based on canSubmit which checks all required conditions.

Comment on lines +252 to +259
// Handle deep links on Windows/Linux
app.on('ready', () => {
// Check if app was opened with a deep link
const url = process.argv.find((arg) => arg.startsWith('rosetta://'));
if (url) {
handleDeepLink(url);
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Deep links on cold start may be silently lost.

When the app is launched via a deep link on Windows/Linux, this ready handler fires before windowManager and the main window are created (lines 119-190). The optional chaining prevents crashes, but the rosettaCloud:authSuccess notification will be silently dropped.

Consider deferring deep link handling until the main window is ready, or queuing the URL for later processing.

🔎 Proposed approach
+let pendingDeepLinkUrl: string | null = null;
+
 async function handleDeepLink(url: string) {
+  // If main window not ready, queue the URL
+  if (!windowManager?.getMainWindow()) {
+    pendingDeepLinkUrl = url;
+    return;
+  }
   try {
     // ... existing logic
   }
 }

 // In the splash.webContents.once('did-finish-load') callback, after showMainWindow():
+if (pendingDeepLinkUrl) {
+  handleDeepLink(pendingDeepLinkUrl);
+  pendingDeepLinkUrl = null;
+}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1144 to +1180
async getRepoInfo(repoPath: string): Promise<RepoInfoRes | null> {
const git = this.getGitInstance(repoPath);

try {
const remotes = await git.getRemotes(true);
const origin = remotes.find((r) => r.name === 'origin');
let remoteUrl = origin?.refs?.fetch || null;

if (remoteUrl && !remoteUrl.endsWith('.git')) {
remoteUrl = `${remoteUrl}.git`;
}

const branchSummary = await git.branch();
const currentBranch = branchSummary.current;

let branchExistsOnRemote = false;
if (currentBranch) {
try {
await git.fetch();
const remoteBranches = await git.branch(['-r']);
branchExistsOnRemote = remoteBranches.all.includes(
`origin/${currentBranch}`,
);
} catch (err) {
branchExistsOnRemote = false;
}
}

return {
remoteUrl,
currentBranch,
branchExistsOnRemote,
};
} catch (err: any) {
return null;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalizing remote URL by appending .git may not always be correct.

Lines 1152-1154 append .git to the remote URL if it doesn't end with .git. However, some Git remotes (especially internal/enterprise ones or those using different protocols) may not follow this convention. This could produce incorrect URLs.

🔎 Suggested approach

Consider removing the normalization or making it optional, as the original remote URL should be authoritative:

       let remoteUrl = origin?.refs?.fetch || null;
 
-      if (remoteUrl && !remoteUrl.endsWith('.git')) {
-        remoteUrl = `${remoteUrl}.git`;
-      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRepoInfo(repoPath: string): Promise<RepoInfoRes | null> {
const git = this.getGitInstance(repoPath);
try {
const remotes = await git.getRemotes(true);
const origin = remotes.find((r) => r.name === 'origin');
let remoteUrl = origin?.refs?.fetch || null;
if (remoteUrl && !remoteUrl.endsWith('.git')) {
remoteUrl = `${remoteUrl}.git`;
}
const branchSummary = await git.branch();
const currentBranch = branchSummary.current;
let branchExistsOnRemote = false;
if (currentBranch) {
try {
await git.fetch();
const remoteBranches = await git.branch(['-r']);
branchExistsOnRemote = remoteBranches.all.includes(
`origin/${currentBranch}`,
);
} catch (err) {
branchExistsOnRemote = false;
}
}
return {
remoteUrl,
currentBranch,
branchExistsOnRemote,
};
} catch (err: any) {
return null;
}
}
async getRepoInfo(repoPath: string): Promise<RepoInfoRes | null> {
const git = this.getGitInstance(repoPath);
try {
const remotes = await git.getRemotes(true);
const origin = remotes.find((r) => r.name === 'origin');
let remoteUrl = origin?.refs?.fetch || null;
const branchSummary = await git.branch();
const currentBranch = branchSummary.current;
let branchExistsOnRemote = false;
if (currentBranch) {
try {
await git.fetch();
const remoteBranches = await git.branch(['-r']);
branchExistsOnRemote = remoteBranches.all.includes(
`origin/${currentBranch}`,
);
} catch (err) {
branchExistsOnRemote = false;
}
}
return {
remoteUrl,
currentBranch,
branchExistsOnRemote,
};
} catch (err: any) {
return null;
}
}
🤖 Prompt for AI Agents
In @src/main/services/git.service.ts around lines 1144 - 1180, getRepoInfo
currently forces ".git" on remoteUrl (variable remoteUrl inside getRepoInfo),
which can produce incorrect addresses for some remotes; remove the conditional
that appends ".git" (or make it conditional on a new optional flag like
normalizeRemoteUrl) so the function returns the original fetch URL from
remotes.find(...).refs.fetch without modification, and adjust callers if they
relied on the forced ".git" suffix.

Comment on lines +28 to +41
const postJson = async (url: string, data?: object): Promise<any> => {
const apiKey = await this.getApiKey();
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: data ? JSON.stringify(data) : undefined,
});

return response.json();
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing error handling for non-OK responses in postJson.

The nested postJson helper function returns response.json() without checking response.ok. If the API returns an error status (4xx/5xx), this will still attempt to parse JSON which may lead to unexpected behavior or silent failures.

🔎 Proposed fix
     const postJson = async (url: string, data?: object): Promise<any> => {
       const apiKey = await this.getApiKey();
       const response = await fetch(url, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
           Accept: 'application/json',
           Authorization: `Bearer ${apiKey}`,
         },
         body: data ? JSON.stringify(data) : undefined,
       });

+      if (!response.ok) {
+        throw new Error(`Request failed: ${response.status}`);
+      }
+
       return response.json();
     };
🤖 Prompt for AI Agents
In @src/main/services/rosettaCloud.service.ts around lines 28 - 41, The postJson
helper (function postJson) currently returns response.json() without checking
response.ok; update it to check response.ok after fetch and, if false, read and
include the error body (prefer JSON if possible, fallback to text) along with
response.status/statusText in a thrown Error (or custom error) so callers can
handle failures; keep the Authorization logic using this.getApiKey() as-is and
ensure successful responses still return the parsed JSON.

Comment on lines +52 to +79
// Cloud execution state
const [runInCloudModal, setRunInCloudModal] = useState<DbtCommandType>();
const [cloudDbtArguments, setCloudDbtArguments] = useState<string>('');

const {
compile: dbtCompileModel,
run: dbtRunModel,
test: dbtTestModel,
isRunning: isRunningDbtModel,
list: dbtList,
build: dbtBuildModel,
} = useDbt();
} = useDbt(undefined, (command) => {
setRunInCloudModal(command);
});

// Helper function to handle cloud vs local execution
const executeCommand = async (
command: DbtCommandType,
localHandler: () => Promise<void>,
dbtArgs?: string,
) => {
if (environment === 'cloud') {
setCloudDbtArguments(dbtArgs || '');
setRunInCloudModal(command);
} else {
await localHandler();
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find src -name "*useDbt*" -type f

Repository: rosettadb/dbt-studio

Length of output: 92


🏁 Script executed:

rg -n "export.*useDbt|function useDbt" --type ts --type tsx src/

Repository: rosettadb/dbt-studio

Length of output: 91


🏁 Script executed:

cat -n src/renderer/components/dbtModelButtons/ModelSplitButton.tsx | head -100

Repository: rosettadb/dbt-studio

Length of output: 3848


🏁 Script executed:

cat -n src/renderer/hooks/useDbt.ts

Repository: rosettadb/dbt-studio

Length of output: 18121


🏁 Script executed:

cat -n src/renderer/components/dbtModelButtons/ModelSplitButton.tsx | sed -n '80,200p'

Repository: rosettadb/dbt-studio

Length of output: 4438


🏁 Script executed:

cat -n src/renderer/components/dbtModelButtons/ModelSplitButton.tsx | sed -n '200,350p'

Repository: rosettadb/dbt-studio

Length of output: 5777


🏁 Script executed:

cat -n src/renderer/components/dbtModelButtons/ModelSplitButton.tsx | sed -n '350,450p'

Repository: rosettadb/dbt-studio

Length of output: 4028


🏁 Script executed:

# Search for direct calls to dbtRunModel, dbtTestModel, dbtBuildModel outside of executeCommand wrapper
rg -n "dbtRunModel\(|dbtTestModel\(|dbtBuildModel\(" src/renderer/components/dbtModelButtons/ModelSplitButton.tsx

Repository: rosettadb/dbt-studio

Length of output: 768


🏁 Script executed:

# Also check if executeCommand helper is the ONLY way these handlers are called
rg -n "handleRunModel\|handleTestModel\|handleBuildModel" src/renderer/components/dbtModelButtons/ModelSplitButton.tsx

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

rg -n "useAppContext" src/renderer/hooks/useDbt.ts -A 2 -B 2

Repository: rosettadb/dbt-studio

Length of output: 470


🏁 Script executed:

# Check where ModelSplitButton is used to see if environment prop comes from the same source
rg -n "ModelSplitButton" src/renderer/components --type ts | head -20

Repository: rosettadb/dbt-studio

Length of output: 399


🏁 Script executed:

# Check useAppContext to understand env source
fd "appContext" src/renderer -type f | head -5

Repository: rosettadb/dbt-studio

Length of output: 234


🏁 Script executed:

find src/renderer -name "*context*" -o -name "*Context*" | head -10

Repository: rosettadb/dbt-studio

Length of output: 383


🏁 Script executed:

rg -n "createContext\|React.createContext" src/renderer --type ts

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Check where ModelSplitButton receives the environment prop
rg -n "environment=" src/renderer/components -B 2 -A 2 | head -40

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

cat src/renderer/hooks/useAppContext.ts

Repository: rosettadb/dbt-studio

Length of output: 221


🏁 Script executed:

# Check where ModelSplitButton is instantiated/used
rg -n "<ModelSplitButton" src/renderer --type ts -A 5 | head -60

Repository: rosettadb/dbt-studio

Length of output: 1215


🏁 Script executed:

sed -n '789,810p' src/renderer/screens/projectDetails/index.tsx

Repository: rosettadb/dbt-studio

Length of output: 1196


🏁 Script executed:

# Check the AppContext definition
cat src/renderer/context/*.tsx 2>/dev/null | head -100

Repository: rosettadb/dbt-studio

Length of output: 3145


🏁 Script executed:

sed -n '40,150p' src/renderer/context/index.tsx

Repository: rosettadb/dbt-studio

Length of output: 137


🏁 Script executed:

# Check if AppContext.env is ever updated based on settings
rg -n "env:" src/renderer/context -A 2 -B 2

Repository: rosettadb/dbt-studio

Length of output: 702


🏁 Script executed:

cat -n src/renderer/context/AppProvider.tsx | sed -n '160,175p'

Repository: rosettadb/dbt-studio

Length of output: 558


🏁 Script executed:

# Verify that both environment and env ultimately come from the same settings?.env
rg -n "settings\?.env" src/renderer --type ts | grep -E "(environment=|env:)" | head -10

Repository: rosettadb/dbt-studio

Length of output: 371


The two environment mechanisms don't double-trigger, but there's an inconsistent environment check that could cause unexpected behavior.

The handlers (e.g., handleRunModel) always route through the component's executeCommand helper, which checks the environment prop. When environment === 'cloud', it sets the modal state and returns early without calling dbtRunModel, so the useDbt callback never fires.

However, there is an inconsistency: the component receives environment={settings?.env} directly, while the useDbt hook reads env from AppContext, which applies fallback logic (profile ? (settings?.env ?? 'local') : 'local'). For an unauthenticated user with a cloud environment setting, these values could diverge, causing the component to open the cloud modal while the context would otherwise default to local execution.

To avoid confusion, ensure both the component and hook use the same source for environment determination—either both use the context value or both use the prop consistently.

🤖 Prompt for AI Agents
In @src/renderer/components/dbtModelButtons/ModelSplitButton.tsx around lines 52
- 79, The component and the hook are using different environment sources; align
them by reading the environment from AppContext in this component (like useDbt
does) instead of using the environment prop. Import and call
useContext(AppContext) to obtain the same env value/profile fallback, replace
all uses of the environment prop in executeCommand and handlers (e.g.,
executeCommand, handleRunModel) with that context-derived env, and remove
reliance on props?.env so the component and useDbt share the exact same
environment decision logic.

Comment on lines +244 to +247
updateSettings({
...settings!,
env: newEnv,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential crash if settings is undefined.

The non-null assertion settings! assumes settings is always defined when the switch is toggled. If useGetSettings hasn't resolved yet or returns undefined, this will crash.

🔎 Proposed fix
                     onChange={(event) => {
+                      if (!settings) return;
                       const newEnv = event.target.checked ? 'cloud' : 'local';
                       updateSettings({
-                        ...settings!,
+                        ...settings,
                         env: newEnv,
                       });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @src/renderer/components/menu/index.tsx around lines 244 - 247, The code uses
a non-null assertion on settings when calling updateSettings({...settings!, env:
newEnv}), which can crash if settings is undefined; change the call to guard
against undefined by either early-returning when settings is falsy or merging
with a safe default (e.g., updateSettings({ ...(settings ?? {}), env: newEnv }))
and ensure this logic lives where updateSettings, settings (from useGetSettings)
and newEnv are referenced so the switch toggle cannot dereference undefined.

Comment on lines +883 to +893
<TextField
value={env.key}
onChange={(e) =>
updateEnvironmentVariable(
env.id,
e.target.value,
env.value,
)
}
onFocus={() => handleEnvFocus(env.id)}
onBlur={() => handleEnvBlur(env.id)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Focus handler attached to key field may cause unintended behavior.

The handleEnvFocus and handleEnvBlur handlers (lines 892-893) are attached to the key field, but these handlers clear the value and track value edits. This means focusing on the key field will clear the value field and mark it as edited, which seems unintended.

🔎 Proposed fix

Remove focus/blur handlers from the key field—they should only be on the value field:

                         <TextField
                           value={env.key}
                           onChange={(e) =>
                             updateEnvironmentVariable(
                               env.id,
                               e.target.value,
                               env.value,
                             )
                           }
-                          onFocus={() => handleEnvFocus(env.id)}
-                          onBlur={() => handleEnvBlur(env.id)}
                           variant="outlined"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<TextField
value={env.key}
onChange={(e) =>
updateEnvironmentVariable(
env.id,
e.target.value,
env.value,
)
}
onFocus={() => handleEnvFocus(env.id)}
onBlur={() => handleEnvBlur(env.id)}
<TextField
value={env.key}
onChange={(e) =>
updateEnvironmentVariable(
env.id,
e.target.value,
env.value,
)
}
variant="outlined"
🤖 Prompt for AI Agents
In @src/renderer/components/modals/pushToCloudModal/index.tsx around lines 883 -
893, The key TextField is incorrectly wired to handleEnvFocus and handleEnvBlur
which clear and mark the value as edited; remove the onFocus and onBlur props
from the key TextField (the instance using value={env.key} and
updateEnvironmentVariable(env.id, e.target.value, env.value)) and ensure those
focus/blur handlers remain only on the value TextField (the one that edits
env.value) so focus behavior only affects the value field.

Comment on lines +117 to +149
export const useGetLocalChanges = (
path: string,
customOptions?: UseQueryOptions<
GitChangesRes | null,
CustomError,
GitChangesRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_LOCAL_CHANGES],
queryFn: async () => {
return gitServices.getLocalChanges(path);
},
...customOptions,
});
};

export const useGetRepoInfo = (
path: string,
customOptions?: UseQueryOptions<
RepoInfoRes | null,
CustomError,
RepoInfoRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_REPO_INFO],
queryFn: async () => {
return gitServices.getRepoInfo(path);
},
...customOptions,
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Query keys missing path parameter will cause incorrect caching across projects.

Both useGetLocalChanges and useGetRepoInfo pass path to the query function but don't include it in the queryKey. This means React Query will return cached results from one project when querying a different project, causing incorrect data to be displayed.

🔎 Proposed fix
 export const useGetLocalChanges = (
   path: string,
   customOptions?: UseQueryOptions<
     GitChangesRes | null,
     CustomError,
     GitChangesRes | null
   >,
 ) => {
   return useQuery({
-    queryKey: [QUERY_KEYS.GIT_LOCAL_CHANGES],
+    queryKey: [QUERY_KEYS.GIT_LOCAL_CHANGES, path],
     queryFn: async () => {
       return gitServices.getLocalChanges(path);
     },
     ...customOptions,
   });
 };

 export const useGetRepoInfo = (
   path: string,
   customOptions?: UseQueryOptions<
     RepoInfoRes | null,
     CustomError,
     RepoInfoRes | null
   >,
 ) => {
   return useQuery({
-    queryKey: [QUERY_KEYS.GIT_REPO_INFO],
+    queryKey: [QUERY_KEYS.GIT_REPO_INFO, path],
     queryFn: async () => {
       return gitServices.getRepoInfo(path);
     },
     ...customOptions,
   });
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useGetLocalChanges = (
path: string,
customOptions?: UseQueryOptions<
GitChangesRes | null,
CustomError,
GitChangesRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_LOCAL_CHANGES],
queryFn: async () => {
return gitServices.getLocalChanges(path);
},
...customOptions,
});
};
export const useGetRepoInfo = (
path: string,
customOptions?: UseQueryOptions<
RepoInfoRes | null,
CustomError,
RepoInfoRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_REPO_INFO],
queryFn: async () => {
return gitServices.getRepoInfo(path);
},
...customOptions,
});
};
export const useGetLocalChanges = (
path: string,
customOptions?: UseQueryOptions<
GitChangesRes | null,
CustomError,
GitChangesRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_LOCAL_CHANGES, path],
queryFn: async () => {
return gitServices.getLocalChanges(path);
},
...customOptions,
});
};
export const useGetRepoInfo = (
path: string,
customOptions?: UseQueryOptions<
RepoInfoRes | null,
CustomError,
RepoInfoRes | null
>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.GIT_REPO_INFO, path],
queryFn: async () => {
return gitServices.getRepoInfo(path);
},
...customOptions,
});
};
🤖 Prompt for AI Agents
In @src/renderer/controllers/git.controller.ts around lines 117 - 149, Both
hooks use a static queryKey which omits the path, causing cached responses to
bleed across repositories; update useGetLocalChanges and useGetRepoInfo to
include the path in their queryKey (e.g., append the path to
QUERY_KEYS.GIT_LOCAL_CHANGES and QUERY_KEYS.GIT_REPO_INFO) so the queryKey
uniquely identifies the repo, leaving the rest of the useQuery config and
queryFn (gitServices.getLocalChanges(path) / gitServices.getRepoInfo(path))
unchanged.

Comment on lines +76 to +85
export const useGetSecrets = (
projectId?: string,
options?: UseQueryOptions<Secret[], CustomError, Secret[]>,
) => {
return useQuery({
queryKey: [QUERY_KEYS.CLOUD_SECRETS],
queryFn: () => rosettaCloudServices.getSecrets(projectId ?? ''),
...options,
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Query key should include projectId for proper cache isolation.

The queryKey only includes QUERY_KEYS.CLOUD_SECRETS, but the query depends on projectId. If different projects are queried, React Query will return cached data from the previous project until the cache is invalidated.

🔎 Proposed fix
 export const useGetSecrets = (
   projectId?: string,
   options?: UseQueryOptions<Secret[], CustomError, Secret[]>,
 ) => {
   return useQuery({
-    queryKey: [QUERY_KEYS.CLOUD_SECRETS],
+    queryKey: [QUERY_KEYS.CLOUD_SECRETS, projectId],
     queryFn: () => rosettaCloudServices.getSecrets(projectId ?? ''),
+    enabled: !!projectId, // Only fetch when projectId is provided
     ...options,
   });
 };
🤖 Prompt for AI Agents
In @src/renderer/controllers/rosettaCloud.controller.ts around lines 76 - 85,
The query key for useGetSecrets must include the projectId to avoid
cross-project cache collisions; update the queryKey in the useGetSecrets
function (currently using QUERY_KEYS.CLOUD_SECRETS) to include the projectId
(e.g., [QUERY_KEYS.CLOUD_SECRETS, projectId ?? '']) so each project has an
isolated cache entry while keeping the existing queryFn that calls
rosettaCloudServices.getSecrets(projectId ?? '') and preserving passed options.

Comment on lines +227 to +230
if (env === 'cloud') {
cloudRunCb?.(command);
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

activeCommand is never reset when env === 'cloud'.

When the cloud path is taken, setActiveCommand(command) at line 225 sets the active command, but the early return bypasses the finally block (lines 265-267) that resets it to null. This leaves activeCommand stuck indefinitely.

🔎 Proposed fix
       if (env === 'cloud') {
         cloudRunCb?.(command);
+        setActiveCommand(null);
         return;
       }
🤖 Prompt for AI Agents
In @src/renderer/hooks/useDbt.ts around lines 227 - 230, When env === 'cloud'
the early return prevents the finally block from clearing activeCommand, leaving
the state stuck; update the cloud branch in the run flow (in useDbt) to ensure
setActiveCommand(null) is invoked before returning — either call
setActiveCommand(null) immediately after cloudRunCb?.(command) or refactor so
the finally block always runs (e.g., avoid returning early in the try and
instead short-circuit after performing cleanup). Target symbols: activeCommand,
setActiveCommand, cloudRunCb, and the run function in useDbt to apply the fix.

Comment on lines +16 to +18
emailVerified: Date | null;
createdAt: Date;
updatedAt: Date;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the profile.ts file to confirm the current state
cat -n src/types/profile.ts

Repository: rosettadb/dbt-studio

Length of output: 919


🏁 Script executed:

# Search for profile service and how data is fetched
fd -t f 'profile.service' src/ && find src/ -name '*profile*.service*' -o -name '*profile*service*'

Repository: rosettadb/dbt-studio

Length of output: 145


🏁 Script executed:

# Look for any date transformation utilities
rg -n 'new Date|Date\.parse|toDate|parseDate|dateTransform' --type ts src/ -A 2

Repository: rosettadb/dbt-studio

Length of output: 38838


🏁 Script executed:

# Check how these fields are actually used in the codebase
rg -n 'emailVerified|createdAt|updatedAt' --type ts src/ -B 2 -A 2 | head -100

Repository: rosettadb/dbt-studio

Length of output: 5571


🏁 Script executed:

cat -n src/renderer/services/profile.service.ts

Repository: rosettadb/dbt-studio

Length of output: 1036


🏁 Script executed:

# Also check for any API response interceptors or transformers
rg -n 'axios|fetch|interceptor|transform' src/renderer/services/profile.service.ts -A 3 -B 1

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Check the actual API endpoint and response handling
rg -n 'getProfile|ProfileResponse|UserProfile' src/renderer/services/profile.service.ts -A 5

Repository: rosettadb/dbt-studio

Length of output: 826


🏁 Script executed:

# Check the client configuration for response interceptors or transformers
cat -n src/renderer/config/client.ts

Repository: rosettadb/dbt-studio

Length of output: 987


🏁 Script executed:

# Search for any IPC or response interceptor patterns that might handle date transformation
rg -n 'ipcRenderer|transform|Date|parse' src/renderer/config/ -A 2 -B 1

Repository: rosettadb/dbt-studio

Length of output: 1749


🏁 Script executed:

# Check how the profile data is used in components to understand if dates are expected as Date objects
rg -n 'emailVerified|profile\.createdAt|profile\.updatedAt' src/renderer/ -A 2 -B 1

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Check the main process to see how rosettaCloud:getProfile is implemented
rg -n 'rosettaCloud.*getProfile|getProfile.*Rosetta' src/main/ -A 5

Repository: rosettadb/dbt-studio

Length of output: 596


🏁 Script executed:

# Search for the actual IPC handler for getProfile
rg -n 'handle.*getProfile|getProfile.*handle' src/main/ -A 5

Repository: rosettadb/dbt-studio

Length of output: 596


🏁 Script executed:

# Check the rosettaCloud.service for profile fetching
rg -n 'getProfile|class.*Cloud|UserProfile' src/main/services/rosettaCloud.service.ts -A 5 | head -80

Repository: rosettadb/dbt-studio

Length of output: 1247


🏁 Script executed:

# Get the full implementation of getProfile in RosettaCloudService
sed -n '157,197p' src/main/services/rosettaCloud.service.ts

Repository: rosettadb/dbt-studio

Length of output: 1220


🏁 Script executed:

# Check if there's any JSON.parse or date transformation happening during profile retrieval
rg -n 'JSON.parse|new Date|Date\.parse|parseISO' src/main/services/rosettaCloud.service.ts -A 2 -B 2

Repository: rosettadb/dbt-studio

Length of output: 329


🏁 Script executed:

# Look for the actual API response and how it's handled
rg -n 'fetch.*getProfile|axios.*profile|response.*profile' src/main/services/rosettaCloud.service.ts -A 5

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Check how profile is used in renderer components - look for date operations
rg -n 'useProfile|profile\.|getProfile\|refreshProfile' src/renderer/ --type tsx --type ts -A 2 | grep -A 2 'createdAt\|updatedAt\|emailVerified' | head -40

Repository: rosettadb/dbt-studio

Length of output: 91


🏁 Script executed:

# Look for any custom hooks or context that might transform the profile data
fd -t f 'useProfile\|ProfileContext\|ProfileProvider' src/renderer/

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Check if there are any usages in React components that indicate how the dates are being used
rg -n 'profile\.createdAt\|profile\.updatedAt\|profile\.emailVerified' src/renderer/screens/ src/renderer/components/ -A 2

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Search more broadly for profile usage in all components
rg -n 'profile\.' src/renderer/components/ src/renderer/screens/ src/renderer/controllers/ -A 1 | head -80

Repository: rosettadb/dbt-studio

Length of output: 1717


🏁 Script executed:

# Check if profile context or hooks exist
rg -n 'useAuth|useProfile|ProfileContext|useUser' src/renderer/ -A 3 | head -60

Repository: rosettadb/dbt-studio

Length of output: 4135


🏁 Script executed:

# Look for any date parsing or usage of profile date fields
rg -n 'toLocaleDateString|toISOString|getTime|moment.*profile' src/renderer/ -B 2 -A 1 | head -40

Repository: rosettadb/dbt-studio

Length of output: 3299


🏁 Script executed:

# Check profile.controller.ts to see if useProfile does any transformation
cat -n src/renderer/controllers/profile.controller.ts

Repository: rosettadb/dbt-studio

Length of output: 3996


🏁 Script executed:

# Look for any query transformers or response processors in React Query setup
rg -n 'queryClient|QueryClient|transformer|select' src/renderer/controllers/profile.controller.ts -A 3

Repository: rosettadb/dbt-studio

Length of output: 1166


🏁 Script executed:

# Check if the profile date fields are ever used with Date methods anywhere
rg -n 'profile\.createdAt\.|profile\.updatedAt\.|profile\.emailVerified\.' src/renderer/ -A 2

Repository: rosettadb/dbt-studio

Length of output: 46


🏁 Script executed:

# Also check how the profile object is serialized/used to understand if this causes actual issues
rg -n 'JSON\.stringify.*profile|profileService\|useProfile' src/renderer/components/settings/ src/renderer/context/ -A 3

Repository: rosettadb/dbt-studio

Length of output: 46


Ensure Date fields are properly transformed when parsing API responses, or adjust type definitions.

emailVerified, createdAt, and updatedAt are typed as Date in the UserProfile interface, but JSON API responses deserialize these fields as ISO strings. The profile service returns the raw API response without transformation, creating a type-to-runtime mismatch. If components attempt to call Date methods on these fields (e.g., .getTime(), .toLocaleDateString()), they will fail at runtime.

Either add date parsing in RosettaCloudService.getProfile() to convert these fields to Date objects, or update the type definitions to reflect that they arrive as strings.

🤖 Prompt for AI Agents
In @src/types/profile.ts around lines 16 - 18, UserProfile declares
emailVerified, createdAt and updatedAt as Date but
RosettaCloudService.getProfile() returns raw JSON ISO strings; fix by parsing
those ISO strings into Date objects inside RosettaCloudService.getProfile()
(convert emailVerified nullable ISO to Date|null and createdAt/updatedAt to
Date) before returning, or alternatively change UserProfile types to string (and
emailVerified to string|null) and update all callers that use Date methods;
reference UserProfile and RosettaCloudService.getProfile() when making the
change so callers like components expecting .getTime() or .toLocaleDateString()
are updated accordingly.

Copy link

@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 @.github/workflows/publish.yml:
- Line 8: Remove the temporary feature branch entry "feature/authentication"
from the publish workflow triggers: edit the publish.yml workflow's
push/branches array and delete the "feature/authentication" entry so the
workflow only triggers on the intended branches (e.g., dev/main); ensure no
leftover references to that branch remain before merging.
🧹 Nitpick comments (2)
release/app/package.json (1)

3-3: Consider a minor version bump for new features.

This PR introduces significant new functionality (Rosetta Cloud integration, authentication, API key storage, Git repository status, deployment features). Per semver conventions, new backwards-compatible features typically warrant a MINOR version increment (e.g., 1.3.0) rather than a PATCH increment (1.2.7).

If the project follows a different versioning policy, feel free to disregard.

src/types/backend.ts (1)

205-214: Consider consistent naming convention for CUSTOM_DBT_COMMANDS.

The field CUSTOM_DBT_COMMANDS uses SCREAMING_SNAKE_CASE while all other fields in this type use camelCase (gitUrl, gitBranch, githubUsername, etc.). Consider renaming to customDbtCommands for consistency.

Suggested change
 export type CloudDeploymentPayload = {
   id: string;
   title: string;
   gitUrl: string;
   gitBranch: string;
   githubUsername?: string;
   githubPassword?: string;
   secrets: Record<string, string>;
-  CUSTOM_DBT_COMMANDS?: string;
+  customDbtCommands?: string;
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f079aab and 737596f.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • release/app/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • .github/workflows/publish.yml
  • package.json
  • release/app/package.json
  • src/renderer/screens/projectDetails/index.tsx
  • src/types/backend.ts
  • src/types/ipc.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/ipc.ts
🔇 Additional comments (7)
src/renderer/screens/projectDetails/index.tsx (2)

796-804: LGTM!

The environment prop is appropriately passed to ModelSplitButton, enabling environment-aware behavior (cloud vs local). The optional chaining on settings?.env correctly handles cases where settings may be undefined.


806-817: LGTM!

The environment prop is consistently passed to ProjectDbtSplitButton, matching the pattern used for ModelSplitButton. This enables the component to internally manage cloud vs local execution paths based on the current environment setting.

src/types/backend.ts (5)

201-202: LGTM!

The new optional fields externalId and lastRun are well-typed and consistent with the existing patterns in the Project type (e.g., createdAt: string).


280-281: LGTM!

The env field with literal union type 'local' | 'cloud' is a clean approach for environment switching. Being optional allows backward compatibility.


738-745: LGTM!

Well-structured type with both boolean flags for quick checks and numeric counts for detailed information. The naming is clear and self-documenting.


747-751: LGTM!

The type correctly handles the case where a repository might not have a remote configured by making remoteUrl nullable.


753-757: Verify plaintext transmission of RosettaCloud secrets over IPC.

The Secret type values are fetched from the RosettaCloud API and transmitted to the renderer as plaintext over the IPC channel via rosettaCloud:getSecrets. If these secrets contain sensitive credentials, consider whether they should be:

  • Encrypted before IPC transmission, or
  • Kept in the main process and only referenced by ID in the renderer

Note: Local cloud credentials (AWS, Azure, GCS) are properly secured via secureStorageService.

branches:
- main
- release/dev
- feature/authentication
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove feature branch from publish triggers before merging.

Adding a feature branch to the publish workflow will create draft releases from unfinished code. This appears to be a temporary addition for testing purposes. Ensure this line is removed before merging to dev to avoid:

  • Orphan draft releases from the feature branch
  • Unnecessary CI/CD resource consumption (macOS runners, code signing)
  • Potential release tag conflicts if the version matches an existing release
🤖 Prompt for AI Agents
In @.github/workflows/publish.yml at line 8, Remove the temporary feature branch
entry "feature/authentication" from the publish workflow triggers: edit the
publish.yml workflow's push/branches array and delete the
"feature/authentication" entry so the workflow only triggers on the intended
branches (e.g., dev/main); ensure no leftover references to that branch remain
before merging.

Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/ipcHandlers/git.ipcHandlers.ts (1)

13-35: ⚠️ Potential issue | 🟠 Major

Add new channels to handlerChannels to avoid duplicate handler registration.

removeGitIpcHandlers() won’t remove the newly added handlers, so re-registering can throw due to existing handlers.

Proposed fix
 const handlerChannels = [
   'git:init',
   'git:clone',
   'git:listBranches',
   'git:checkout',
   'git:addRemote',
   'git:isInitialized',
   'git:getRemotes',
   'git:add',
   'git:unstage',
   'git:stageAll',
   'git:unstageAll',
   'git:discardChanges',
   'git:commit',
   'git:pull',
   'git:push',
   'git:fileDiff',
   'git:fileStatusList',
   'git:aheadBehind',
   'git:createBranch',
   'git:deleteBranch',
   'git:renameBranch',
+  'git:getLocalChanges',
+  'git:repoInfo',
 ];
🧹 Nitpick comments (1)
src/renderer/components/dbtModelButtons/ModelSplitButton.tsx (1)

727-731: Avoid disabling eslint by renaming the unused property.

You can avoid the inline eslint disable by aliasing the unused key.

🛠️ Suggested tweak
-        menuItems={filteredMenuItems.map((item) => {
-          // eslint-disable-next-line `@typescript-eslint/no-unused-vars`
-          const { localOnly, ...menuItem } = item;
-          return menuItem;
-        })}
+        menuItems={filteredMenuItems.map((item) => {
+          const { localOnly: _localOnly, ...menuItem } = item;
+          return menuItem;
+        })}

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