Install
$ agentstack add skill-firstp1ck-pi-coding-agent-forge-tauri-django-react ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →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
- Which layers are involved? If the question spans two or more of Rust/Python/TypeScript, this skill applies.
- Is it a lifecycle issue? Port selection, process spawning, health checking, cleanup -> Backend Lifecycle section.
- Is it an auth issue? Cookies not working in Tauri, CORS errors, session tokens -> Cross-Origin Auth section.
- Is it a build issue? PyInstaller fails, bundle missing files,
tauri builderrors -> Build Pipeline section. - Is it a dual-mode issue? Works in browser but not Tauri (or vice versa) -> Frontend Integration section.
- Mandatory local runner: When creating or updating a project with this skill, add or maintain an executable
scripts/start.shthat starts both Django and React for local development. - 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.
- Mandatory GitHub release automation: When creating or updating a project with this skill, add or maintain both
.github/workflows/release.ymland an executablescripts/release.shrelease helper. - 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.ymlbuilds the Tauri desktop app, uploads installer artifacts, and publishes a GitHub Release forv*tags or manualworkflow_dispatchruns.scripts/release.shupdates project version files, regenerates relevant locks when tooling is available, commits/pushes version changes, creates an annotatedvX.X.Xtag, and pushes the tag to trigger the workflow.dev/RELEASES/is the canonical release-notes directory; release notes usedev/RELEASES/RELEASE_vX.X.X.mdand 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_pathin the app config (config.json) stores the local/network directory to scan for installer updates. - Config overrides: support
APP_UPDATE_SOURCE_PATHorTAURI_UPDATE_SOURCE_PATHenv vars for deployment overrides; supportAPP_CONFIG_PATHfor a custom runtime config file; supportAPP_UPDATE_INSTALLER_PATTERNSfor app-specific installer filename regexes. - Version source: Rust passes
TAURI_APP_VERSIONandAPP_VERSIONfromapp.package_info().versioninto the Django backend. Django falls back toAPP_VERSION, Django settings, orpyproject.tomlin 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, authenticatedGET/PATCH /api/updates/settings/for the configurable path, and authenticatedPOST /api/updates/install/for launching a validated installer. Ifcheckis public, redact local/network paths unless the request is authenticated. - Frontend contract: provide an update service and menu/button whose label changes between
Check updateandInstall 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.exeor 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
uvfor backend dependency sync and Django commands; - use
bunfor 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_URLto the Django/apibase URL unless already provided; - print the frontend and backend URLs;
- trap
EXIT,INT, andTERMand 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_PORTenv 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(+.exeon Windows) CREATE_NO_WINDOW(0x08000000) prevents a visible console window on Windows- Pass the Tauri package version to Django via
TAURI_APP_VERSION/APP_VERSIONso update checks compare against the installed app version - Store the
Childhandle inMutex>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-readywith the port number - On failure: emit
backend-errorwith 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 /PIDto 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_exitevent, andon_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: Sessionheader (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_expiryon 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 = Trueis 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:
- 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 asbackground_light.pngandbackground_dark.png; if not supplied, use temporary CSS gradients and leave a clear TODO. - Persistent light/dark setting: Implement a
Theme = "light" | "dark"state, initialize it fromlocalStorage, fall back toprefers-color-scheme, writedocument.documentElement.dataset.theme, setdocument.documentElement.style.colorScheme, and expose a translated toggle button. - Theme-aware backgrounds: Wire backgrounds through CSS selectors (
bodyfor light,:root[data-theme='dark'] bodyfor dark). Use overlays/gradients above the images so text contrast remains acceptable in both modes. - German/English i18n: Implement
Language = "en" | "de", complete dictionaries for both languages, a typedtranslate()/t()helper with parameter interpolation, language detection fromlocalStorageandnavigator.language, a language switcher, anddocument.documentElement.lang+document.titleupdates. - 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.
- Author: Firstp1ck
- Source: Firstp1ck/pi-coding-agent-forge
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.