[PR #5256] [CLOSED] feat(desktop): portable mode foundation phase-1 #5126

Closed
opened 2026-03-17 02:36:16 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/hoppscotch/hoppscotch/pull/5256
Author: @CuriousCorrelation
Created: 7/18/2025
Status: Closed

Base: nextHead: desktop-feat-portable-app-phase-1-foundations


📝 Commits (2)

  • bdd2b57 feat(desktop): portable mode foundation phase-1
  • 0453f1a docs: typo

📊 Changes

62 files changed (+4233 additions, -1834 deletions)

View changed files

📝 packages/hoppscotch-common/src/components.d.ts (+6 -5)
📝 packages/hoppscotch-common/src/kernel/index.ts (+1 -1)
📝 packages/hoppscotch-common/src/platform/instance.ts (+241 -0)
📝 packages/hoppscotch-data/package.json (+1 -1)
packages/hoppscotch-desktop/.eslintrc.cjs (+67 -0)
📝 packages/hoppscotch-desktop/.gitignore (+4 -0)
📝 packages/hoppscotch-desktop/package.json (+22 -4)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/build.rs (+1 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.cjs (+4 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.d.ts (+7 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.d.ts.map (+1 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.js (+9 -6)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/examples/tauri-app/src/main.js (+4 -4)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/examples/tauri-app/vite.config.js (+10 -8)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/guest-js/index.ts (+20 -6)
packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/autogenerated/commands/close.toml (+13 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/autogenerated/reference.md (+26 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/default.toml (+1 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/schemas/schema.json (+10 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/rollup.config.js (+13 -13)

...and 42 more files

📄 Description

This is first in a series of architectural overhauls for the Desktop app
to support portable and organizational instances, starting by
transitioning from legacy LazyStore-based data management to a common
kernel-unified persistence, cross-platform path management, automated
data migration, and other foundational portable mode support.

Closes FE-929
Closes FE-930
Closes FE-931
Closes FE-932
Closes FE-933
Closes FE-934
Closes FE-935
Closes FE-936
Closes FE-937
Closes FE-938
Closes FE-939

The desktop app right now has a rather fragmented data storage, direct
LazyStore usage throughout the codebase, and no unified system for
cross-platform path management.

Also lack of proper linting infrastructure that exists in
hoppscotch-common or hoppscotch-selfhost-web, etc.

This refactoring addresses these issues and also establishes some
patterns for future development; preparing the groundwork for portable
mode implementation.

This is a first in a series of foundational PRs so this one contains a
ton of inert code that establishes infrastructure patterns, but doesn't
actually do anything substantial from the get-go.

So the currently active code maintains full backward compatibility with
existing user data and workflows to be routed through the migration
systems in the subsequent phases.

Kernel Integration

Added foundational kernel integration that wraps existing platform calls with
unified operations across the app. Created desktop-specific
kernel modules in src/kernel/ including index.ts, io.ts,
relay.ts, and store.ts that provide identical interfaces (as to
hoppscotch-common's kernel directory') for IO ops, requests via
Relay, and data store ops.

// src/kernel/index.ts - Central access
export const getModule = <K extends keyof KernelAPI>(name: K): NonNullable<KernelAPI[K]> => {
  const kernel = window.__KERNEL__
  if (!kernel?.[name]) throw new Error(`Kernel ${String(name)} not initialized`)
  return kernel[name]
}

// src/kernel/store.ts - Dynamic path resolution
const getStorePath = async (): Promise<string> => {
  try {
    const instanceDir = await getInstanceDir()
    return join(instanceDir, STORE_PATH)
  } catch (error) {
    console.error("Failed to get instance directory:", error)
    return "hoppscotch-unified.store"
  }
}

Initialized kernel in main.ts with initKernel("desktop") and added
TypeScript definitions for kernel globals in src/types/kernel.d.ts.
The kernel modules currently wrap existing functionality and provide a
foundation for future platform-agnostic development without changing
external behavior.

Backward Compatibility: All existing functionality continues to work
unchanged. The kernel system provides wrappers around existing platform
calls without modifying external behavior.

Unified Persistence Service

Created DesktopPersistenceService in
src/services/persistence.service.ts as centralized persistence layer
actively replacing direct LazyStore usage throughout the codebase. The service
provides typed methods for common operations with schema validation
using Zod and reactive updates through watchers.

// Typed persistence operations with error handling
async setUpdateState(state: UpdateState): Promise<void> {
  const result = await Store.set(STORE_NAMESPACE, STORE_KEYS.UPDATE_STATE, state)
  if (E.isLeft(result)) {
    console.error("Failed to save update state:", result.left)
  }
}

async watchUpdateState(handler: (state: UpdateState) => void): Promise<() => void> {
  const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.UPDATE_STATE)
  return watcher.on("change", ({ value }: { value?: unknown }) => {
    if (value) handler(value as UpdateState)
  })
}

The service includes schema validation using Zod, implements
watchers for reactive state updates, and uses namespace
hoppscotch-desktop.v1 for data storage. Added support for
portable settings, recent instances management, and connection state
tracking with active usage in the updater and home view components.

Backward Compatibility: The persistence service maintains the same
external API contracts while using the kernel store system internally.
All existing data access patterns continue to function unchanged.

Path Management System

Implemented path resolution functions in src-tauri/src/path.rs
supporting both standard and portable installation modes. The system handles platform-specific paths following Tauri conventions, creates
directory structure automatically, and exposes paths to frontend via
Tauri commands.

// Platform-aware path resolution with portable mode support
fn platform_config_dir() -> PathBuf {
    if cfg!(feature = "portable") {
        return std::env::current_dir()
            .unwrap_or_else(|_| std::env::temp_dir())
            .join("hoppscotch-desktop-data");
    }

    #[cfg(target_os = "macos")]
    {
        dirs::home_dir()
            .map(|dir| dir.join("Library/Application Support").join(APP_ID))
            .unwrap_or_else(|| std::env::temp_dir().join(APP_ID))
    }

    #[cfg(target_os = "windows")]
    {
        dirs::config_dir()
            .map(|dir| dir.join(APP_ID))
            .unwrap_or_else(|| std::env::temp_dir().join(APP_ID))
    }
}

Currently implemented Tauri commands include get_config_dir and
get_logs_dir that provide dynamic path resolution to the frontend. The
system automatically creates directories when needed and provides
fallback behavior for permission issues. Currently, logging and kernel
store functionality actively use these paths, while other directory
functions are prepared for future migration phases.

Note: Additional path functions (get_instance_dir, get_store_dir,
get_backup_dir, etc.) are referenced in frontend code but not yet
implemented as Tauri commands in the Rust backend.

Backward Compatibility: Standard mode uses identical paths to the
previous system. Portable mode is additive functionality that doesn't
affect existing installations.

Native Dialog System

Created cross-platform dialog system in src-tauri/src/dialog.rs using
native-dialog crate for critical notifications that work independently
of webview. Includes panic, info, warn, error, and confirm
functions with appropriate message types and logging integration.

pub fn confirm(title: &str, msg: &str, icon: MessageType) -> bool {
    MessageDialog::new()
        .set_type(icon)
        .set_title(title)
        .set_text(msg)
        .show_confirm()
        .unwrap_or_default()
}

The dialog system provides platform-appropriate native dialogs for
critical system messages, error reporting, and user confirmations with
active usage in portable WebView2 installation prompts. All
dialog operations include logging and fallback handling for display
failures. This part is mainly aimed at portable version where we cannot
rely on WebView2 being present by default (handled by the installer).
See FE-624.

Backward Compatibility: Dialog system is additive functionality used
for new error handling scenarios. Existing error handling mechanisms
continue to function unchanged.

ESLint Config / Linting Infra

Added ESLint setup in .eslintrc.cjs with TypeScript and
Vue support, configured rules to prevent localStorage misuse, and added
specific restrictions on direct localStorage access to encourage proper
persistence patterns.

// localStorage usage restrictions
"no-restricted-globals": [
  "error", {
    name: "localStorage",
    message: "Do not use 'localStorage' directly. Please use
    localpersistence.ts functions or stores",
  },
],
"no-restricted-syntax": [
  "error", {
    selector: "CallExpression[callee.object.property.name='localStorage']",
    message: "Do not use 'localStorage' directly. Please use
    localpersistence.ts functions or stores",
  },
],

Added linting scripts to package.json including lint, lint:ts,
lintfix, and prod-lint for different development scenarios.
Identical to what is already in hoppscotch-selfhost-web or
hoppscotch-common.

Build Config Updates

Updated package.json scripts and Cargo.toml for portable mode
support with feature flags and proper feature gating. Updated build.rs
to handle different config files based on features and separated
portable and standard build commands.

// Feature-gated build configuration
fn main() {
    #[cfg(feature = "portable")]
    {
        println!("cargo:rerun-if-changed=tauri.portable.conf.json");
    }

    #[cfg(not(feature = "portable"))]
    {
        println!("cargo:rerun-if-changed=tauri.conf.json");
    }

    tauri_build::build()
}

Added new package scripts: dev:portable, build:portable,
prepare-web, and updated existing scripts for development
workflow. Created separate Tauri configuration file for portable
builds with appropriate bundling settings that disable automatic updater
artifacts for portable distributions.

Backward Compatibility: Standard build process remains identical.
Portable mode builds are new functionality that doesn't affect existing
build pipelines.

Directory Organization Structure

Defined new directory structure functions with clear separation between
current working data and logs, though most remain unused in this phase:

├── latest/          # Current working data (prepared)
│   ├── instance/    # Instance-related files (will be used for kernel store)
│   └── store/       # Store files (prepared for future migration)
└── logs/            # app logs (actively used, platform-specific location)

The path management functions automatically create directories when
needed and provide the foundation for future migration system that will
move existing data to this new structure while maintaining full backward
compatibility. Currently only logging and kernel store paths are
actively utilized, with other directory functions prepared for
subsequent migration phases.

Updater Service Refactor

Refactored UpdaterService from src/utils/updater.ts to
src/services/updater.service.ts using new persistence layer instead of
direct LazyStore access. The service now uses
DesktopPersistenceService for state management, provides
progress tracking during downloads, and includes error
handling.

// Progress tracking with persistence integration
await updateResult.downloadAndInstall((event: DownloadEvent) => {
  if (event.event === "Started") {
    totalBytes = event.data.contentLength
    downloadedBytes = 0
  } else if (event.event === "Progress") {
    downloadedBytes += event.data.chunkLength
    this.currentProgress = { downloaded: downloadedBytes, total: totalBytes }
  } else if (event.event === "Finished") {
    this.persistence.setUpdateState({ status: UpdateStatus.INSTALLING })
  }
})

The refactored service separates update logic from UI concerns and
provides state management through the persistence
layer with integration in the home view component.

Backward Compatibility: Update functionality remains identical from
user perspective. Internal architecture changes don't affect
external behavior.

Cross-Platform Util Functions

Implemented platform-specific utilities in src-tauri/src/util.rs for
common operations like opening external links. The open_link function
handles Windows, macOS, and Linux with appropriate platform commands and
proper error handling, currently used by the portable updater for opening
download pages.

pub fn open_link(link: &str) -> Option<()> {
    #[cfg(target_os = "windows")]
    {
        Command::new("rundll32")
            .args(["url.dll,FileProtocolHandler", link])
            .stdout(Stdio::null())
            .spawn()
            .ok()
            .map(|_| ())
    }

    #[cfg(target_os = "macos")]
    {
        Command::new("open")
            .arg(link)
            .stdout(Stdio::null())
            .spawn()
            .ok()
            .map(|_| ())
    }

    #[cfg(target_os = "linux")]
    {
        Command::new("xdg-open")
            .arg(link)
            .stdout(Stdio::null())
            .spawn()
            .ok()
            .map(|_| ())
    }
}

Added platform detection using cfg attributes, error handling
with Option return types, and stdout redirection to prevent console
output pollution.

Backward Compatibility: Utility functions are additive
functionality that adds capabilities without modifying
current behavior.

Rust Backend Restructuring

Reorganized Rust backend with modular structure including
separate modules for each concern: config.rs,
dialog.rs, error.rs, path.rs, updater.rs, util.rs, and
webview/mod.rs. Error handling with custom HoppError type
using thiserror crate for error reporting and debugging.

// Centralized error handling with detailed context
pub enum HoppError {
    #[error("IO error: {0}")]
    Io(#[from] io::Error),

    #[error("Failed to initialize server port")]
    ServerPortInitialization,

    #[error("Failed to emit event: {0}")]
    EventEmission(String),

    #[error("Tauri error: {0}")]
    Tauri(#[from] tauri::Error),
}

Added new dependencies including thiserror for error handling,
native-dialog for cross-platform dialogs, tauri-plugin-http and
tauri-plugin-opener for functionality, semver for
version management, and tempfile/winreg for Windows-specific portable
WebView2 handling. The modular structure separates concerns.

Backward Compatibility: Backend restructuring maintains all existing
API contracts. Internal organization changes won't affect external
functionality.

Frontend Architecture Changes

Replaced direct LazyStore usage throughout frontend with
service-oriented architecture. Updated src/views/Home.vue to actively use new
persistence and migration services while maintaining identical user
experience. Implemented reactive state management using RxJS observables
and Vue composables.

// Service integration in Home.vue maintaining existing UX
const setupUpdateStateWatcher = async () => {
  return persistence.watchUpdateState((newValue) => {
    if (!newValue) return

    updateStatus.value = newValue.status
    updateMessage.value = newValue.message || ""

    if (newValue.status === UpdateStatus.AVAILABLE) {
      appState.value = AppState.UPDATE_AVAILABLE
    }
    // Handle other states...
  })
}

The frontend now initializes migration service and uses new updater
service architecture while preserving the exact same user interface and
interaction patterns.

Backward Compatibility: User experience remains unchanged. All UI
interactions, workflows, and visual elements function identically to
previous versions.

Portable Mode Foundation

Established foundation for portable mode functionality with
feature-gated compilation supporting both standard and portable modes.
In portable mode, the app uses relative paths from executable
directory instead of system directories, stores all data within
app directory, and includes WebView runtime checking for
Windows.

// Portable mode path resolution
if cfg!(feature = "portable") {
    return std::env::current_dir()
        .unwrap_or_else(|_| std::env::temp_dir())
        .join("hoppscotch-desktop-data");
}

Created separate Tauri configuration for portable
builds with appropriate bundling
settings. Added WebView2 installation checking and automatic
installation for Windows portable builds using registry detection
following Microsoft's official documentation.

Backward Compatibility: Portable mode is entirely new functionality
that doesn't affect existing standard installations. Standard mode
behavior remains unchanged.

Data Migration Strategy

The implementation includes migration strategy framework that
will detect existing data from legacy store files, migrate connection state
and recent instances to new kernel store, move .hoppscotch.store
files to organized directory structure, create version-based backups
before making changes, and clean up legacy files after successful
migration. Currently the migration system includes placeholder functions
prepared for future phases.

Migration will occur automatically on first startup with the new version and
includes logging, error handling, and rollback capabilities.

Backward Compatibility: Migration framework preserves all existing
user data, settings, and workflows. Users experience no disruption
during the upgrade process and can continue using the app exactly as
before.

Foundational Infrastructure

Much of the code in this PR is currently inert but establishes the
architectural foundation for future features including portable
mode implementation, cross-platform support, and data
management capabilities.

Notes to reviewers

Testing should verify data integrity in different user scenarios,
cross-platform functionality on Windows/macOS/Linux, portable mode
resolution in CI and general data storage + cleanup, dialog system
functionality on all platforms, and backward compatibility with existing
user data and workflows.

Closing Note

Closing in favor of more granular phased approach starting from
https://github.com/hoppscotch/hoppscotch/pull/5259 for better backwards compatibility and stable releases.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/hoppscotch/hoppscotch/pull/5256 **Author:** [@CuriousCorrelation](https://github.com/CuriousCorrelation) **Created:** 7/18/2025 **Status:** ❌ Closed **Base:** `next` ← **Head:** `desktop-feat-portable-app-phase-1-foundations` --- ### 📝 Commits (2) - [`bdd2b57`](https://github.com/hoppscotch/hoppscotch/commit/bdd2b570c5de7df4d96e9131a5c3024b46b3d5c4) feat(desktop): portable mode foundation phase-1 - [`0453f1a`](https://github.com/hoppscotch/hoppscotch/commit/0453f1ac9f37745a2110819d2b0405b689025d0d) docs: typo ### 📊 Changes **62 files changed** (+4233 additions, -1834 deletions) <details> <summary>View changed files</summary> 📝 `packages/hoppscotch-common/src/components.d.ts` (+6 -5) 📝 `packages/hoppscotch-common/src/kernel/index.ts` (+1 -1) 📝 `packages/hoppscotch-common/src/platform/instance.ts` (+241 -0) 📝 `packages/hoppscotch-data/package.json` (+1 -1) ➕ `packages/hoppscotch-desktop/.eslintrc.cjs` (+67 -0) 📝 `packages/hoppscotch-desktop/.gitignore` (+4 -0) 📝 `packages/hoppscotch-desktop/package.json` (+22 -4) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/build.rs` (+1 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.cjs` (+4 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.d.ts` (+7 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.d.ts.map` (+1 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/dist-js/index.js` (+9 -6) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/examples/tauri-app/src/main.js` (+4 -4) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/examples/tauri-app/vite.config.js` (+10 -8) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/guest-js/index.ts` (+20 -6) ➕ `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/autogenerated/commands/close.toml` (+13 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/autogenerated/reference.md` (+26 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/default.toml` (+1 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/permissions/schemas/schema.json` (+10 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-appload/rollup.config.js` (+13 -13) _...and 42 more files_ </details> ### 📄 Description This is first in a series of architectural overhauls for the Desktop app to support portable and organizational instances, starting by transitioning from legacy LazyStore-based data management to a common kernel-unified persistence, cross-platform path management, automated data migration, and other foundational portable mode support. Closes FE-929 Closes FE-930 Closes FE-931 Closes FE-932 Closes FE-933 Closes FE-934 Closes FE-935 Closes FE-936 Closes FE-937 Closes FE-938 Closes FE-939 The desktop app right now has a rather fragmented data storage, direct LazyStore usage throughout the codebase, and no unified system for cross-platform path management. Also lack of proper linting infrastructure that exists in `hoppscotch-common` or `hoppscotch-selfhost-web`, etc. This refactoring addresses these issues and also establishes some patterns for future development; preparing the groundwork for portable mode implementation. This is a first in a series of foundational PRs so this one contains a ton of inert code that establishes infrastructure patterns, but doesn't actually do anything substantial from the get-go. So the currently active code maintains full backward compatibility with existing user data and workflows to be routed through the migration systems in the subsequent phases. ### Kernel Integration Added foundational kernel integration that wraps existing platform calls with unified operations across the app. Created desktop-specific kernel modules in `src/kernel/` including `index.ts`, `io.ts`, `relay.ts`, and `store.ts` that provide identical interfaces (as to `hoppscotch-common`'s `kernel` directory') for IO ops, requests via Relay, and data store ops. ```typescript // src/kernel/index.ts - Central access export const getModule = <K extends keyof KernelAPI>(name: K): NonNullable<KernelAPI[K]> => { const kernel = window.__KERNEL__ if (!kernel?.[name]) throw new Error(`Kernel ${String(name)} not initialized`) return kernel[name] } // src/kernel/store.ts - Dynamic path resolution const getStorePath = async (): Promise<string> => { try { const instanceDir = await getInstanceDir() return join(instanceDir, STORE_PATH) } catch (error) { console.error("Failed to get instance directory:", error) return "hoppscotch-unified.store" } } ``` Initialized kernel in `main.ts` with `initKernel("desktop")` and added TypeScript definitions for kernel globals in `src/types/kernel.d.ts`. The kernel modules currently wrap existing functionality and provide a foundation for future platform-agnostic development without changing external behavior. **Backward Compatibility**: All existing functionality continues to work unchanged. The kernel system provides wrappers around existing platform calls without modifying external behavior. ### Unified Persistence Service Created `DesktopPersistenceService` in `src/services/persistence.service.ts` as centralized persistence layer actively replacing direct LazyStore usage throughout the codebase. The service provides typed methods for common operations with schema validation using Zod and reactive updates through watchers. ```typescript // Typed persistence operations with error handling async setUpdateState(state: UpdateState): Promise<void> { const result = await Store.set(STORE_NAMESPACE, STORE_KEYS.UPDATE_STATE, state) if (E.isLeft(result)) { console.error("Failed to save update state:", result.left) } } async watchUpdateState(handler: (state: UpdateState) => void): Promise<() => void> { const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.UPDATE_STATE) return watcher.on("change", ({ value }: { value?: unknown }) => { if (value) handler(value as UpdateState) }) } ``` The service includes schema validation using Zod, implements watchers for reactive state updates, and uses namespace `hoppscotch-desktop.v1` for data storage. Added support for portable settings, recent instances management, and connection state tracking with active usage in the updater and home view components. **Backward Compatibility**: The persistence service maintains the same external API contracts while using the kernel store system internally. All existing data access patterns continue to function unchanged. ### Path Management System Implemented path resolution functions in `src-tauri/src/path.rs` supporting both standard and portable installation modes. The system handles platform-specific paths following Tauri conventions, creates directory structure automatically, and exposes paths to frontend via Tauri commands. ```rust // Platform-aware path resolution with portable mode support fn platform_config_dir() -> PathBuf { if cfg!(feature = "portable") { return std::env::current_dir() .unwrap_or_else(|_| std::env::temp_dir()) .join("hoppscotch-desktop-data"); } #[cfg(target_os = "macos")] { dirs::home_dir() .map(|dir| dir.join("Library/Application Support").join(APP_ID)) .unwrap_or_else(|| std::env::temp_dir().join(APP_ID)) } #[cfg(target_os = "windows")] { dirs::config_dir() .map(|dir| dir.join(APP_ID)) .unwrap_or_else(|| std::env::temp_dir().join(APP_ID)) } } ``` Currently implemented Tauri commands include `get_config_dir` and `get_logs_dir` that provide dynamic path resolution to the frontend. The system automatically creates directories when needed and provides fallback behavior for permission issues. Currently, logging and kernel store functionality actively use these paths, while other directory functions are prepared for future migration phases. **Note**: Additional path functions (`get_instance_dir`, `get_store_dir`, `get_backup_dir`, etc.) are referenced in frontend code but not yet implemented as Tauri commands in the Rust backend. **Backward Compatibility**: Standard mode uses identical paths to the previous system. Portable mode is additive functionality that doesn't affect existing installations. ### Native Dialog System Created cross-platform dialog system in `src-tauri/src/dialog.rs` using `native-dialog` crate for critical notifications that work independently of webview. Includes `panic`, `info`, `warn`, `error`, and `confirm` functions with appropriate message types and logging integration. ```rust pub fn confirm(title: &str, msg: &str, icon: MessageType) -> bool { MessageDialog::new() .set_type(icon) .set_title(title) .set_text(msg) .show_confirm() .unwrap_or_default() } ``` The dialog system provides platform-appropriate native dialogs for critical system messages, error reporting, and user confirmations with active usage in portable WebView2 installation prompts. All dialog operations include logging and fallback handling for display failures. This part is mainly aimed at portable version where we cannot rely on `WebView2` being present by default (handled by the installer). See FE-624. **Backward Compatibility**: Dialog system is additive functionality used for new error handling scenarios. Existing error handling mechanisms continue to function unchanged. ### ESLint Config / Linting Infra Added ESLint setup in `.eslintrc.cjs` with TypeScript and Vue support, configured rules to prevent localStorage misuse, and added specific restrictions on direct localStorage access to encourage proper persistence patterns. ```javascript // localStorage usage restrictions "no-restricted-globals": [ "error", { name: "localStorage", message: "Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores", }, ], "no-restricted-syntax": [ "error", { selector: "CallExpression[callee.object.property.name='localStorage']", message: "Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores", }, ], ``` Added linting scripts to `package.json` including `lint`, `lint:ts`, `lintfix`, and `prod-lint` for different development scenarios. Identical to what is already in `hoppscotch-selfhost-web` or `hoppscotch-common`. ### Build Config Updates Updated `package.json` scripts and `Cargo.toml` for portable mode support with feature flags and proper feature gating. Updated `build.rs` to handle different config files based on features and separated portable and standard build commands. ```rust // Feature-gated build configuration fn main() { #[cfg(feature = "portable")] { println!("cargo:rerun-if-changed=tauri.portable.conf.json"); } #[cfg(not(feature = "portable"))] { println!("cargo:rerun-if-changed=tauri.conf.json"); } tauri_build::build() } ``` Added new package scripts: `dev:portable`, `build:portable`, `prepare-web`, and updated existing scripts for development workflow. Created separate Tauri configuration file for portable builds with appropriate bundling settings that disable automatic updater artifacts for portable distributions. **Backward Compatibility**: Standard build process remains identical. Portable mode builds are new functionality that doesn't affect existing build pipelines. ### Directory Organization Structure Defined new directory structure functions with clear separation between current working data and logs, though most remain unused in this phase: ``` ├── latest/ # Current working data (prepared) │ ├── instance/ # Instance-related files (will be used for kernel store) │ └── store/ # Store files (prepared for future migration) └── logs/ # app logs (actively used, platform-specific location) ``` The path management functions automatically create directories when needed and provide the foundation for future migration system that will move existing data to this new structure while maintaining full backward compatibility. Currently only logging and kernel store paths are actively utilized, with other directory functions prepared for subsequent migration phases. ### Updater Service Refactor Refactored `UpdaterService` from `src/utils/updater.ts` to `src/services/updater.service.ts` using new persistence layer instead of direct LazyStore access. The service now uses `DesktopPersistenceService` for state management, provides progress tracking during downloads, and includes error handling. ```typescript // Progress tracking with persistence integration await updateResult.downloadAndInstall((event: DownloadEvent) => { if (event.event === "Started") { totalBytes = event.data.contentLength downloadedBytes = 0 } else if (event.event === "Progress") { downloadedBytes += event.data.chunkLength this.currentProgress = { downloaded: downloadedBytes, total: totalBytes } } else if (event.event === "Finished") { this.persistence.setUpdateState({ status: UpdateStatus.INSTALLING }) } }) ``` The refactored service separates update logic from UI concerns and provides state management through the persistence layer with integration in the home view component. **Backward Compatibility**: Update functionality remains identical from user perspective. Internal architecture changes don't affect external behavior. ### Cross-Platform Util Functions Implemented platform-specific utilities in `src-tauri/src/util.rs` for common operations like opening external links. The `open_link` function handles Windows, macOS, and Linux with appropriate platform commands and proper error handling, currently used by the portable updater for opening download pages. ```rust pub fn open_link(link: &str) -> Option<()> { #[cfg(target_os = "windows")] { Command::new("rundll32") .args(["url.dll,FileProtocolHandler", link]) .stdout(Stdio::null()) .spawn() .ok() .map(|_| ()) } #[cfg(target_os = "macos")] { Command::new("open") .arg(link) .stdout(Stdio::null()) .spawn() .ok() .map(|_| ()) } #[cfg(target_os = "linux")] { Command::new("xdg-open") .arg(link) .stdout(Stdio::null()) .spawn() .ok() .map(|_| ()) } } ``` Added platform detection using cfg attributes, error handling with Option return types, and stdout redirection to prevent console output pollution. **Backward Compatibility**: Utility functions are additive functionality that adds capabilities without modifying current behavior. ### Rust Backend Restructuring Reorganized Rust backend with modular structure including separate modules for each concern: `config.rs`, `dialog.rs`, `error.rs`, `path.rs`, `updater.rs`, `util.rs`, and `webview/mod.rs`. Error handling with custom `HoppError` type using `thiserror` crate for error reporting and debugging. ```rust // Centralized error handling with detailed context pub enum HoppError { #[error("IO error: {0}")] Io(#[from] io::Error), #[error("Failed to initialize server port")] ServerPortInitialization, #[error("Failed to emit event: {0}")] EventEmission(String), #[error("Tauri error: {0}")] Tauri(#[from] tauri::Error), } ``` Added new dependencies including `thiserror` for error handling, `native-dialog` for cross-platform dialogs, `tauri-plugin-http` and `tauri-plugin-opener` for functionality, `semver` for version management, and `tempfile`/`winreg` for Windows-specific portable WebView2 handling. The modular structure separates concerns. **Backward Compatibility**: Backend restructuring maintains all existing API contracts. Internal organization changes won't affect external functionality. ### Frontend Architecture Changes Replaced direct LazyStore usage throughout frontend with service-oriented architecture. Updated `src/views/Home.vue` to actively use new persistence and migration services while maintaining identical user experience. Implemented reactive state management using RxJS observables and Vue composables. ```typescript // Service integration in Home.vue maintaining existing UX const setupUpdateStateWatcher = async () => { return persistence.watchUpdateState((newValue) => { if (!newValue) return updateStatus.value = newValue.status updateMessage.value = newValue.message || "" if (newValue.status === UpdateStatus.AVAILABLE) { appState.value = AppState.UPDATE_AVAILABLE } // Handle other states... }) } ``` The frontend now initializes migration service and uses new updater service architecture while preserving the exact same user interface and interaction patterns. **Backward Compatibility**: User experience remains unchanged. All UI interactions, workflows, and visual elements function identically to previous versions. ### Portable Mode Foundation Established foundation for portable mode functionality with feature-gated compilation supporting both standard and portable modes. In portable mode, the app uses relative paths from executable directory instead of system directories, stores all data within app directory, and includes WebView runtime checking for Windows. ```rust // Portable mode path resolution if cfg!(feature = "portable") { return std::env::current_dir() .unwrap_or_else(|_| std::env::temp_dir()) .join("hoppscotch-desktop-data"); } ``` Created separate Tauri configuration for portable builds with appropriate bundling settings. Added WebView2 installation checking and automatic installation for Windows portable builds using registry detection following Microsoft's official documentation. **Backward Compatibility**: Portable mode is entirely new functionality that doesn't affect existing standard installations. Standard mode behavior remains unchanged. ### Data Migration Strategy The implementation includes migration strategy framework that will detect existing data from legacy store files, migrate connection state and recent instances to new kernel store, move `.hoppscotch.store` files to organized directory structure, create version-based backups before making changes, and clean up legacy files after successful migration. Currently the migration system includes placeholder functions prepared for future phases. Migration will occur automatically on first startup with the new version and includes logging, error handling, and rollback capabilities. **Backward Compatibility**: Migration framework preserves all existing user data, settings, and workflows. Users experience no disruption during the upgrade process and can continue using the app exactly as before. ### Foundational Infrastructure Much of the code in this PR is currently inert but establishes the architectural foundation for future features including portable mode implementation, cross-platform support, and data management capabilities. ### Notes to reviewers Testing should verify data integrity in different user scenarios, cross-platform functionality on Windows/macOS/Linux, portable mode resolution in CI and general data storage + cleanup, dialog system functionality on all platforms, and backward compatibility with existing user data and workflows. ## Closing Note Closing in favor of more granular phased approach starting from https://github.com/hoppscotch/hoppscotch/pull/5259 for better backwards compatibility and stable releases. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-17 02:36:16 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hoppscotch#5126
No description provided.