Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

ChatGPT Desktop Application — Administrator & Developer Guide

Generated June 2026 from lencx/ChatGPT at a6de9a8

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the deep reference for the lencx/ChatGPT 2.0 desktop application: its architecture, configuration, build and release pipeline, security model, and internals. It is written for developers, packagers, and administrators who need to build, deploy, audit, or extend the app. End users should start with the User Guide.


1. Executive Summary

ChatGPT is an unofficial, cross-platform desktop client that renders the official ChatGPT website (https://chatgpt.com) inside a native window built with the Tauri 2 framework. The 2.0 line is a ground-up rewrite: a compact Rust backend manages the native window, child web views, configuration persistence, theming, and OS integration, while a small React + TypeScript frontend renders the custom toolbar, the Quick Ask input, and a (placeholder) settings view inside Tauri web views.

Attribute Value
Project lencx/ChatGPT
Version 2.0.0 (frontend package.json); crate version 0.0.0
License AGPL-3.0
Author lencx <[email removed]>
Bundle identifier com.nofwl.chatgpt
Product name ChatGPT
Frameworks Tauri 2 (beta), React 18, TypeScript 5, Tailwind CSS 3, Vite 5
Rust edition / MSRV 2021 / 1.77.1
Target platforms macOS, Windows, Linux
Source commit documented a6de9a8 (2024-08-30)

Note

The two version numbers are intentional: the user-facing product version comes from the frontend package.json (2.0.0), which tauri.conf.json references via "version": "../package.json". The Rust crate's own version field is 0.0.0 and is not the release version.

Important

This codebase pins beta releases of Tauri 2 and its plugins (for example @tauri-apps/api 2.0.0-beta.15, tauri = "2.0.0-beta"). Anyone building or maintaining this app should expect beta-era API surfaces and pin dependencies carefully; newer stable Tauri 2 releases may require migration.


2. Architecture

2.1 High-level model

The app is a single native window (label core) that hosts three child web views, plus an optional second native window for settings.

┌─────────────────────────── Native window: "core" ───────────────────────────┐
│                                                                              │
│   Webview "titlebar"  ── React UI (custom toolbar)                           │
│   ──────────────────────────────────────────────────────────────────────    │
│                                                                              │
│   Webview "main"      ── https://chatgpt.com  (the actual ChatGPT website)   │
│                          + injected scripts: ask.js, INIT_SCRIPT             │
│                                                                              │
│   ──────────────────────────────────────────────────────────────────────    │
│   Webview "ask"       ── React UI (Quick Ask input box, height 120px)        │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

         ┌──────────── Native window: "settings" (opened on demand) ────────────┐
         │   Webview ── React UI (placeholder "Settings" view)                  │
         └──────────────────────────────────────────────────────────────────────┘

The React bundle (index.html → src/main.tsx → src/App.tsx) is loaded into the titlebar, ask, and settings web views. App.tsx inspects getCurrentWebview().label and renders the matching component:

const viewMap = {
  titlebar: <Titlebar />,
  ask: <Ask />,
  settings: <Settings />,
};

export default function App() {
  const webview = getCurrentWebview();
  return viewMap[webview.label as keyof typeof viewMap] || null;
}

The main web view is not React — it loads chatgpt.com directly. The Rust backend injects two scripts into it at startup (see §2.4).

Note

This "one window, multiple web views" layout is what lets a native toolbar and a native input box float over a remote website without modifying that website. The backend continuously repositions and resizes the three web views as the window changes.

2.2 Layout geometry

The backend computes web view positions from two constants and the current window size and scale factor:

Constant Value Meaning
TITLEBAR_HEIGHT 28.0 Height (logical px) of the custom toolbar web view
ASK_HEIGHT 120.0 Height (logical px) of the Quick Ask web view when enabled (0 when disabled)

Defined in src-tauri/src/core/constant.rs. The main web view occupies the remaining space. Platform branches differ:

  • macOS: toolbar is pinned to the top (y = 0); main view starts below it (y = TITLEBAR_HEIGHT); Quick Ask sits at the bottom.
  • Windows / Linux: main view starts at the top (y = 0); toolbar and Quick Ask are stacked at the bottom.

Repositioning happens in two places: setup::init (initial build) and the window Resized event handler, both in setup.rs, and again in cmd::set_view_ask when Quick Ask is toggled.

2.3 Process / module map

src-tauri/                      Rust backend (the native app)
├── build.rs                    Build script; sets MACOSX_DEPLOYMENT_TARGET=10.13, runs tauri_build
├── tauri.conf.json             Tauri app config (product, bundle, build hooks, security)
├── Info.plist                  macOS bundle metadata
├── capabilities/desktop.json   Capability/permission grants (ACL)
├── scripts/ask.js              Userscript injected into chatgpt.com (ChatAsk class)
└── src/
    ├── main.rs                 Entry point; registers commands + setup
    └── core/
        ├── mod.rs              Module declarations
        ├── cmd.rs              10 #[command] handlers invoked from the frontend
        ├── conf.rs             AppConf struct + load/save/amend/theme persistence
        ├── constant.rs         Layout constants + INIT_SCRIPT
        ├── setup.rs            Builds the core window, 3 web views, download + resize handlers
        ├── template.rs         Manages the bundled ask.js script (version-aware update)
        └── window.rs           open_settings command (settings window)

src/                            React frontend (loaded into titlebar/ask/settings web views)
├── main.tsx                    React root render
├── App.tsx                     Renders component by current web view label
├── types.d.ts                  AppConf + SVG TypeScript types
├── base.css                    Tailwind base styles
├── components/WinTitlebar.tsx  Min/max/close window buttons (non-macOS)
├── hooks/useInfo.tsx           Platform detection (isMac)
├── hooks/useTheme.tsx          Reactive window theme
├── icons/*.tsx                 16 inline SVG icon components
└── view/
    ├── Titlebar.tsx            The toolbar UI + all invoke() calls
    ├── Ask.tsx                 Quick Ask input box
    └── Settings.tsx            Placeholder ("Settings")

index.html                     Vite entry; mounts #root and src/main.tsx
vite.config.ts                 Vite config (port 1420, ignores src-tauri)
tailwind.config.js             Tailwind theme (custom app colors, tablet breakpoint)

2.4 Injected scripts (main web view)

Two scripts are injected into the chatgpt.com web view at initialization (setup.rs):

  1. ask.js (src-tauri/scripts/ask.js) — defines window.ChatAsk with two static methods used by Quick Ask:

    • ChatAsk.sync(message) — finds the page <textarea> and sets its value using the native setter, dispatching an input event so the page's React state updates.
    • ChatAsk.submit() — finds the last <button> inside main form and clicks it to send. The file carries a userscript-style header (@name, @version 0.1.0, @url) that the backend uses for version-based updates (see §6.3).
  2. INIT_SCRIPT (string constant in constant.rs) — runs on DOMContentLoaded and:

    • Tracks SPA navigation by wrapping history.pushState / history.replaceState and listening for popstate, emitting a navigation:change event (with the new URL) to the titlebar web view.
    • Forces in-page links whose target is set (and isn't _blank) to open as _blank, so the link-handling logic routes them to the system browser.

Important

tauri.conf.json sets "withGlobalTauri": true, exposing window.__TAURI__ inside web views. The injected scripts rely on this (for example __TAURI__.webview.Webview.getByLabel('ask') and window.__TAURI__.webviewWindow...emit(...)). Disabling global Tauri would break navigation tracking and Quick Ask focus handling.


3. Frontend ↔ Backend Command Reference

The frontend communicates with Rust exclusively through Tauri's invoke() mechanism. All commands are registered in src-tauri/src/main.rs and defined in cmd.rs (and window.rs for open_settings).

Command Params Returns Effect
view_reload — — Reloads the main web view (window.location.reload())
view_url — tauri::Url Returns the current URL of the main web view
view_go_forward — — window.history.forward() in the main web view
view_go_back — — window.history.back() in the main web view
window_pin pin: bool — Persists stay_on_top and calls set_always_on_top(pin)
ask_sync message: String — Evaluates ChatAsk.sync(<message>) in the main view (live mirror)
ask_send — — Evaluates ChatAsk.submit(), then refocuses the ask web view after 500 ms
set_theme theme: String — Persists theme, then restarts the app (app.restart())
get_app_conf — AppConf Returns the current configuration object
set_view_ask enabled: bool — Persists ask_mode, resizes/repositions all three web views, sets focus
open_settings — — Shows the existing settings window or builds it (window.rs)

Note

ask_sync is debounced on the frontend (Ask.tsx, 300 ms) so keystrokes mirror into the page without flooding the bridge. ask_send is bound to ⌘/Ctrl + Enter via react-hotkeys-hook.

3.1 Frontend wiring notes

  • Titlebar.tsx is the hub: on mount it calls get_app_conf to hydrate toolbar state (ask_mode, stay_on_top, theme, mac_titlebar_hidden), listens for navigation:change to display the current hostname, and listens for tauri://resize to track full-screen state.
  • Ask.tsx mirrors input via ask_sync (debounced) and submits via ask_send.
  • useInfo.tsx resolves the platform through @tauri-apps/plugin-os to choose macOS vs. non-macOS behavior (toolbar padding, send shortcut).
  • WinTitlebar.tsx provides minimize / maximize / close for the core window on non-macOS platforms by calling Window.getByLabel('core') methods directly (no Rust command needed).
  • Path aliases (~view, ~icons, ~hooks, ~) are resolved by vite-tsconfig-paths from tsconfig.json.

4. Configuration

4.1 Runtime configuration (AppConf)

User preferences persist to a JSON file managed by conf.rs.

Location: <OS config dir>/com.nofwl.chatgpt/config.json

Platform Resolved path (typical)
macOS ~/Library/Application Support/com.nofwl.chatgpt/config.json
Windows %APPDATA%\com.nofwl.chatgpt\config.json
Linux ~/.config/com.nofwl.chatgpt/config.json

Schema (AppConf):

Field Type Default Meaning
theme string "system" One of system, light, dark
stay_on_top bool false Window pinned always-on-top
ask_mode bool false Quick Ask box visible
mac_titlebar_hidden bool true on macOS, false elsewhere Hide custom toolbar until hover (macOS)

Example config.json:

{
  "ask_mode": false,
  "mac_titlebar_hidden": true,
  "stay_on_top": false,
  "theme": "system"
}

Tip

You can edit config.json by hand while the app is closed to preset preferences (for example, force "theme": "dark" or enable "ask_mode": true for a fleet). Keys are serialized in sorted order because amend() round-trips through a BTreeMap.

Loading and self-healing. AppConf::load:

  1. If the file is missing, writes defaults and returns them.
  2. If the file exists but fails to deserialize cleanly, it logs the error, starts from defaults, amends them with whatever valid keys were present, saves the repaired file, and returns it.

This means a malformed or partial config is repaired rather than fatal — useful when adding fields across versions. amend() merges a JSON object over the current config via a BTreeMap<String, Value> and re-validates against AppConf.

Theme resolution (get_theme). When theme is "system", the backend uses the dark-light crate to detect the OS appearance (defaulting to Light when undetermined); "dark" and "light" map directly. The resolved Theme is applied to the window at build time.

4.2 Build-time configuration (tauri.conf.json)

{
  "productName": "ChatGPT",
  "version": "../package.json",
  "identifier": "com.nofwl.chatgpt",
  "build": {
    "beforeDevCommand": "pnpm dev",
    "devUrl": "http://localhost:1420",
    "beforeBuildCommand": "pnpm build",
    "frontendDist": "../dist"
  },
  "app": {
    "withGlobalTauri": true,
    "windows": [],
    "security": { "csp": null }
  },
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"]
  }
}
Key Value Notes
build.devUrl http://localhost:1420 Must match Vite's fixed port (vite.config.ts, strictPort: true)
build.frontendDist ../dist Where pnpm build (tsc + vite build) emits the bundle
app.windows [] No windows are declared statically — all windows are created at runtime in setup.rs / window.rs
app.security.csp null No Content-Security-Policy is enforced by Tauri (see §7)
bundle.targets all Produce every installer type the host toolchain supports

4.3 Other config files

File Purpose
vite.config.ts React + tsconfig-paths plugins; dev server on port 1420 (strictPort), ignores src-tauri/**
tailwind.config.js Custom colors (app-gray-1/2/3, app-active: #10a37f), tablet: 480px breakpoint, typography + autoprefixer plugins
postcss.config.js Tailwind + autoprefixer
tsconfig.json / tsconfig.node.json TypeScript + path aliases (bundler resolution)
rustfmt.toml Rust formatting (100 col, 4-space, edition 2021)
build.rs Sets MACOSX_DEPLOYMENT_TARGET=10.13; invokes tauri_build::build()
Cargo.toml (workspace) Release profile: panic = "abort", opt-level = "s", lto = true, codegen-units = 1 — optimized for small binary size

5. Requirements

5.1 Build toolchain

Tool Version / constraint Why
Rust >= 1.77.1 (edition 2021) rust-version in src-tauri/Cargo.toml
Node.js >= 18.18 Required by @tauri-apps/api engines
pnpm current Lockfile is pnpm-lock.yaml (lockfileVersion: 9.0); build hooks call pnpm
Tauri CLI 2.0.0-beta.22 Dev dependency @tauri-apps/cli
Platform build deps per Tauri prerequisites e.g. WebView2 (Windows), webkit2gtk (Linux), Xcode CLT (macOS)

5.2 Runtime

  • A working internet connection to reach https://chatgpt.com.
  • An OS web view runtime: WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux (provided/installed per Tauri's platform requirements).
  • An OpenAI account for sign-in.

6. Build, Run, and Release

6.1 Development

# Install JS dependencies
pnpm install

# Run the app in dev mode (starts Vite on :1420, then launches the Tauri shell)
pnpm tauri dev
# Tauri compiles the Rust backend, Vite serves the frontend on http://localhost:1420,
# and a native ChatGPT window opens loading chatgpt.com.

The tauri dev command runs beforeDevCommand (pnpm dev) automatically and points the shell at devUrl.

Tip

The frontend alone can be served with pnpm dev, but the app is only meaningful inside the Tauri shell — getCurrentWebview(), invoke(), and the OS plugins require the Tauri runtime.

6.2 Production build

# Type-check + build the frontend, then bundle native installers for the host platform
pnpm tauri build
# Output installers are written under src-tauri/target/release/bundle/<type>/.
# "targets": "all" produces every bundle type the host toolchain can build.

tauri build runs beforeBuildCommand (pnpm build, i.e. tsc && vite build) to emit ../dist, then compiles the optimized Rust binary using the size-tuned release profile.

Note

Cross-platform installers are produced by building on each target OS (and/or CI), not from a single host. The published Releases cover macOS, Windows, and Linux.

6.3 The bundled ask.js lifecycle

template.rs embeds scripts/ask.js at compile time (include_bytes!) and, at startup (Template::new, called from setup::init), writes/updates a working copy into <config dir>/com.nofwl.chatgpt/scripts/ask.js:

  • It parses the @version header from both the bundled and the on-disk copies (via regex).
  • It overwrites the on-disk file only when the bundled version is newer (semver comparison), when the on-disk version is missing/unparseable, or when the file doesn't exist.

At web view creation, AppConf::load_script(&handle, "ask.js") reads that on-disk copy and injects it as an initialization script.

Important

Because the runtime injects the on-disk scripts/ask.js, advanced users can patch Quick Ask behavior by editing that file — but on the next launch the bundled version will overwrite it if its @version is greater. To make a sticky local change, bump the @version in your edited copy above the bundled value.


7. Security

7.1 Capability / permission model

Tauri 2 gates backend functionality behind an explicit allowlist. capabilities/desktop.json grants a broad set:

{
  "identifier": "desktop-capability",
  "windows": ["*"],
  "remote": { "urls": ["https://chatgpt.com/*"] },
  "platforms": ["linux", "macOS", "windows"],
  "permissions": [
    "window:default", "window:allow-create", "window:allow-start-dragging",
    "window:allow-toggle-maximize", "window:allow-minimize", "window:allow-close",
    "webview:default", "webview:allow-internal-toggle-devtools",
    "webview:allow-set-webview-zoom", "webview:allow-create-webview",
    "webview:allow-create-webview-window", "webview:allow-set-webview-focus",
    "event:default", "event:allow-emit", "event:allow-emit-to",
    "event:allow-listen", "event:allow-unlisten",
    "shell:default", "shell:allow-execute", "shell:allow-open",
    "os:allow-arch", "os:allow-platform", "os:allow-version",
    "os:allow-os-type", "os:default", "shell:default"
  ]
}
Permission group Grants Used by
window:* Create, drag, maximize, minimize, close windows Toolbar, WinTitlebar, settings window
webview:* Create web views/windows, set zoom, focus, toggle devtools Multi-web-view layout, Quick Ask focus
event:* Emit/listen to events navigation:change, resize tracking
shell:allow-open Open URLs/files with the OS default handler Address-chip "open in browser", auto-open downloads
shell:allow-execute Execute shell commands Granted via the shell plugin
os:* Query arch/platform/version/type useInfo platform detection

Caution

The remote.urls ACL (https://chatgpt.com/*) is what scopes these capabilities to remote content loaded from ChatGPT. Combined with withGlobalTauri: true, the remote ChatGPT page has window.__TAURI__ available within the bounds of the granted permissions. This is required for the injected scripts to function, but it also means the trust boundary includes whatever chatgpt.com serves. Anyone forking this app should review whether shell:allow-execute is actually needed (the code paths observed use only shell:allow-open), and consider tightening remote.urls and removing unused permissions.

Warning

app.security.csp is null, so Tauri enforces no Content-Security-Policy. The app relies on loading only chatgpt.com. Hardening deployments should consider defining a CSP and pruning the permission set to the minimum the features require.

7.2 Download handling

The main web view registers an on_download handler (setup.rs):

  • On DownloadEvent::Requested, the destination is rewritten to the OS Downloads directory (path().download_dir()), so files always land there.
  • On DownloadEvent::Finished { success: true, .. }, the file is opened with the OS default application via the shell plugin.

Caution

Auto-opening downloaded files is a convenience that carries risk: any file ChatGPT (or a page served under the allowed origin) hands to the download flow is opened automatically on completion. In higher-security environments, consider disabling auto-open and requiring an explicit user action.

7.3 Data handling

The app stores no conversation data itself. Authentication and chat history live in the embedded web view's session storage and in the user's OpenAI account. The only local persistence is config.json (UI preferences) and the working copy of scripts/ask.js.

7.4 Platform notes

  • On Windows release builds, #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] suppresses the extra console window. Do not remove this attribute.
  • build.rs sets MACOSX_DEPLOYMENT_TARGET=10.13 for macOS compatibility.
  • Builds are unsigned by default; distribution requires the operator to handle code signing / notarization if desired.

8. Operations & Troubleshooting

8.1 Resetting an installation

# Close the app first, then remove its config directory to restore defaults.
# macOS
rm -rf ~/Library/Application\ Support/com.nofwl.chatgpt
# Linux
rm -rf ~/.config/com.nofwl.chatgpt
# On next launch, AppConf::load() recreates config.json with defaults
# and template.rs re-creates scripts/ask.js from the bundled copy.

On Windows, delete %APPDATA%\com.nofwl.chatgpt.

8.2 Known issues and developer-facing notes

These are drawn from the community issue tracker. Several reported issues reference other ChatGPT clients (including Electron-based and official apps) and do not apply to this Tauri 2 codebase — triage accordingly.

Symptom Likely area Notes
Session expired / login fails (macOS) Embedded web view session Reload the main view; sessions behave like browser sessions
Zoom shortcuts (Ctrl +/-/0) inert No global zoom shortcut wired webview:allow-set-webview-zoom is granted but not bound to a shortcut/command in this revision
Multiple windows on link click Link handling INIT_SCRIPT forces targeted links to _blank; these are routed to the system browser
Context-menu crash on previous chats Upstream / web view Originates in the embedded page; reproduce at chatgpt.com to confirm scope
Inline LaTeX not rendering Upstream ChatGPT site Not controlled by this app

Tip

When triaging a bug report, first determine whether it reproduces in a plain browser at chatgpt.com. If it does, it belongs to OpenAI's site, not this wrapper. Only behavior tied to the toolbar, Quick Ask, theming, pin, downloads, window management, or config belongs to this codebase.

8.3 Logging

The backend uses the log crate (info! / error!) in conf.rs and template.rs, and eprintln! for view-positioning failures. Run pnpm tauri dev to see backend output in the terminal during development; release builds suppress the console on Windows.


9. Code Quality & Conventions

  • Separation of concerns: layout math lives in setup.rs/cmd.rs, persistence in conf.rs, constants in constant.rs, script lifecycle in template.rs, and window creation in window.rs. The frontend keeps all backend calls in view/Titlebar.tsx and view/Ask.tsx.
  • Configuration robustness: AppConf self-heals malformed configs rather than crashing (§4.1).
  • Size-tuned release profile: panic = "abort", opt-level = "s", lto = true, codegen-units = 1.
  • Error handling pattern: backend command handlers favor .unwrap() on Tauri/web view calls. This is concise but means an unexpected None/Err (for example a missing web view) will panic the command. View-positioning is the exception — it logs via eprintln! and continues.
  • template.rs uses anyhow for ergonomic error context; conf.rs returns Box<dyn std::error::Error>.

Note

AST extraction over this repo surfaced 20 functions and 2 types (AppConf, Template); there are no CLI commands, HTTP endpoints, or server-side config keys — consistent with a thin desktop wrapper whose "API" is the set of Tauri commands documented in §3.


10. Potential Improvements

These are observations for maintainers, derived from the source as documented.

  • [ ] Tighten the capability set. shell:allow-execute is granted but no observed code path executes shell commands; only shell:allow-open is used. Removing unused permissions reduces the attack surface.
  • [ ] Define a CSP. security.csp is null; an explicit policy would harden the remote-content trust boundary.
  • [ ] Reduce .unwrap() in command handlers. Returning Result and surfacing errors to the frontend would make failures (missing web view, config I/O) recoverable instead of fatal.
  • [ ] Finish the Settings window. view/Settings.tsx is a placeholder; surfacing AppConf fields (and future options) there would remove the need to hand-edit config.json.
  • [ ] Wire zoom shortcuts. The zoom permission is granted but not bound; binding Ctrl/⌘ +/-/0 to set_webview_zoom would address a recurring request.
  • [ ] Gate download auto-open. Make the auto-open-on-finish behavior configurable for security-sensitive users.
  • [ ] Reconsider the dual version numbers. The crate version is 0.0.0; aligning it with the product version would reduce confusion in build artifacts and logs.

11. Quick Reference

Item Value
Bundle identifier com.nofwl.chatgpt
Config file <OS config dir>/com.nofwl.chatgpt/config.json
Injected script (working copy) <OS config dir>/com.nofwl.chatgpt/scripts/ask.js
Dev server http://localhost:1420 (fixed, strictPort)
Frontend build output dist/ (tsc && vite build)
Native build output src-tauri/target/release/bundle/
Embedded site https://chatgpt.com
Backend commands 11 (10 in cmd.rs, 1 in window.rs)
Web views main, titlebar, ask (in core) + settings window
Layout constants TITLEBAR_HEIGHT = 28, ASK_HEIGHT = 120
License AGPL-3.0

Build & run cheat-sheet:

pnpm install        # install JS deps
pnpm tauri dev      # develop (Vite :1420 + native shell)
pnpm tauri build    # produce native installers
pnpm dev            # frontend only (Vite)
pnpm build          # tsc + vite build → dist/