AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Tauri Django React

skill-firstp1ck-pi-coding-agent-forge-tauri-django-react · by Firstp1ck

Agents should invoke this skill for Tauri + Django + React desktop apps, especially backend lifecycle, CORS/auth, frontend integration, mandatory light/dark theming, German/English i18n, build packaging, dual desktop/web deployment, Rust commands, and platform-specific gotchas.

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add skill-firstp1ck-pi-coding-agent-forge-tauri-django-react

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution Used
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-firstp1ck-pi-coding-agent-forge-tauri-django-react)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Tauri Django React? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Tauri + Django + React Integration Patterns

Dense technical reference for building desktop applications that use Tauri as the native shell, Django as the backend API, and React as the frontend UI. Patterns are designed for reuse across projects.

This skill is self-contained; it must not depend on package-level docs/ files at runtime.


Quick Start

Identify the Integration Concern

  1. Which layers are involved? If the question spans two or more of Rust/Python/TypeScript, this skill applies.
  2. Is it a lifecycle issue? Port selection, process spawning, health checking, cleanup -> Backend Lifecycle section.
  3. Is it an auth issue? Cookies not working in Tauri, CORS errors, session tokens -> Cross-Origin Auth section.
  4. Is it a build issue? PyInstaller fails, bundle missing files, tauri build errors -> Build Pipeline section.
  5. Is it a dual-mode issue? Works in browser but not Tauri (or vice versa) -> Frontend Integration section.
  6. Mandatory local runner: When creating or updating a project with this skill, add or maintain an executable scripts/start.sh that starts both Django and React for local development.
  7. Mandatory frontend UX baseline: Ask the user for separate light-mode and dark-mode background images before finalizing visual work; implement a persistent light/dark mode setting; implement German/English i18n for every user-facing string.
  8. Mandatory GitHub release automation: When creating or updating a project with this skill, add or maintain both .github/workflows/release.yml and an executable scripts/release.sh release helper.
  9. Mandatory configurable update path: Add or maintain a filesystem update checker that reads a configurable update source directory before checking/installing desktop updates.

Mandatory GitHub Release Automation

Every Tauri + Django + React project handled by this skill must include GitHub release automation:

  • .github/workflows/release.yml builds the Tauri desktop app, uploads installer artifacts, and publishes a GitHub Release for v* tags or manual workflow_dispatch runs.
  • scripts/release.sh updates project version files, regenerates relevant locks when tooling is available, commits/pushes version changes, creates an annotated vX.X.X tag, and pushes the tag to trigger the workflow.
  • dev/RELEASES/ is the canonical release-notes directory; release notes use dev/RELEASES/RELEASE_vX.X.X.md and the workflow falls back to the annotated tag message when the file is absent.
  • The workflow and script must be adapted to the actual app name, artifact names, package names, secrets, signing/updater setup, and platform targets. Do not copy private updater/Gist/signing logic unless the current project explicitly has that configuration.

Mandatory Configurable Filesystem Update System

Every desktop-distributed Tauri + Django + React project handled by this skill must include a configurable filesystem update path unless the user explicitly chooses Tauri's built-in updater only:

  • Canonical setting: settings.update_source_path in the app config (config.json) stores the local/network directory to scan for installer updates.
  • Config overrides: support APP_UPDATE_SOURCE_PATH or TAURI_UPDATE_SOURCE_PATH env vars for deployment overrides; support APP_CONFIG_PATH for a custom runtime config file; support APP_UPDATE_INSTALLER_PATTERNS for app-specific installer filename regexes.
  • Version source: Rust passes TAURI_APP_VERSION and APP_VERSION from app.package_info().version into the Django backend. Django falls back to APP_VERSION, Django settings, or pyproject.toml in development.
  • Installer discovery: Django scans the configured directory for versioned installer filenames, extracts semantic X.Y.Z, compares integer tuples, and returns the newest installer newer than the current version. Keep the current-version installer path too so the UI can offer reinstall.
  • Safety boundary: only launch installers that exist, are regular files, match the configured filename pattern, and resolve inside the configured update source directory.
  • API contract: expose GET /api/updates/check/ for availability, authenticated GET/PATCH /api/updates/settings/ for the configurable path, and authenticated POST /api/updates/install/ for launching a validated installer. If check is public, redact local/network paths unless the request is authenticated.
  • Frontend contract: provide an update service and menu/button whose label changes between Check update and Install update; check at startup and periodically; prompt before launching the installer; close the Tauri app after a successful installer launch.
  • Release artifact contract: release workflows/scripts must produce installer names that match the checker (for example -1.2.3-Setup.exe or Tauri NSIS _1.2.3_x64-setup.exe).

Mandatory scripts/start.sh Local Runner

Every Tauri + Django + React project handled by this skill must include scripts/start.sh unless the user explicitly declines it. The script is the one-command local development entry point and must:

  • use uv for backend dependency sync and Django commands;
  • use bun for frontend dependency install and Vite/React startup;
  • run Django migrations before starting the backend;
  • optionally run a clearly named seed/demo command when the project has one;
  • start Django and React concurrently;
  • set VITE_API_BASE_URL to the Django /api base URL unless already provided;
  • print the frontend and backend URLs;
  • trap EXIT, INT, and TERM and stop both child processes;
  • be executable (chmod +x scripts/start.sh).

Recommended template:

#!/usr/bin/env bash
set -Eeuo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKEND_DIR="$ROOT_DIR/backend"
FRONTEND_DIR="$ROOT_DIR/frontend"

BACKEND_HOST="${BACKEND_HOST:-127.0.0.1}"
BACKEND_PORT="${BACKEND_PORT:-8000}"
FRONTEND_URL="http://localhost:5173"
BACKEND_URL="http://${BACKEND_HOST}:${BACKEND_PORT}"

backend_pid=""
frontend_pid=""

cleanup() {
  echo
  echo "Stopping application..."
  if [[ -n "$frontend_pid" ]] && kill -0 "$frontend_pid" 2>/dev/null; then
    kill "$frontend_pid" 2>/dev/null || true
  fi
  if [[ -n "$backend_pid" ]] && kill -0 "$backend_pid" 2>/dev/null; then
    kill "$backend_pid" 2>/dev/null || true
  fi
  wait 2>/dev/null || true
}

trap cleanup EXIT INT TERM

(
  cd "$BACKEND_DIR"
  uv sync --all-groups
  uv run python manage.py migrate
  if uv run python manage.py help seed_demo >/dev/null 2>&1; then
    uv run python manage.py seed_demo
  fi
)

(
  cd "$FRONTEND_DIR"
  bun install
)

(
  cd "$BACKEND_DIR"
  uv run python manage.py runserver "${BACKEND_HOST}:${BACKEND_PORT}"
) &
backend_pid=$!

(
  cd "$FRONTEND_DIR"
  VITE_API_BASE_URL="${VITE_API_BASE_URL:-${BACKEND_URL}/api}" bun run dev
) &
frontend_pid=$!

echo "Frontend: $FRONTEND_URL"
echo "Backend:  $BACKEND_URL/api"
echo "Press Ctrl+C to stop both processes."

wait -n "$backend_pid" "$frontend_pid"

Backend Lifecycle (Tauri / Rust)

The core responsibility of the Rust layer: start the Django backend, confirm it's healthy, tell the frontend, and clean up on exit.

Port Selection

const DEFAULT_BACKEND_PORT: u16 = 8000;
const MAX_PORT_OFFSET: u16 = 10;

fn find_available_port() -> Option {
    for offset in 0..=MAX_PORT_OFFSET {
        let port = DEFAULT_BACKEND_PORT + offset;
        if TcpListener::bind(("127.0.0.1", port)).is_ok() {
            return Some(port);
        }
    }
    None
}
  • Probes 8000-8010 using TcpListener::bind() -- bind succeeds means port is free
  • The selected port is passed to Django via BACKEND_PORT env var and to React via a Tauri event
  • Never hardcode the port in frontend code

Subprocess Spawning

let mut cmd = Command::new(&backend_path);
cmd.current_dir(&backend_dir);
cmd.env("BACKEND_PORT", port.to_string());
cmd.env("DJANGO_DATABASE_PATH", db_path.to_string_lossy().to_string());
cmd.env("TAURI_APP_DATA_DIR", app_data.to_string_lossy().to_string());
cmd.env("TAURI_APP_VERSION", app.package_info().version.to_string());
cmd.env("APP_VERSION", app.package_info().version.to_string());

#[cfg(target_os = "windows")]
{
    use std::os::windows::process::CommandExt;
    cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
}

let child = cmd.spawn()?;
  • Backend path resolved from resource_dir/python-backend/tauri_entry (+ .exe on Windows)
  • CREATE_NO_WINDOW (0x08000000) prevents a visible console window on Windows
  • Pass the Tauri package version to Django via TAURI_APP_VERSION/APP_VERSION so update checks compare against the installed app version
  • Store the Child handle in Mutex> for later cleanup
  • On Windows, strip UNC \\?\ prefix from paths before passing to Python

Health Polling

let client = reqwest::blocking::Client::builder()
    .timeout(Duration::from_secs(2))
    .build()?;

for attempt in 0..max_retries {
    match client.get(&format!("http://127.0.0.1:{}/api/health/", port)).send() {
        Ok(resp) if resp.status().is_success() => {
            app_handle.emit("backend-ready", json!({ "port": port }))?;
            return Ok(());
        }
        _ => std::thread::sleep(Duration::from_millis(500)),
    }
}
app_handle.emit("backend-error", json!({ "error": "Backend failed to start" }))?;
  • Poll GET /api/health/ with a 2-second timeout per request
  • Retry with 500ms sleep between attempts (typically 30-60 retries = 15-30 seconds max wait)
  • On success: emit backend-ready with the port number
  • On failure: emit backend-error with a description

Process Cleanup

fn kill_backend_process(state: &Mutex>) {
    if let Some(mut backend) = state.lock().unwrap().take() {
        #[cfg(target_os = "windows")]
        {
            // taskkill /F /T kills the entire process tree
            let _ = std::process::Command::new("taskkill")
                .args(["/F", "/T", "/PID", &backend.child.id().to_string()])
                .creation_flags(0x08000000)
                .output();
        }
        #[cfg(not(target_os = "windows"))]
        {
            let _ = backend.child.kill();
        }
    }
}
  • Windows: Must use taskkill /F /T /PID to kill the entire process tree (PyInstaller spawns child processes)
  • Unix: child.kill() sends SIGKILL, which is sufficient
  • Call cleanup on: app exit, window close, before_exit event, and on_window_event(CloseRequested)

Window Lifecycle

  • Windows: Minimize to system tray on close (api.prevent_close() + window.hide())
  • Linux/macOS: Quit the application on window close (standard behavior)
  • Tray icon re-shows the window on click

Event Emission Summary

| Event | Payload | When | |---|---|---| | backend-ready | { "port": number } | Health check passes | | backend-error | { "error": string } | Health check exhausts retries, or spawn fails |


Cross-Origin Authentication (Django / Python)

WebView in Tauri runs from tauri://localhost (or https://tauri.localhost), which means cookies may not work reliably for http://127.0.0.1:8000. The solution: a hybrid auth system.

HybridSessionAuthentication

class HybridSessionAuthentication(authentication.SessionAuthentication):
    def authenticate(self, request):
        auth_header = authentication.get_authorization_header(request)
        if auth_header:
            auth_parts = auth_header.decode("utf-8").split()
            if auth_parts and auth_parts[0].lower() == "session":
                token_auth = SessionTokenAuthentication()
                return token_auth.authenticate(request)
        return super().authenticate(request)
  • Priority 1: Check for Authorization: Session header (Tauri path)
  • Priority 2: Fall back to standard cookie-based session auth (browser path)
  • Register in DRF settings: DEFAULT_AUTHENTICATION_CLASSES = ["api.authentication.HybridSessionAuthentication"]

SessionTokenAuthentication

class SessionTokenAuthentication:
    def authenticate(self, request):
        session_key = self._extract_key(request)
        session = SessionStore(session_key=session_key)
        if not session.exists(session_key):
            raise AuthenticationFailed("Invalid session")
        user_id = session.get("_auth_user_id")
        user = User.objects.get(pk=user_id)
        session["_session_expiry"] = settings.SESSION_COOKIE_AGE  # sliding expiration
        session.save()
        return (user, None)
  • Looks up the session in Django's session store using the key from the header
  • Implements sliding expiration by resetting _session_expiry on each authenticated request

CORS Configuration

CORS_ALLOWED_ORIGIN_REGEXES = [
    r"^tauri://localhost$",
    r"^https?://tauri\.localhost$",
    r"^https?://localhost(:\d+)?$",
    r"^https?://127\.0\.0\.1(:\d+)?$",
]
CORS_ALLOW_CREDENTIALS = True

CSRF Configuration

CSRF_TRUSTED_ORIGINS = [
    "tauri://localhost",
    "https://tauri.localhost",
    "http://localhost",
    "http://127.0.0.1",
] + [f"http://localhost:{p}" for p in range(8000, 8011)]
  + [f"http://127.0.0.1:{p}" for p in range(8000, 8011)]
CSRF_COOKIE_SAMESITE = "Lax"
  • Include the full port range (8000-8010) to handle dynamic port selection
  • CORS_ALLOW_CREDENTIALS = True is required for session cookies in web mode

Health Endpoint

@api_view(["GET"])
@permission_classes([AllowAny])
def health_check(_request):
    return Response({"status": "ok"}, status=200)
  • Must be unauthenticated (AllowAny) -- Tauri polls this before any user login
  • URL: GET /api/health/
  • Keep the response minimal for fast polling

Frontend Integration (React / TypeScript)

Mandatory Theme + Background + i18n Baseline

Every React frontend created or substantially updated with this skill must include these UI foundations unless the user explicitly declines them:

  1. Ask for backgrounds first: Before finalizing visual styling, ask the user whether they have separate light-mode and dark-mode background images. If supplied, store them under frontend/src/assets/ with stable names such as background_light.png and background_dark.png; if not supplied, use temporary CSS gradients and leave a clear TODO.
  2. Persistent light/dark setting: Implement a Theme = "light" | "dark" state, initialize it from localStorage, fall back to prefers-color-scheme, write document.documentElement.dataset.theme, set document.documentElement.style.colorScheme, and expose a translated toggle button.
  3. Theme-aware backgrounds: Wire backgrounds through CSS selectors (body for light, :root[data-theme='dark'] body for dark). Use overlays/gradients above the images so text contrast remains acceptable in both modes.
  4. German/English i18n: Implement Language = "en" | "de", complete dictionaries for both languages, a typed translate()/t() helper with parameter interpolation, language detection from localStorage and navigator.language, a language switcher, and document.documentElement.lang + document.title updates.
  5. No hardcoded UI strings: All visible labels, buttons, status messages, errors, ARIA labels, document titles, and theme/language controls must use i18n keys in both English and German.

Recommended theme pattern:

type Theme = "light" | "dark";
const themeStorageKey = "app-theme";

function getInitialTheme(): Theme {
  const stored = window.localStorage.getItem(themeStorageKey);
  if (stored === "light" || stored === "dark") return stored;
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

useEffect(() => {
  document.documentElement.dataset.theme = theme;
  document.documentElement.style.colorScheme = theme;
  window.localStorage.setItem(themeStorageKey, theme);
}, [theme]);

Recommended ba

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.