Skip to content

Tauri + Rust core spike (issue #360)#361

Open
etiennechabert wants to merge 7 commits into
mainfrom
claude/romantic-spence-39f7cf
Open

Tauri + Rust core spike (issue #360)#361
etiennechabert wants to merge 7 commits into
mainfrom
claude/romantic-spence-39f7cf

Conversation

@etiennechabert

@etiennechabert etiennechabert commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Closes #360 — a Tauri + Rust spike that runs the existing React renderer verbatim on a Rust backend. Additive: the Electron app and the TS core are untouched. Decision + phased plan: specs/rust-tauri-migration.md; package details: packages/tauri-shell/README.md.

It started as the read-only core and — driven against a real CUR dataset (18 months, 138 org accounts) — grew to cover most of migration Phases 1–3.

What's real

  • All analytical views (Overview, Trends, Missing Tags, Explorer, Entity detail) with full-fidelity numbers matching Electron: cost-metric from cost-scope.yaml (unblended/list/amortized), cost-scope exclusion rules, and the org-account join (resource→account tag fallback, account display names via accountNameFromTag, account-only/OU-path dims).
  • Findings/Savings from the local cost-optimization Parquet.
  • Real AWS Organizations sync via aws-sdk-organizations + aws-config (SSO/profile) — validates the issue's "AWS SDK for Rust is mature" claim end-to-end (read-only).
  • AWS profile list (~/.aws), data inventory, tag/column discovery, filter values, and a live Debug query log.

How the seam works

The UI reads window.costgoblin (installed by Electron's preload). The spike swaps only that seam: src/bridge.ts installs the same globals backed by Tauri invoke; src/main.tsx renders the real App (no fork). Rust backend in src-tauri/src (commands.rs, query.rs, config.rs, aws_org.rs, querylog.rs, db.rs).

Notable findings

  • Threading: Tauri runs sync commands on the main thread → heavy DuckDB/AWS work froze the UI. Fixed by making all commands #[tauri::command(async)] (off-main-thread, concurrent). No result cache / materialized base yet, so repeat queries are slower than Electron but never block (Phase-2 work).
  • Production rendering: the official tauri build bundle renders; Vite's crossorigin attribute is stripped so embedded assets load under tauri://localhost.
  • Footprint (measured vs Electron v0.3.1, arm64): installed .app ~70 MB vs 409 MB (~6×); download ~22–30 MB vs 158 MB; runtime RSS ~2.6 GB vs ~3.3 GB (both DuckDB-dominated — the "much lower idle memory" claim is overstated for real workloads; the clear win is bundle/distribution size).

Still stubbed (Phase 3/4)

S3 CUR download sync, SSM region names, config-sharing, auto-updater, MCP server, and save* writes.

Run it

npm install
cd packages/tauri-shell && npm run tauri:build   # release .app (or `npm run tauri:dev` on fixtures)

First build compiles bundled DuckDB + AWS SDK from source (~several min). Point at real data via COSTGOBLIN_DATA_DIR / COSTGOBLIN_CONFIG_DIR.

Notes

  • Version bumped to 0.3.2; the new package has no build script so release/CI's --workspaces --if-present skips it. Not wired into CI.
  • npm run check passes (607 tests). Reversible — behind the existing CostApi boundary.

Boots the existing React renderer verbatim on a Tauri/Rust backend that serves the analytical views from the fixture Parquet via the native duckdb crate. Additive — the Electron app and TS core are untouched.

- packages/tauri-shell: Vite frontend (bridge.ts installs window.costgoblin backed by Tauri invoke, replacing only the Electron preload seam) + Rust backend (CostApi read path ported from core's query builders, run over the fixtures with the duckdb crate; AWS/sync/sharing/update/MCP stubbed)
- specs/rust-tauri-migration.md: decision + phased migration plan
- bump version to 0.3.2 (first PR of the cycle past tag v0.3.1)
… threading fix

Extends the spike (issue #360) from the read-only core to full fidelity, validated on a real CUR dataset (18 months, 138 org accounts):

- query.rs/config.rs/commands.rs: org-account join (resource->account tag fallback, account names via accountNameFromTag, account-only/OU-path dims), cost-metric selection (unblended/list/amortized) + cost-scope exclusion rules -> numbers match Electron
- aws_org.rs: real read-only AWS Organizations sync (aws-sdk-organizations + aws-config SSO/profile)
- query_savings: cost-optimization recommendations from local Parquet
- querylog.rs: Debug panel query log (recorded in db::query_map)
- list_aws_profiles: real ~/.aws read
- all commands are #[tauri::command(async)] so DuckDB/AWS work runs off the main thread (fixes UI freeze; sync Tauri commands run on the main thread)
- vite: strip crossorigin so the release bundle renders under tauri://localhost
- docs: README + migration spec updated (scope, threading, footprint ~6x smaller bundle)
- mcp.rs: token-authed JSON-RPC MCP server (tiny_http, loopback) over the
  query layer — get_cost_overview / query_costs / list_dimensions /
  get_filter_values; HTTP-level test (mcp_server_responds).
- Persist UI / explorer / savings preferences; Open/Reveal folder via OS opener.
- bridge.ts routes the MCP + prefs + folder methods to real invokes.
- Docs: README + migration spec mark MCP + prefs as real.
- config_write.rs: ports the core *ConfigToYaml transformers (dimensions /
  views / cost-scope) onto serde_json Values so saved YAML round-trips with the
  same stable, undefined-omitting shape; serde_json preserve_order keeps key
  order. saveDimensionsConfig / saveViewsConfig / saveCostScope + the surgical
  updateAwsProfile are now real writes (3 unit tests).
- aws_ssm.rs: real read-only SSM region-name enrichment (GetParametersByPath +
  batched GetParameters), profile-region resolution from ~/.aws/config, cached
  to region-names.json. org sync now piggybacks it (matches Electron); adds
  syncRegionNames / getRegionNamesInfo / clearOrgData.
- Cargo: serde_json preserve_order, aws-sdk-s3, aws-sdk-ssm, sha2, chrono,
  tauri-plugin-dialog (S3/sharing land next).
- sync.rs: faithful port of the desktop sync — bulk download shells out to the
  'aws s3 sync' CLI (reuses SSO, multipart, incremental skips; files land
  directly in aws/raw/{tier}-{period}/, no repartitioning). cost-optimization
  tier mirrors the per-date staging + usage_date=* copy. etag files persisted
  ({period:{key:hash}}). Cancellable via a per-syncId flag that kills the child.
- Remote inventory via aws-sdk-s3 ListObjectsV2 (period grouping + local-status
  diff vs saved etags), with a local-only fallback when offline / no SSO.
- Progress flows through a shared per-syncId status map polled by getSyncStatus
  (mirrors Electron's state.syncStatuses + sync:status).
- Commands: sync_periods / cancel_sync / delete_local_period / get_sync_status /
  sso_login; get_data_inventory now does the real remote listing. Bridge wired.
- 3 unit tests (period/prefix/date extraction, completed-bytes, s3 path).
- bundle.rs: ports the core bundle transformers + SHA-256 fingerprint. Builds
  the same canonical sections YAML-object form (reusing the *_to_yaml
  transformers) and hashes its compact JSON, so serde_json preserve_order yields
  fingerprints that match JS JSON.stringify — round-tripped bundles re-import as
  fingerprintValid. parse_bundle validates kind/schemaVersion structurally;
  summarize + materialize (with credential re-injection). 2 unit tests.
- sharing.rs: S3 get/put of the bundle object (aws-sdk-s3), pre-import backup to
  config/backups/<ts>/, s3 location parsing + beacon-absence classification.
- Commands: export / preview (native dialogs via tauri-plugin-dialog) / fetch /
  apply / publish / check-beacon. Bridge wired to real invokes.
- Auto-updater command surface (check_for_updates / download_update /
  quit_and_install) + a real bridge state machine with a working onStatusChanged
  subscription, so the ReleaseNotesModal works end-to-end. A check honestly
  reports 'idle': finding an update needs a Tauri-format signed release feed
  (the GitHub releases are electron-builder format) — the one genuine blocker,
  documented in the spec.
- README + migration spec: all Phase 3/4 desktop-main features now marked ported
  (config writes, SSM, S3 sync, config sharing, prefs, MCP); honest caveats for
  the auto-updater feed blocker, SSO/CLI requirements, and wizard-only stubs.
- 11 Rust tests pass (8 unit + full-fidelity + MCP HTTP smoke).
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.

Investigate incremental migration to a Rust core via Tauri (keep the React UI)

1 participant