mirror of
https://github.com/lights0123/n-link.git
synced 2026-08-07 18:23:28 +00:00
Add design spec for Tauri 2 + Vue 3 desktop port
Ports desktop from Tauri 1.0.0-beta.8 (webkit2gtk-4.0, unavailable on Ubuntu 26.04) to Tauri 2.x (webkit2gtk-4.1), alongside Vue 2 -> Vue 3 and Vue CLI -> Vite. Wires in the locally patched libnspire-sys already proven by nlink-cli. Scoped to Linux (deb + appimage). The web/ package shares n-link-core and is knowingly left broken, deferred to its own project. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0472908ef4
commit
754fbf9c70
|
|
@ -0,0 +1,191 @@
|
|||
# Design: Port n-link desktop to Tauri 2 + Vue 3
|
||||
|
||||
Date: 2026-07-19
|
||||
Status: Approved
|
||||
Branch: `port/tauri2-vue3`
|
||||
|
||||
## Problem
|
||||
|
||||
The n-link desktop app cannot build or run on Ubuntu 26.04. `desktop/src-tauri`
|
||||
pins `tauri = "1.0.0-beta.8"`, which links against `webkit2gtk-4.0`. That library
|
||||
is not available on this system — only `webkit2gtk-4.1` is. The prebuilt 0.1.6
|
||||
`.deb` fails for the same reason, plus a missing `libssl.so.1.1` (only
|
||||
`libssl.so.3` is present).
|
||||
|
||||
This is the blocker that caused `nlink-cli` to be written as a GUI-free
|
||||
workaround. This project removes the blocker properly so the GUI works.
|
||||
|
||||
Tauri 2.x links against `webkit2gtk-4.1`, which is what makes the port the fix.
|
||||
|
||||
### Verified environment
|
||||
|
||||
| Component | State |
|
||||
|---|---|
|
||||
| `webkit2gtk-4.0` | absent, not installable |
|
||||
| `libwebkit2gtk-4.1-dev` | available, 2.52.3-0ubuntu0.26.04.2 (not yet installed) |
|
||||
| `libsoup-3.0-dev` | available, 3.6.6-1 (not yet installed) |
|
||||
| `libssl` | 3 only; no 1.1 |
|
||||
| `libusb-1.0` | 1.0.29, installed |
|
||||
| rustc / cargo | 1.97.1 |
|
||||
| node | v22.22.1 |
|
||||
| yarn | **not installed** |
|
||||
| TI-Nspire CX II | connected, USB `0451:e022` |
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
1. `n-link-core` — port 8 components from Vue 2 to Vue 3.
|
||||
2. `desktop` — port to Tauri 2, Vue 3, and Vite; bundle deb + appimage.
|
||||
|
||||
Out of scope (deferred to a separate project):
|
||||
|
||||
- `web` (Nuxt 2). It shares `n-link-core`, so porting core **knowingly breaks
|
||||
it**. `web/` is left untouched at its current commit rather than
|
||||
half-migrated. It is a browser/WebUSB product and does not bear on the
|
||||
Linux GUI problem.
|
||||
- Windows and macOS targets. `msi` and `dmg` are dropped from the bundle
|
||||
config; they cannot be verified from this machine.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Platform targets | Linux only; deb + appimage | Fully testable here |
|
||||
| Frontend stack | Vue 3 + Vite | Vue CLI 4.5 / webpack 4 fails on Node 22 (OpenSSL 3); `vue-cli-plugin-tauri` is Tauri-v1-era and abandoned |
|
||||
| Shared core | Port `n-link-core` to Vue 3 | `web` deferred, accepted as broken |
|
||||
| Component style | `<script setup>` + Composition API | `vue-class-component` has no Vue 3 future |
|
||||
| Migration method | Scaffold clean, port code across | v1→v2 config is a near-total rewrite; a generated baseline means only our code is ever in question |
|
||||
| Verification | Manual, `n-link-cli` as oracle | Repo has zero tests; risk concentrates in USB behavior needing real hardware |
|
||||
|
||||
## Approach
|
||||
|
||||
Scaffold a fresh Tauri 2 + Vue 3 + Vite + TypeScript app, then move logic into
|
||||
it. Backend goes first — this is forced, not preference: the Vue 3 frontend
|
||||
cannot be exercised until a Tauri 2 shell runs, because Tauri 1 cannot launch
|
||||
on this machine at all.
|
||||
|
||||
"Scaffold clean" means the generated baseline **replaces the contents of
|
||||
`desktop/`** in place, on the `port/tauri2-vue3` branch — it is not a new
|
||||
top-level package. The workspace keeps its three members (`desktop`,
|
||||
`n-link-core`, `web`). The old `desktop/src` and `desktop/src-tauri` are
|
||||
removed in the same commit that adds the scaffold, so history shows one
|
||||
coherent replacement rather than a half-migrated intermediate state.
|
||||
|
||||
Trade-off accepted: per-file `git blame` continuity in `desktop/` is lost.
|
||||
Given that `tauri.conf.json`, the build tooling, and every component's syntax
|
||||
are all being replaced anyway, that continuity is largely notional.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
desktop/
|
||||
src-tauri/
|
||||
Cargo.toml tauri 2.x, patched libnspire-sys
|
||||
tauri.conf.json v2 schema
|
||||
capabilities/default.json replaces v1 allowlist
|
||||
src/
|
||||
main.rs thin: CLI passthrough + Builder
|
||||
device.rs Device/DeviceState/DEVICES registry
|
||||
commands.rs the 13 #[tauri::command] wrappers
|
||||
error.rs SerializedError
|
||||
cli.rs headless mode
|
||||
src/ Vue 3 + Vite
|
||||
```
|
||||
|
||||
`main.rs` is currently 478 lines holding the device registry, hotplug monitor,
|
||||
all 13 commands, and `main()`. Splitting it into focused modules is a targeted
|
||||
improvement to code being rewritten end-to-end anyway — not unrelated
|
||||
refactoring.
|
||||
|
||||
The patched `vendor/libnspire-sys` (CX II NNSE handshake retry limit raised
|
||||
10 → 100) is wired via `[patch.crates-io]`, exactly as `nlink-cli` already does.
|
||||
|
||||
## IPC contract — held invariant
|
||||
|
||||
Deliberately unchanged, so the frontend and backend ports can be verified
|
||||
independently.
|
||||
|
||||
**13 commands:** `enumerate`, `open_device`, `close_device`, `update_device`,
|
||||
`list_dir`, `download_file`, `upload_file`, `upload_os`, `delete_file`,
|
||||
`delete_dir`, `create_nspire_dir`, `move_file`, `copy`
|
||||
|
||||
**3 events:** `addDevice`, `removeDevice`, `progress`
|
||||
|
||||
**Payloads:** `DevId{busNumber,address}`, `AddDevice`, `ProgressUpdate`,
|
||||
`FileInfo` — all `camelCase` via serde, unchanged.
|
||||
|
||||
## Data flow
|
||||
|
||||
`rusb` hotplug thread → `DeviceMon` → emits `addDevice`/`removeDevice` → Vue
|
||||
store in `devices.ts`. Commands mutate the global `DEVICES` map behind a
|
||||
`RwLock`. Long transfers emit `progress`.
|
||||
|
||||
One frontend change: the ~10 `$set`/`$delete` calls in `devices.ts` become
|
||||
plain assignment and `delete`. Vue 3 proxy reactivity makes them unnecessary —
|
||||
a deletion, not a rewrite.
|
||||
|
||||
## Error handling
|
||||
|
||||
Unchanged in shape. `SerializedError` wraps `libnspire::Error` and
|
||||
`rusb::Error` into a serde-serializable form for crossing IPC. Kept as-is,
|
||||
relocated to `error.rs`.
|
||||
|
||||
## Migration hazards
|
||||
|
||||
Specific traps identified in the existing code:
|
||||
|
||||
| Hazard | Detail |
|
||||
|---|---|
|
||||
| `on_page_load` | Signature changed in v2 (`&Webview` + payload). Hotplug registration at `main.rs:440` must move; `setup()` is the better home |
|
||||
| `Window` → `WebviewWindow` | `emit` additionally requires `use tauri::Emitter` in scope |
|
||||
| `drain_filter` | `cmd.rs:62`, hashbrown 0.11 → renamed `extract_if`, **with changed semantics** |
|
||||
| `allowlist` → capabilities | allowlist block deleted; `dialog:allow-open` + `shell:allow-open` become plugin permissions |
|
||||
| Plugins | `dialog` and `shell` are separate crates + JS packages in v2. `notification-all` is in Cargo features but unused by the frontend — dropped |
|
||||
| CSP | Commit `0472908` fixed a CSP blank-screen in release mode. v2 moves CSP to `app.security.csp`; this fix must be carried forward or release builds go blank again |
|
||||
| `clap` 3.0.0-beta.2 | `cli.rs` uses it — but `nlink-cli/src/cli.rs` is **already** the clap-4 port of this same file. Reuse it instead of redoing the migration |
|
||||
| element-ui | Only `el-popover`, 5 usages across 3 files. Swap to element-plus |
|
||||
| Vue class components | 9 `@Component`, 18 `@Prop`, 6 `@Watch` across 9 files → `<script setup>` |
|
||||
| yarn missing | Root `package.json` declares yarn workspaces but yarn is absent. Migrating to **npm workspaces** (npm ships with Node 22; the root `yarn.lock` is invalidated by this port's dependency changes regardless) |
|
||||
|
||||
## Build sequence
|
||||
|
||||
1. Install system deps (`libwebkit2gtk-4.1-dev`, `libsoup-3.0-dev`,
|
||||
`librsvg2-dev`, `libxdo-dev`, `libayatana-appindicator3-dev`). Requires sudo.
|
||||
2. Scaffold Tauri 2 + Vue 3 + Vite shell. **Go/no-go gate.**
|
||||
3. Port Rust backend; wire patched `libnspire-sys`.
|
||||
4. Port `n-link-core` components to Vue 3 `<script setup>`.
|
||||
5. Port `desktop/src` (`devices.ts`, `Home.vue`, router); drop `$set`/`$delete`.
|
||||
6. Bundle deb + appimage.
|
||||
|
||||
## Verification
|
||||
|
||||
Each gate runs against the connected CX II.
|
||||
|
||||
1. **Go/no-go** — an empty Tauri 2 window opens on webkit2gtk-4.1. Cheap, and
|
||||
everything downstream is wasted if it fails, so it runs first.
|
||||
2. **Backend** — `enumerate` sees the device; `list_dir /` matches
|
||||
`n-link-cli ls /`.
|
||||
3. **Frontend** — components render; device appears in the UI.
|
||||
4. **End-to-end** — download a `.tns`; `sha256sum` matches the same file
|
||||
fetched via `n-link-cli download`.
|
||||
5. **Bundle** — deb and appimage build; installed deb launches.
|
||||
|
||||
### Known ceiling, not a regression
|
||||
|
||||
Per `nlink-cli/README.md`: libnspire's CX II support has been frozen upstream
|
||||
since 2020, and against 2026 firmware, multi-packet transfers are unreliable —
|
||||
long operations can fail mid-transfer with `Busy`.
|
||||
|
||||
This ceiling is inherited by the GUI. Step 4 therefore uses a **short**
|
||||
transfer, and any failure is compared against `n-link-cli` behavior before
|
||||
being treated as a port regression.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Go/no-go fails.** If webkit2gtk-4.1 cannot produce a window, the whole
|
||||
approach is void. Mitigated by making it step 2, at near-zero cost.
|
||||
- **Vue 3 reactivity drift.** Removing `$set`/`$delete` is where silent
|
||||
behavioral regressions are most likely. Caught by oracle cross-check at
|
||||
gate 2 and 4, not by tests.
|
||||
- **`web` stays broken.** Accepted and explicit; tracked as follow-up work.
|
||||
Loading…
Reference in a new issue