Vibecoded CI/CD
Some checks failed
Build and Deploy Lean / build (push) Failing after 16m44s
Build and Deploy Nuxt / build (push) Failing after 48s

This commit is contained in:
2026-10-06 00:23:17 +02:00
parent a57770619e
commit e2c2c66d7f
79 changed files with 6044 additions and 47 deletions

6
frontend/src-tauri/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# Rust's build output — hundreds of MB, never belongs in the wiki.
target/
# Written by tauri-build on every build: the JSON schemas for the permissions
# listed under capabilities/. Generated, so regenerable.
gen/schemas

4912
frontend/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
[package]
name = "mathew"
version = "0.1.0"
description = "Mathew — a wiki for mathematics, in a window of its own"
authors = ["Aran Roig"]
edition = "2021"
# The app is a library with a thin main: Tauri's own entry points want it that
# way, so the same `run()` also serves a mobile build should one ever be asked
# for (the window itself is desktop-shaped — see src/lib.rs).
[lib]
name = "mathew_desktop_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
# A link inside the window hands its document to the system's browser rather
# than replacing the wiki in the only view there is. The wiki asks for this
# itself (app/utils/desktop.ts); this is the door it goes out by.
tauri-plugin-opener = "2"
# Only a desktop app can be launched twice onto one window.
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2"

View File

@@ -0,0 +1,78 @@
# src-tauri/ — the app window
Mathew as a **desktop app** (Tauri 2, Linux / Windows / macOS): the wiki in a
window of its own, no browser around it. The shell is deliberately thin — the
app *is* the same Nuxt bundle the website serves, written out as plain files
and embedded in the binary. Nothing in `frontend/app/` is desktop code; the
bundle asks at run time whether it happens to be sitting in a window
(`app/utils/desktop.ts`) and only then takes the window's habits: a link leads
out to the system's browser instead of replacing the wiki in the only view
there is, and the session token and interface language are mirrored into
`localStorage` because a webview is not everywhere obliged to keep cookies for
its own `tauri://localhost` address.
The wiki needs a wiki to talk to: the window holds no server and no database.
It calls the **public Mathew** over HTTPS by default — the API base is baked in
at build, see the table below.
## Build & run
From `frontend/` — these need [Rust](https://rustup.rs) (stable) and the
[Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS;
on Linux that is the WebKitGTK stack:
`libwebkit2gtk-4.1-dev libgtk-3-dev libssl-dev libxdo-dev librsvg2-dev build-essential`.
| Script | What it does |
| --- | --- |
| `npm run desktop:dev` | `tauri dev` — opens the **Nuxt dev server** (`localhost:3000`, started for you) in the window, hot-reload included; `isDesktop()` is already true inside it |
| `npm run desktop:build` | `tauri build` — runs `desktop:generate` (below), compiles the shell, embeds the files, and leaves installers under `src-tauri/target/release/bundle/` (deb/rpm/AppImage on Linux, NSIS/MSI on Windows, .app/.dmg on macOS) |
| `npm run desktop:generate` | the bundle the window loads: `MATHEW_DESKTOP=1 nuxt generate` → `dist-desktop/public`, the `frontendDist` this config names (a desktop build never overwrites the web `.output/`) |
| `npm run desktop:icons` | re-cuts `src-tauri/icons/` from `desktop/icon-source.png` (redraw that one with `python3 desktop/make-icon.py`) |
The desktop shape lives in `nuxt.config.ts` under one flag
(`MATHEW_DESKTOP=1`, also implied by the `TAURI_ENV_*` the Tauri CLI passes to
its build hooks): `ssr: false`, no `/api` proxy route, and the pre-paint theme
script served from `public/preboot.js` instead of inline. The web build takes
none of it. Deep routes (`/wiki/en/bayes-theorem`, …) are not files in the
bundle — Tauri's own asset serving answers unknown paths with `index.html`, so
the client router takes over exactly as it does on a static host.
**Dev-server port**: `devUrl` is fixed to `3000`, and Nuxt falls back to `3001`
when `3000` is taken (see the main gotchas) — free the port before
`desktop:dev`, or the window loads whatever else answers there.
## Where the app reaches
| Build variable | Default (desktop) | Meaning |
| --- | --- | --- |
| `NUXT_PUBLIC_API_BASE` | `https://mathew.aranroig.com` | the wiki the app reads and writes; point it at `http://localhost:5000` to test against this machine |
| `NUXT_PUBLIC_LEAN_URL` | empty | the Lean server `/lean` talks to; empty leaves that page saying there is no server here, which is the honest answer on a random laptop. The API's default CORS (`*`) carries the window's `tauri://localhost` origin fine — it sends a bearer header, not cookies |
Set them in the environment before `npm run desktop:build`.
## What the shell owns
- **one window per launch** — `tauri-plugin-single-instance`: pressing the
icon again brings the open window forward instead of stacking a second wiki
behind it (`src/lib.rs`).
- **the way out** — `tauri-plugin-opener` is the door `app/utils/desktop.ts`
hands outward links to the system's browser; `capabilities/default.json` is
what grants it. That and `core:default` are the whole permission list:
reading and writing the wiki is the webview's own business.
- **no CSP** (`csp: null` in `tauri.conf.json`): the window loads no foreign
script and talks to one API over HTTPS, and `/lean`'s Monaco wants
`eval`-like tricks a strict `script-src` would have to excuse. The one
script the page writes itself (the pre-paint theme, `public/preboot.js`)
already lives in a **file**, so turning a policy on later is a config line —
keep it that way if one is ever set.
Nothing here is signed: bundles come out unsigned, and updating is
reinstalling. An updater, signing, and a per-machine "which wiki?" setting are
the obvious next steps if this ever ships for real.
## Versioning
The app's version lives in `tauri.conf.json` (`version`); it names the
installers and the platform bundle metadata. Commit `Cargo.lock` once a build
has produced one — for an application binary, a locked dependency set is what
makes a rebuild reproducible.

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "What the Mathew window may ask of the machine. Reading and writing the wiki is the webview's own business — plain HTTP out to the API and, if one is configured, a websocket to the Lean server — and needs no permission here. The one thing it cannot do by itself is hand a link to the system's browser, which is what `opener:default` lets app/utils/desktop.ts ask for.",
"windows": ["main"],
"permissions": ["core:default", "opener:default"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,38 @@
/* Mathew as a window.
The shell is thin on purpose. Everything the app *is* — the open book, the
editor, the five interface languages — is the Nuxt bundle it loads, written
out by `npm run desktop:generate` and read from disk by the system webview
(see src-tauri/README.md). The wiki reaches its own API by itself: no Rust
command stands between the two, so an article is fetched the same way in the
window as in a browser.
Two things are the shell's own:
- `opener`, the door a link goes out by. The wiki asks for it by name
(app/utils/desktop.ts); `capabilities/default.json` is what answers.
- one window per launch. Pressing the icon again brings the window that is
already open to the front instead of stacking a second copy of the wiki
behind it. */
#[cfg(desktop)]
use tauri::Manager;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let mut builder = tauri::Builder::default().plugin(tauri_plugin_opener::init());
#[cfg(desktop)]
{
builder = builder.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
if let Some(window) = app.get_webview_window("main") {
let _ = window.unminimize();
let _ = window.set_focus();
}
}));
}
builder
.run(tauri::generate_context!())
.expect("error while running Mathew");
}

View File

@@ -0,0 +1,7 @@
// An app window has no terminal to answer from: keep one from opening behind
// it on Windows, where a release build would otherwise start a console.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
mathew_desktop_lib::run()
}

View File

@@ -0,0 +1,47 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Mathew",
"version": "0.1.0",
"identifier": "com.aranroig.mathew",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "npm run desktop:generate",
"frontendDist": "../dist-desktop/public"
},
"app": {
"windows": [
{
"label": "main",
"title": "Mathew",
"width": 1440,
"height": 900,
"minWidth": 940,
"minHeight": 600,
"center": true,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"publisher": "Aran Roig",
"homepage": "https://mathew.aranroig.com",
"category": "Education",
"shortDescription": "A collaborative wiki of mathematics",
"longDescription": "Mathew is a wiki of mathematical concepts: Markdown with LaTeX, an open shelf of articles in several languages, a Markdown editor, a calculator, a graph plotter and a Lean 4 editor. This is the same wiki in an app window of its own; it reads and writes a Mathew wiki over HTTP, so it needs one to reach.",
"copyright": "© Aran Roig",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}