diff --git a/README.md b/README.md
index d848278..d6670b1 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,47 @@
-
-

-
TexSet
-
A fast, local-first LaTeX editor that runs in your browser.
-
-
-TexSet is an Overleaf-style editor you run yourself. Write LaTeX on the left,
-watch the PDF build on the right. Everything happens on your machine: no
-accounts, no external APIs, no cloud. Your documents are just files in a folder
-you control.
-
-It's built to be modular. Today it compiles LaTeX with xelatex; the editor and
-compiler are wired through an engine abstraction so Typst support can drop in
-later without reworking the app. The accent color even follows the document type
-(green for LaTeX) so you always know what you're editing.
-
-> **Status:** active development. The foundation is in place and features are
-> landing branch by branch.
+# TexSet
+
+A fast, local-first LaTeX editor. Write LaTeX on the left, watch the PDF build on
+the right. Everything runs on your machine: no accounts, no external APIs, no
+cloud. Your documents are just files in a folder you control.
+
+> **In active development.** TexSet is usable today but still growing, and not
+> everything is polished yet. Bug reports, ideas, and feedback are very welcome —
+> open an issue and let me know what works and what doesn't.
+
+It's built to be modular. Today it compiles LaTeX with xelatex (driven by
+latexmk); the editor and compiler sit behind an engine abstraction so Typst
+support can drop in later. The accent color follows the document type (green for
+LaTeX) so you always know what you're editing.
+
+## What you can do
+
+- **Editor** — CodeMirror 6 with LaTeX highlighting, a formatting toolbar (bold,
+ italic, headings, lists, math), and autocomplete for commands, environments,
+ packages, and your own `\ref`/`\cite`/`\label`.
+- **Live preview** — the PDF rebuilds as you type (2s debounce) with a manual
+ compile button, pinch-to-zoom, and zoom controls. Errors are listed and click
+ to jump to the offending line.
+- **Real LaTeX** — latexmk runs bibtex/biber and makeindex and reruns as needed,
+ so citations, bibliographies, indexes, and cross-references resolve. The Docker
+ image ships the full TeX Live plus common fonts.
+- **Multiple files** — add and edit several `.tex` files, with `\input`/`\include`
+ from the main one.
+- **Images & assets** — upload images (drag and drop), preview them, insert
+ `\includegraphics`, and rename or delete files.
+- **Projects** — a gallery with Word-like previews, pin to top, rename, and
+ delete with confirmation.
+- **Templates** — article, letter, résumé, and a Beamer presentation, each with a
+ preview.
+- **Import / export** — open a `.tex` as a new project; download your source or
+ any asset.
+- **Dark mode** — a light/dark toggle that's remembered between visits.
+- **Keyboard shortcuts** — save (`Ctrl/Cmd+S`) and compile (`Ctrl/Cmd+Enter`).
## Running it
-The quickest way is Docker, which bundles Node and a TeX Live install so you
-don't have to set up a LaTeX toolchain yourself.
+### Docker (recommended)
+
+Bundles Node and a full TeX Live, so you don't set up a LaTeX toolchain yourself.
```bash
git clone https://github.com/texset/texset.git
@@ -28,27 +49,29 @@ cd texset
docker compose up --build
```
-Then open http://localhost:7474. Your projects appear in `./projects` on your
-machine.
+Open http://localhost:7474. Your projects appear in `./projects` on your machine.
+The first build is large because it installs TeX Live; later builds are fast.
+See [docs/SELF_HOSTING.md](docs/SELF_HOSTING.md) for configuration.
-See [docs/SELF_HOSTING.md](docs/SELF_HOSTING.md) for configuration and other
-deployment notes.
+### Desktop app
-## Local development
+A desktop build (Electron) is in progress so the app can be installed from the
+Microsoft Store and run without Docker. It bundles a small TeX distribution
+(TinyTeX) and falls back to a system install if you already have one. See
+[desktop/README.md](desktop/README.md).
-You'll need Node.js 20+, pnpm, and a working `xelatex` on your `PATH`.
+### Local development
+
+You'll need Node.js 20+, pnpm, and — to actually compile — a LaTeX engine on your
+`PATH` (TeX Live or MiKTeX). Without one, the app runs and shows a banner
+explaining how to install it.
```bash
pnpm install
pnpm dev
```
-The dev server runs on http://localhost:7474. There's also a Docker dev setup
-with hot reload that includes TeX Live, if you'd rather not install it locally:
-
-```bash
-docker compose -f docker-compose.dev.yml up
-```
+The dev server runs on http://localhost:7474.
## How it's built
@@ -60,15 +83,16 @@ docker compose -f docker-compose.dev.yml up
| Editor | CodeMirror 6 |
| PDF viewer | pdf.js |
| Index | SQLite (better-sqlite3) |
-| TeX engine | xelatex (TeX Live) |
+| TeX engine | latexmk + xelatex (TeX Live) |
| Container | Docker, node:20-bookworm-slim |
More detail in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
-## Contributing
+## Contributing & feedback
-Contributions are welcome. Start with [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md)
-for the branching and pull request workflow.
+Contributions and feedback are welcome — see
+[docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for the branching and pull request
+workflow, and the issue templates for bug reports and feature requests.
## License
diff --git a/desktop/.gitignore b/desktop/.gitignore
new file mode 100644
index 0000000..266875b
--- /dev/null
+++ b/desktop/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+dist
+
+# assembled at build time
+resources/standalone
+resources/tinytex
diff --git a/desktop/README.md b/desktop/README.md
new file mode 100644
index 0000000..142d224
--- /dev/null
+++ b/desktop/README.md
@@ -0,0 +1,76 @@
+# TexSet desktop
+
+An Electron shell that runs TexSet as a native app — no Docker, no separate
+LaTeX install for most people. It bundles a small TeX distribution (TinyTeX) and
+prefers a system LaTeX install if you already have one.
+
+Electron is used (rather than Tauri) because TexSet has a Node backend — the
+Next.js API, `better-sqlite3`, and spawning `latexmk` — so the standalone server
+runs inside the app via Electron's Node.
+
+> Status: scaffold. The build has to be assembled and tested on the target OS
+> (Windows for the Microsoft Store). The notes below are the steps; expect to
+> iterate, especially around the native module and TinyTeX.
+
+## How it works
+
+`main.js` starts `resources/standalone/server.js` (the Next standalone server) on
+`127.0.0.1:7475`, then opens a window pointing at it. It sets:
+
+- `TEXSET_DATA_DIR` to the OS user-data folder, so projects persist per user.
+- `TEXSET_TEX_BIN_DIR` to the bundled TinyTeX `bin` folder. The app prefers a
+ system LaTeX engine and only falls back to this (see `src/lib/tex.ts`).
+
+## Building (outline)
+
+Run these on the OS you're targeting. For the Microsoft Store, that's Windows.
+
+1. **Build the web app** (repo root):
+ ```bash
+ pnpm install
+ pnpm build
+ ```
+
+2. **Assemble the server bundle** into `desktop/resources/standalone`:
+ - copy `.next/standalone` → `desktop/resources/standalone`
+ - copy `.next/static` → `desktop/resources/standalone/.next/static`
+ - copy `public` → `desktop/resources/standalone/public`
+
+3. **Rebuild the native module for Electron.** `better-sqlite3` ships a binary
+ built for system Node; it must match Electron's Node ABI. From `desktop/`,
+ after `pnpm install`, run `electron-rebuild` against
+ `resources/standalone/node_modules` (or reinstall `better-sqlite3` there with
+ Electron headers). This is the step most likely to need attention.
+
+4. **Bundle TinyTeX** into `desktop/resources/tinytex`. Install TinyTeX
+ (https://yihui.org/tinytex/) for the target platform and copy its folder here
+ so `resources/tinytex/bin//latexmk` exists. TinyTeX installs missing
+ packages on demand (needs internet the first time for uncommon ones).
+
+5. **Package**:
+ ```bash
+ cd desktop
+ pnpm install
+ pnpm dist:win # or dist:mac / dist:linux
+ ```
+
+## Microsoft Store
+
+`electron-builder`'s `appx` target produces an `.appx`/`.msix`. To publish:
+
+- Register on Partner Center and create the app listing to get your identity
+ values, then set `build.appx.identityName`, `publisher`, and
+ `publisherDisplayName` in `package.json` to match.
+- Build the package, then upload it through Partner Center.
+
+The MSIX runs the bundled `server.js` and TinyTeX binaries from inside the package
+(allowed), so no external install is required at runtime.
+
+## Try it in dev
+
+With the web app built and `resources/standalone` assembled, from `desktop/`:
+
+```bash
+pnpm install
+pnpm start
+```
diff --git a/desktop/main.js b/desktop/main.js
new file mode 100644
index 0000000..e23f73f
--- /dev/null
+++ b/desktop/main.js
@@ -0,0 +1,94 @@
+// Electron shell for TexSet. It runs the Next.js standalone server in the
+// background (with Electron's own Node via ELECTRON_RUN_AS_NODE) and shows it in
+// a window. A small bundled TeX distribution (TinyTeX) is pointed at through
+// TEXSET_TEX_BIN_DIR; if the user already has a system LaTeX install, the app
+// prefers that one (see src/lib/tex.ts).
+const { app, BrowserWindow, shell } = require("electron");
+const { spawn } = require("node:child_process");
+const path = require("node:path");
+const http = require("node:http");
+
+const PORT = 7475;
+const ORIGIN = `http://127.0.0.1:${PORT}`;
+
+let serverProcess = null;
+
+// In a packaged build, resources live under process.resourcesPath. In dev we run
+// against the repo so you can iterate without packaging.
+function resource(...parts) {
+ return app.isPackaged
+ ? path.join(process.resourcesPath, ...parts)
+ : path.join(__dirname, "..", ...parts);
+}
+
+// TinyTeX lays its binaries out per platform.
+function tinytexBinDir() {
+ const platformDir =
+ process.platform === "win32"
+ ? "windows"
+ : process.platform === "darwin"
+ ? "universal-darwin"
+ : "x86_64-linux";
+ return resource("tinytex", "bin", platformDir);
+}
+
+function startServer() {
+ const serverDir = resource("standalone");
+ serverProcess = spawn(process.execPath, [path.join(serverDir, "server.js")], {
+ cwd: serverDir,
+ env: {
+ ...process.env,
+ ELECTRON_RUN_AS_NODE: "1",
+ PORT: String(PORT),
+ HOSTNAME: "127.0.0.1",
+ TEXSET_DATA_DIR: path.join(app.getPath("userData"), "projects"),
+ TEXSET_TEX_BIN_DIR: tinytexBinDir(),
+ },
+ stdio: "inherit",
+ });
+ serverProcess.on("exit", () => {
+ serverProcess = null;
+ });
+}
+
+// Wait until the server answers before showing the window.
+function whenServerReady(onReady, attempt = 0) {
+ http
+ .get(ORIGIN, () => onReady())
+ .on("error", () => {
+ if (attempt > 100) return; // ~20s, give up quietly
+ setTimeout(() => whenServerReady(onReady, attempt + 1), 200);
+ });
+}
+
+function createWindow() {
+ const win = new BrowserWindow({
+ width: 1280,
+ height: 800,
+ title: "TexSet",
+ backgroundColor: "#0f1115",
+ webPreferences: { contextIsolation: true },
+ });
+ win.loadURL(ORIGIN);
+ // open external links (install guides, etc.) in the system browser
+ win.webContents.setWindowOpenHandler(({ url }) => {
+ shell.openExternal(url);
+ return { action: "deny" };
+ });
+}
+
+app.whenReady().then(() => {
+ startServer();
+ whenServerReady(createWindow);
+ app.on("activate", () => {
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
+ });
+});
+
+app.on("window-all-closed", () => {
+ if (process.platform !== "darwin") app.quit();
+});
+
+app.on("quit", () => {
+ serverProcess?.kill();
+});
diff --git a/desktop/package.json b/desktop/package.json
new file mode 100644
index 0000000..0283e3c
--- /dev/null
+++ b/desktop/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "texset-desktop",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Desktop shell for TexSet.",
+ "main": "main.js",
+ "scripts": {
+ "start": "electron .",
+ "dist:win": "electron-builder --win",
+ "dist:mac": "electron-builder --mac",
+ "dist:linux": "electron-builder --linux"
+ },
+ "devDependencies": {
+ "electron": "^33.0.0",
+ "electron-builder": "^25.1.8"
+ },
+ "build": {
+ "appId": "com.texset.app",
+ "productName": "TexSet",
+ "directories": {
+ "output": "dist"
+ },
+ "files": ["main.js"],
+ "extraResources": [
+ { "from": "resources/standalone", "to": "standalone" },
+ { "from": "resources/tinytex", "to": "tinytex" }
+ ],
+ "win": {
+ "target": ["appx", "nsis"]
+ },
+ "appx": {
+ "identityName": "TexSet",
+ "publisher": "CN=YOUR_PUBLISHER_ID",
+ "publisherDisplayName": "Your Name"
+ },
+ "mac": {
+ "target": ["dmg"],
+ "category": "public.app-category.productivity"
+ },
+ "linux": {
+ "target": ["AppImage"]
+ }
+ }
+}
diff --git a/src/app/api/tex-status/route.ts b/src/app/api/tex-status/route.ts
new file mode 100644
index 0000000..9d7e05b
--- /dev/null
+++ b/src/app/api/tex-status/route.ts
@@ -0,0 +1,11 @@
+import { NextResponse } from "next/server";
+import { isTexAvailable } from "@/lib/tex";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
+
+// Tells the UI whether a LaTeX engine is reachable, so it can warn up front
+// instead of letting a compile mysteriously fail.
+export function GET() {
+ return NextResponse.json({ available: isTexAvailable() });
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f129a9d..ee6660e 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,6 @@
import type { Metadata, Viewport } from "next";
import { Inter, JetBrains_Mono } from "next/font/google";
+import { TexStatusBanner } from "@/components/TexStatusBanner";
import "./globals.css";
// fonts are self-hosted at build time, so this stays fully offline
@@ -45,7 +46,10 @@ export default function RootLayout({
- {children}
+
+
+ {children}
+