Skip to content

fix: support file exports in Tauri webview#763

Merged
ErikBjare merged 4 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/tauri-export
Feb 22, 2026
Merged

fix: support file exports in Tauri webview#763
ErikBjare merged 4 commits intoActivityWatch:masterfrom
TimeToBuildBob:fix/tauri-export

Conversation

@TimeToBuildBob
Copy link
Contributor

@TimeToBuildBob TimeToBuildBob commented Feb 22, 2026

Summary

Fixes file export functionality in the Tauri webview (ActivityWatch/aw-tauri#199).

The existing export code uses browser-specific download patterns (<a download>, data: URIs, URL.createObjectURL) that don't work in Tauri's webview. This PR adds a cross-platform download utility that:

  • Detects Tauri at runtime via window.__TAURI__
  • In Tauri: Uses @tauri-apps/plugin-dialog (native save dialog) + @tauri-apps/plugin-fs (file write)
  • In browser: Falls back to the existing Blob + createObjectURL pattern (no behavior change)
  • Graceful fallback: If Tauri plugins aren't available, falls back to browser method

Files changed

  • src/util/export.ts — New cross-platform download utility
  • src/tauri-plugins.d.ts — Type declarations for Tauri plugin dynamic imports
  • src/views/settings/CategorizationSettings.vue — Categories export uses utility
  • src/views/Buckets.vue — Bucket JSON/CSV exports use utility (converted direct API links to click handlers)
  • src/views/Report.vue — Report JSON/CSV exports use utility

Companion change needed

This PR requires a companion change in aw-tauri to register tauri-plugin-fs:

  1. Add tauri-plugin-fs = "2" to src-tauri/Cargo.toml dependencies
  2. Add "fs:default" to src-tauri/capabilities/default.json permissions
  3. Register the plugin in lib.rs: .plugin(tauri_plugin_fs::init())

I can submit that companion PR if this approach looks good.

Test plan

  • Verify exports still work in browser (no regression)
  • Verify exports work in Tauri webview after companion aw-tauri change
  • Test categories JSON export
  • Test single bucket JSON export
  • Test bucket CSV export
  • Test all-buckets JSON export
  • Test report JSON/CSV export

Important

Adds cross-platform download utility for file exports in Tauri webview, updating several Vue components to use this utility.

  • Behavior:
    • Adds cross-platform download utility in src/util/export.ts to handle file exports in Tauri and browser environments.
    • Detects Tauri at runtime using window.__TAURI__.
    • Uses @tauri-apps/plugin-dialog and @tauri-apps/plugin-fs for file exports in Tauri.
    • Falls back to existing browser method if Tauri plugins are unavailable.
  • Type Declarations:
    • Adds src/tauri-plugins.d.ts for Tauri plugin dynamic imports.
  • Vue Components:
    • Updates CategorizationSettings.vue, Buckets.vue, and Report.vue to use the new download utility for JSON/CSV exports.
  • Companion Change:
    • Requires changes in aw-tauri to register tauri-plugin-fs in Cargo.toml and lib.rs.

This description was created by Ellipsis for ded81bf. You can customize this summary. It will automatically update as commits are pushed.

Adds a cross-platform download utility (src/util/export.ts) that
detects Tauri webview at runtime and uses native save dialog +
filesystem plugins instead of the browser download pattern which
doesn't work in webviews.

All 5 export functions (categories JSON, bucket JSON, all buckets
JSON, bucket CSV, report JSON/CSV) now use the shared utility.

Browser behavior is unchanged — the utility falls back to the
existing Blob + createObjectURL pattern in non-Tauri contexts.

Requires companion change in aw-tauri to register tauri-plugin-fs
and add fs:default permission.

Fixes ActivityWatch/aw-tauri#199
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to ded81bf in 6 seconds. Click for details.
  • Reviewed 279 lines of code in 5 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_WqR67Swj4mCMupVm

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 22, 2026

Greptile Summary

Adds cross-platform file export support for Tauri webview environment. The PR introduces a new download utility that detects the runtime environment and uses native Tauri file dialogs when available, falling back gracefully to standard browser downloads otherwise.

Key Changes:

  • Created src/util/export.ts with runtime Tauri detection and dual-path download logic
  • Added TypeScript declarations for dynamically imported Tauri plugins
  • Migrated all export functionality (categories, buckets, reports) to use the new utility
  • Converted direct API links to click handlers in Buckets.vue to enable programmatic downloads

Issues Found:

  • Critical bug in Buckets.vue:272 where CSV export crashes on empty bucket (array access without check)

Confidence Score: 3/5

  • Safe to merge after fixing the empty bucket crash bug
  • The core implementation is solid with good architecture (runtime detection, graceful fallback, error handling). However, there's a critical logic error in the CSV export that will crash when exporting an empty bucket. The fix is trivial but necessary before merge.
  • src/views/Buckets.vue needs the empty array check fixed on line 272

Important Files Changed

Filename Overview
src/util/export.ts New cross-platform download utility with Tauri detection and graceful browser fallback
src/tauri-plugins.d.ts Type declarations for dynamically imported Tauri plugins
src/views/Buckets.vue Updated bucket export to use new utility, potential crash if bucket has no events
src/views/Report.vue Replaced manual download logic with new cross-platform export utility
src/views/settings/CategorizationSettings.vue Simplified category export by replacing data URI approach with new utility

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User clicks export] --> B{Check environment}
    B -->|isTauri| C[downloadFileTauri]
    B -->|browser| D[downloadFileBrowser]
    C --> E{Dynamic import Tauri plugins}
    E -->|Success| F[Show native save dialog]
    E -->|Fail| G[Catch error, fallback]
    F --> H{User selects path?}
    H -->|Yes| I[Write file with writeTextFile]
    H -->|No| J[Cancel, no action]
    G --> D
    D --> K[Create Blob + ObjectURL]
    K --> L[Trigger browser download]
    L --> M[Cleanup URL]
    I --> N[File saved]
    M --> N
Loading

Last reviewed commit: ded81bf

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 22, 2026

Additional Comments (1)

src/views/Buckets.vue
will crash if events array is empty

      const datakeys = events.length > 0 ? Object.keys(events[0].data) : [];

@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 0% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.67%. Comparing base (cd5aafe) to head (ef12256).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/util/export.ts 0.00% 35 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #763      +/-   ##
==========================================
- Coverage   26.20%   25.67%   -0.53%     
==========================================
  Files          29       30       +1     
  Lines        1706     1741      +35     
  Branches      311      303       -8     
==========================================
  Hits          447      447              
- Misses       1195     1272      +77     
+ Partials       64       22      -42     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The @tauri-apps/plugin-dialog and @tauri-apps/plugin-fs modules are not
in package.json since they're only available in the aw-tauri runtime.
The dynamic imports with try/catch already handle graceful fallback, but
bundlers (webpack, vite) still try to resolve modules at build time.

Add webpackIgnore and @vite-ignore magic comments to skip static resolution
while keeping the dynamic import pattern intact.
@TimeToBuildBob
Copy link
Contributor Author

Fixed CI failure in bf8ef99.

The build was failing because webpack and vite try to statically resolve @tauri-apps/plugin-dialog and @tauri-apps/plugin-fs at build time, even though they're wrapped in dynamic import() expressions. These packages aren't in package.json — they're injected by the aw-tauri runtime.

Fix: added /* webpackIgnore: true */ and /* @vite-ignore */ magic comments to the import calls. This tells both bundlers to skip static resolution and leave the dynamic import as-is. The try/catch already handles the graceful fallback for non-Tauri contexts.

…mports

webpack4 doesn't support /* webpackIgnore */ magic comments, and rollup
ignores /* @vite-ignore */ during production builds. Using new Function()
to create a runtime-only dynamic import bypasses static analysis in both
bundlers while still working correctly in the Tauri webview at runtime.
@TimeToBuildBob
Copy link
Contributor Author

Follow-up fix in b614261: previous approach (bundler-ignore magic comments) didn't work.

  • /* webpackIgnore: true */ requires webpack 5, but vue-cli-service uses webpack 4
  • /* @vite-ignore */ suppresses Vite's dev-server analysis, but Rollup still fails during production builds

New approach: new Function('m', 'return import(m)') — creates a runtime function that calls import(), which is invisible to both webpack's and rollup's static analyzers. At runtime in the Tauri webview, native import() resolves the plugins correctly. Falls back to browser download via try/catch if they're unavailable.

@TimeToBuildBob
Copy link
Contributor Author

Addressed greptile's finding: added guard against empty events array in export_csv() (ef12256). Object.keys(events[0].data) would crash if the bucket had no events — now returns [] for empty data arrays.

@TimeToBuildBob
Copy link
Contributor Author

The remaining CI failure (Test (node-20, py-3.9, aw-server-rust master)) is in a cleanup step, not in the actual tests:

  • Step 10 (Run tests): ✅ PASSED
  • Step 13 (Run e2e tests with testcafe): ✅ PASSED
  • Step 16 (Print server logs): ❌ FAILED — cat: '/home/runner/.cache/activitywatch/log/*/*.log': No such file or directory
  • Step 17 (Move logs to subdir): ❌ FAILED — same glob, no files

The cleanup steps run with if: ${{ always() }} and fail when aw-server-rust master doesn't write logs to that path. This is a pre-existing fragility in the upstream workflow, unrelated to the export changes in this PR. All actual test steps passed.

@ErikBjare ErikBjare merged commit 919e9f3 into ActivityWatch:master Feb 22, 2026
7 of 8 checks passed
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