Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 32 additions & 24 deletions AGENTS.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Scaffolded a Tauri iOS build target alongside the existing Mac/Windows desktop app, so the app can now be run in the iOS Simulator (`pnpm tauri:ios:init`, `pnpm tauri:ios:dev`) and eventually shipped to the App Store. Split `src-tauri/src/main.rs` into a shared `lib.rs` (`#[cfg_attr(mobile, tauri::mobile_entry_point)]`) with a thin binary wrapper, and added a `[lib]` crate target (`crate-type = ["staticlib", "cdylib", "rlib"]`) required for mobile builds. The native menu, self-hosted updater, and updater-triggered restart — none of which apply on iOS — are now compiled out on mobile via `#[cfg(desktop)]`; their Tauri permissions moved to a new desktop-only capabilities file (`capabilities/desktop.json`, scoped via `"platforms"`). Added `src-tauri/tauri.ios.conf.json` to override the Tauri `identifier` to the App Store bundle ID (`com.adamjolicoeur.timetraked`) on iOS only, leaving the existing desktop identifier (and its app-data dir/keychain/updater identity) untouched. Also closed a touch-accessibility gap found during the investigation: the command palette (`Cmd/Ctrl+K`) previously had no on-screen trigger, only a keyboard shortcut and a desktop-only menu item — added a header button so it's reachable on iOS, which has neither. `src/lib/platform.ts` gained an `isIOS` check (touch-point-qualified, since iPadOS spoofs a "Macintosh" user agent) used to fix `isMac` misdetecting iPhone/iPad and to gate the startup silent-update check off on iOS. Corrected `index.html`'s CSP, which allowlisted a stale `capacitor://localhost` left over from an earlier Capacitor-based plan (this project ships via Tauri). CI/App Store Connect signing automation is an explicit follow-up, not included here
— `src-tauri/src/lib.rs` (new), `src-tauri/src/main.rs`, `src-tauri/Cargo.toml`, `src-tauri/tauri.ios.conf.json` (new), `src-tauri/capabilities/default.json`, `src-tauri/capabilities/desktop.json` (new), `src/lib/platform.ts`, `src/lib/tauriElectronApiShim.ts`, `src/App.tsx`, `index.html`, `package.json`
- Manual save now shows a toast confirming the save succeeded, or reporting failure so it isn't silently swallowed. Applies to all three manual-save entry points that share the same handler: the `Cmd/Ctrl+S` keyboard shortcut, the Command Palette's "Save Changes" action, and the Electron menu's Save item. `forceSyncToDatabase` now resolves a `boolean` (`true` on full success, `false` if any save failed) instead of `void`, so callers can react to the outcome
— `src/contexts/TimeTrackingContext.tsx`, `src/App.tsx`, `src/contexts/TimeTrackingContext.forceSync.test.tsx` (new)

Expand All @@ -23,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Code Quality

- ESLint now ignores generated Tauri iOS/Rust build directories (`src-tauri/gen`, `src-tauri/target`) so `pnpm lint` keeps checking source files after local mobile builds instead of parsing generated binary asset shims
— `eslint.config.js`
- Added `electron/menu.test.ts` and `electron/preload.test.ts` — the only two files under `electron/` without test coverage. Covers `buildApplicationMenu`'s IPC dispatch per menu item and its macOS-vs-non-macOS Preferences/Settings placement, and the `contextBridge`-exposed `electronAPI` surface (`writeBackup`, `listBackups`, `readBackup`, `requestFlushBeforeQuit`, `onMenuAction`)
— `electron/menu.test.ts` (new), `electron/preload.test.ts` (new)
- Expanded `supabaseService.test.ts` (15 → 30 tests) to cover `getCurrentDay`/`saveCurrentDay`, the `checkNewSchema` schema-detection cache, and `migrateFromLocalStorage`'s no-op/no-existing-data/client-name-reconcile/error-swallow paths — previously untested on a 1100+ line service that had only upsert/delete coverage
Expand All @@ -47,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Tauri iOS dev on a physical iPhone no longer tries to load the Mac dev server through `localhost:8080` on the device. `pnpm tauri:ios:dev` now passes `--host`, and Vite consumes `TAURI_DEV_HOST` for its server/HMR host while keeping the port fixed at 8080
— `package.json`, `vite.config.ts`
- `endDay` persisted the exact clock-out timestamp instead of rounding it to the nearest 15 minutes the way `startDay` already rounds the day's start time, so the archived day's `endTime` (and the last task's mirrored `endTime`) kept its raw seconds-precise value while `ArchiveEditDialog`'s Tasks table and day-summary form independently rounded the same values for display — producing a visible mismatch between the "posted"/persisted end time and what the edit dialog showed. `endDay` now rounds the effective end time at the source, so archived data and its display agree
— `src/contexts/TimeTrackingContext.tsx`, `src/contexts/TimeTracking.test.tsx`
- PWA manifest was duplicated three ways: `public/manifest.json` was an orphaned copy nothing linked to, and VitePWA's `manifest` option in `vite.config.ts` generated a second `manifest.webmanifest` that got injected into `dist/index.html` alongside the hand-maintained `<link rel="manifest" href="/site.webmanifest">`, leaving two manifest links in the built page. The actually-used `site.webmanifest` also pointed at screenshot files that don't exist (`desktop-1.png`/`mobile-1.png`). Deleted the orphaned JSON, set `manifest: false` on the VitePWA plugin, and fixed `site.webmanifest`'s screenshot list to reference the real files
Expand Down
4 changes: 4 additions & 0 deletions README-EXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,10 @@ To add a new theme color: add the HSL value to `:root`/`.dark`, then map it in `

## Development Workflow

### iOS Device Development

Use `pnpm tauri:ios:dev` for iOS hot reload. The script passes `--host` so Tauri replaces the `localhost:8080` dev URL with a host the physical iPhone can reach, and `vite.config.ts` binds/HMRs through `TAURI_DEV_HOST`.

### Git Workflow

**Branch naming:**
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Open [http://localhost:8080](http://localhost:8080) in your browser.
pnpm dev # Start dev server (localhost:8080)
pnpm build # Production build
pnpm preview # Preview production build
pnpm tauri:ios:dev # Run iOS dev build; uses --host for physical devices

# Code Quality
pnpm lint # ESLint
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default tseslint.config(
"dist-electron",
"dist-electron-build",
"src/components/ui",
"src-tauri/gen",
"src-tauri/target",
"node_modules",
"dev-dist/"
]
Expand Down
22 changes: 14 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="Timetraked" />

<!-- Content Security Policy -->
<!-- capacitor://localhost is included for WKWebView (iOS Capacitor) -->
<!-- Content Security Policy for the web/PWA build. The Tauri desktop/iOS
builds are served over the app's own origin (tauri://localhost on
macOS/iOS, http://tauri.localhost on Windows), which 'self' already
covers — Tauri also injects its own CSP header (from
src-tauri/tauri.conf.json's app.security.csp) at runtime, so no
Tauri-specific scheme needs to be listed here. (This previously
allowlisted capacitor://localhost, left over from an earlier
Capacitor-based plan; this project ships via Tauri, not Capacitor.) -->
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self' capacitor://localhost;
script-src 'self' 'unsafe-inline' capacitor://localhost;
style-src 'self' 'unsafe-inline' capacitor://localhost;
connect-src 'self' capacitor://localhost https://*.supabase.co wss://*.supabase.co https://generativelanguage.googleapis.com;
font-src 'self' data: capacitor://localhost;
img-src 'self' data: blob: capacitor://localhost;
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
connect-src 'self' https://*.supabase.co wss://*.supabase.co https://generativelanguage.googleapis.com;
font-src 'self' data:;
img-src 'self' data: blob:;
frame-src 'none';
object-src 'none';
base-uri 'self';
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"tauri": "tauri",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build",
"tauri:ios:init": "tauri ios init",
"tauri:ios:dev": "tauri ios dev --host",
"tauri:ios:build": "tauri ios build",
"db:migrate": "tsx server/migrate.ts",
"db:seed": "tsx server/seed.ts",
"server:dev": "tsx watch server/index.ts",
Expand Down
Binary file added public/icons/app-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/icon-1024-1024.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 8 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ version = "1.10.1"
description = "A time tracking application built for freelancers, consultants, and professionals."
edition = "2021"

[lib]
name = "timetraked_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-updater = "2"
tauri-plugin-dialog = "2"
tauri-plugin-opener = "2"
tauri-plugin-process = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
regex = "1"
chrono = "0.4"
tokio = { version = "1", features = ["fs", "sync", "time", "rt", "macros"] }

[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
tauri-plugin-process = "2"

[dev-dependencies]
tempfile = "3"
tokio = { version = "1", features = ["test-util"] }
10 changes: 2 additions & 8 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capabilities for the main window",
"description": "Cross-platform capabilities for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"dialog:default",
"updater:default",
"process:allow-restart"
]
"permissions": ["core:default", "opener:default", "dialog:default"]
}
8 changes: 8 additions & 0 deletions src-tauri/capabilities/desktop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop",
"description": "Desktop-only capabilities: self-hosted auto-update and updater-triggered restart, neither of which apply on iOS (App Store owns distribution/updates)",
"windows": ["main"],
"platforms": ["macOS", "windows", "linux"],
"permissions": ["updater:default", "process:allow-restart"]
}
Binary file modified src-tauri/icons/128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/128x128@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square107x107Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square142x142Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square150x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square284x284Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square30x30Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square310x310Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square44x44Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square71x71Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/Square89x89Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/StoreLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
Binary file modified src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
Binary file modified src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
Binary file modified src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
Binary file modified src-tauri/icons/icon.icns
Binary file not shown.
Binary file modified src-tauri/icons/icon.ico
Binary file not shown.
Binary file modified src-tauri/icons/icon.png
44 changes: 44 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
mod backup;
#[cfg(desktop)]
mod menu;
mod quit_flush;

use backup::BackupState;
use quit_flush::QuitState;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let builder = tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.manage(BackupState::default())
.manage(QuitState::default())
.invoke_handler(tauri::generate_handler![
backup::backup_write,
backup::backup_list,
backup::backup_read,
quit_flush::before_quit_flush_done,
])
.on_window_event(quit_flush::handle_window_event);

// Native menu bar, self-hosted updater, and process-restart-on-update are
// desktop concepts with no iOS analog: there's no menu bar on iOS, and
// App Store review requires distribution/update flows to go through the
// App Store rather than a self-hosted updater plugin.
#[cfg(desktop)]
let builder = builder
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {
let app_menu = menu::build_menu(app)?;
app.set_menu(app_menu)?;
Ok(())
})
.on_menu_event(menu::handle_menu_event);

let app = builder
.build(tauri::generate_context!())
.expect("error while building tauri application");

app.run(quit_flush::handle_run_event);
}
32 changes: 1 addition & 31 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod backup;
mod menu;
mod quit_flush;

use backup::BackupState;
use quit_flush::QuitState;

fn main() {
let app = tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.manage(BackupState::default())
.manage(QuitState::default())
.invoke_handler(tauri::generate_handler![
backup::backup_write,
backup::backup_list,
backup::backup_read,
quit_flush::before_quit_flush_done,
])
.setup(|app| {
let app_menu = menu::build_menu(app)?;
app.set_menu(app_menu)?;
Ok(())
})
.on_menu_event(menu::handle_menu_event)
.on_window_event(quit_flush::handle_window_event)
.build(tauri::generate_context!())
.expect("error while building tauri application");

app.run(quit_flush::handle_run_event);
timetraked_lib::run();
}
3 changes: 3 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
}
},
"bundle": {
"iOS": {
"bundleVersion": "100"
},
"active": true,
"targets": ["dmg", "nsis"],
"icon": [
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/tauri.ios.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://schema.tauri.app/config/2",
"identifier": "com.adamjolicoeur.timetraked"
}
12 changes: 11 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { AppSidebar } from "@/components/AppSidebar";
import { CommandPalette } from "@/components/CommandPalette";
import { KeyboardShortcutsDialog } from "@/components/KeyboardShortcutsDialog";
import { Button } from "@/components/ui/button";
import { Keyboard } from "lucide-react";
import { Keyboard, Search } from "lucide-react";
import {
SidebarInset,
SidebarProvider,
Expand Down Expand Up @@ -114,6 +114,16 @@ const AppShell = () => {
{badge}
<div className="ml-auto flex items-center gap-2">
{actions}
<Button
variant="ghost"
size="icon"
className="size-7"
onClick={openCommandPalette}
aria-label="Command palette"
title="Command palette (Cmd/Ctrl+K)"
>
<Search className="size-4" />
</Button>
<Button
variant="ghost"
size="icon"
Expand Down
11 changes: 9 additions & 2 deletions src/lib/platform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
const maxTouchPoints = typeof navigator !== "undefined" ? navigator.maxTouchPoints : 0;

// iPadOS has spoofed a "Macintosh" user agent since iPadOS 13, so telling an
// iPad apart from a real Mac requires the touch-point check too (real Macs
// report 0 touch points).
export const isIOS = /iPhone|iPad|iPod/.test(userAgent) || (/Macintosh/.test(userAgent) && maxTouchPoints > 1);

// Detects macOS so shortcut hints can render the platform-appropriate modifier
// glyph (⌘ vs Ctrl) in the command palette and shortcuts help dialog.
export const isMac =
typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
export const isMac = /Mac/.test(userAgent) && !isIOS;

export const modKey = isMac ? "⌘" : "Ctrl";
5 changes: 4 additions & 1 deletion src/lib/tauriElectronApiShim.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { checkForUpdatesManual, checkForUpdatesSilent } from "@/lib/tauriUpdater";
import { isIOS } from "@/lib/platform";

// Populates window.electronAPI with the same shape electron/preload.ts used
// to expose, so useElectronBackup.ts and useElectronMenuActions.ts work
Expand Down Expand Up @@ -53,7 +54,9 @@ export function installTauriElectronApiShim(): void {
},
};

if (import.meta.env.PROD) {
// Self-hosted updates have no iOS equivalent — App Store review requires
// distribution/updates to go through the App Store instead.
if (import.meta.env.PROD && !isIOS) {
void checkForUpdatesSilent();
}
}
19 changes: 16 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
import { fileURLToPath } from "node:url";
import { VitePWA } from "vite-plugin-pwa";

const tauriDevHost = process.env.TAURI_DEV_HOST;
const projectRoot = fileURLToPath(new URL(".", import.meta.url));

// https://vitejs.dev/config/
export default defineConfig({
server: {
host: "::",
port: 8080
host: tauriDevHost ?? "::",
port: 8080,
strictPort: true,
hmr: tauriDevHost
? {
protocol: "ws",
host: tauriDevHost,
port: 8080
}
: undefined
},
plugins: [
react(),
Expand All @@ -24,6 +36,7 @@ export default defineConfig({
],
manifest: false,
workbox: {
maximumFileSizeToCacheInBytes: 4000000,
globPatterns: ["**/*.{js,css,html,ico,png,svg,webp,woff,woff2}"],
cleanupOutdatedCaches: true,
runtimeCaching: [
Expand Down Expand Up @@ -52,7 +65,7 @@ export default defineConfig({
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src")
"@": path.resolve(projectRoot, "./src")
}
},
build: {
Expand Down