EdgeTerm App Mode lets a workspace behave like a local browser-native app instead of the standard terminal and file-manager shell. It runs entirely inside the current EdgeTerm workspace, keeps using the workspace filesystem, and can return to the normal EdgeTerm Workspace view without resetting the container.
- Runs one workspace as a local app surface.
- Supports multiple runtimes behind one loader.
- Starts automatically when a workspace opens if enabled and
autoStartistrue. - Exits back to the normal workspace with a configurable hotkey.
- Keeps the browser-local container model: no real backend server, no socket listener, no port binding.
Current runtimes:
python: Pyodide-powered Flask/WSGI apps.php: php-wasm app requests with static-file fallback from the document root.static: HTML apps rendered from the workspace filesystem.wine: experimental BoxedWine/Wine launches rendered through EdgeTerm Display.
Future runtimes can plug into the same loader later, including Lua or generic WASM apps.
App Mode is controlled by:
/etc/appmode/config.json
If the file does not exist, EdgeTerm creates a default one.
Default example:
{
"enabled": false,
"runtime": "python",
"entrypoint": "/home/user/app.py",
"staticRoot": "/home/user/public",
"workingDirectory": "/home/user",
"fullscreen": true,
"autoStart": true,
"preserveStateOnExit": true,
"showLoadingOverlay": true,
"exit": {
"method": "hotkey",
"hotkey": "Escape",
"confirmBeforeExit": false
},
"ui": {
"hideWorkspaceChrome": true,
"allowDebugTerminal": false,
"debugTerminalHotkey": "Ctrl+`"
},
"python": {
"appObject": "app",
"appSpec": "app:app",
"framework": "flask",
"routePrefix": "/",
"allowFilesystemAccess": true
},
"static": {
"indexFile": "index.html",
"allowInlineScripts": true
}
}enabled- Turns App Mode on for the current workspace.
runtimepythonorstatic.
entrypoint- Python script path for
python. - Optional HTML entry file for
static.
- Python script path for
staticRoot- Base directory for static assets and static HTML routing.
workingDirectory- Directory EdgeTerm switches into before starting the app.
fullscreen- Enables fullscreen App Mode presentation.
autoStart- Starts App Mode automatically when the workspace opens.
preserveStateOnExit- Keeps the current app surface alive when leaving App Mode.
showLoadingOverlay- Shows a loading screen while the app runtime starts.
method- Currently
hotkey.
- Currently
hotkey- Default is
Escape.
- Default is
confirmBeforeExit- If
true, asks for confirmation before leaving App Mode.
- If
hideWorkspaceChrome- Hides the normal workspace chrome while the app is active.
allowDebugTerminal- Enables a debug-terminal hotkey path.
debugTerminalHotkey- Default is `Ctrl+``.
- Current behavior returns to the normal terminal view.
appObject- Name of the app object exported by the script, default
app. Used when deriving an app spec fromentrypoint.
- Name of the app object exported by the script, default
appSpec- WSGI app import path, default
app:app.
- WSGI app import path, default
framework- Default is
flask. Usewsgifor another WSGI callable oredgetermfor the legacy EdgeTerm route dispatcher.
- Default is
routePrefix- Prefix used for Python route dispatch.
allowFilesystemAccess- Documents whether the Python app can use the workspace filesystem. Current runtime keeps normal workspace filesystem access.
indexFile- Default index file used when routing a static directory.
allowInlineScripts- Allows or strips inline
<script>blocks in static HTML mode.
- Allows or strips inline
You can enable App Mode in two ways:
- Edit
/etc/appmode/config.jsondirectly. - Open
Settingsinside EdgeTerm Workspace and use theApp Modepanel.
The settings panel lets you:
- enable or disable App Mode
- pick
pythonorstatic - set entrypoint path
- set static root
- set working directory
- configure fullscreen
- configure auto-start
- configure the exit hotkey
- launch App Mode immediately
- open the config file in the built-in editor
Python App Mode runs Flask as a real WSGI app inside Pyodide. There is no TCP listener or external HTTP server; EdgeTerm builds WSGI requests in the browser and dispatches them directly to the Flask app.
Example:
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
return """
<h1>Hello from EdgeTerm App Mode</h1>
<p><a href="/api/data">Open JSON route</a></p>
"""
@app.route("/api/data")
def data():
return {"value": 123, "query": request.args.to_dict()}For a default /home/user/app.py, EdgeTerm uses the WSGI app spec app:app, meaning module app and object app.
- Flask route handling,
request, JSON responses, status tuples, redirects, cookies, and Werkzeug response objects are handled through Flask's WSGI interface. - EdgeTerm auto-installs Flask into the Pyodide runtime if it is missing and package installation is available.
- If a Flask route is not found, EdgeTerm can still serve matching files from
staticRoot.
Static mode loads HTML directly from the workspace filesystem.
Typical layout:
/home/user/public/
index.html
app.js
styles.css
logo.png
Example config:
{
"enabled": true,
"runtime": "static",
"entrypoint": "/home/user/public/index.html",
"staticRoot": "/home/user/public",
"ui": {
"showAddressBar": false
}
}Static mode supports:
- HTML from the workspace filesystem
- linked JS, CSS, images, audio, and video assets rewritten into browser-safe blob URLs
- internal navigation between local HTML pages
- local
fetch()calls routed back into the App Mode loader for workspace files
entrypointshould be a Python file such as/home/user/app.pyworkingDirectoryis applied before running the script
- set
runtimetophp entrypointcan point directly to a PHP file such as/home/user/public/index.php- if
entrypointpoints to a directory, App Mode treats it as the PHP document root and looks forindex.php - static files beside the PHP app can still be served from the same document root
entrypointcan point directly to an HTML file- if omitted or directory-like, EdgeTerm uses
staticRoot+static.indexFile
Set ui.showAddressBar to true to show a small App Mode URL bar above the iframe. It lets users type or refresh internal app paths such as /, /admin, or /index.php?page=home without returning to the workspace.
Primary exit is the configured hotkey:
- default:
Escape
When App Mode exits:
- EdgeTerm returns to the normal workspace view
- the container is not reset
- if
preserveStateOnExitistrue, the app surface stays available for quick re-entry
- Runs locally through Pyodide
- No backend server
- No sockets or bound ports
- Browser-side WSGI request dispatch only
- Loads local files from the workspace filesystem
- Renders them inside the App Mode surface
Experimental Wine App Mode config:
{
"enabled": true,
"runtime": "wine",
"entrypoint": "notepad.exe",
"workingDirectory": "/home/user",
"fullscreen": true,
"autoStart": true,
"preserveStateOnExit": true,
"wine": {
"prefix": "/home/user/.wine",
"runtimePackage": "boxedwine",
"winePackage": "wine-runtime",
"display": true,
"sharedApp": true
}
}Wine App Mode is local-first: BoxedWine and Wine run in the browser through WebAssembly, GUI output attaches to Display, and the Wine prefix persists in the workspace. Cloud shares/snapshots can carry the prefix and package files, but the backend does not execute Win32 code.
- Flask apps run in-process through WSGI; raw sockets and real port binding are not available.
allowFilesystemAccessis descriptive right now; Python apps still use the workspace filesystem normally.- Static asset rewriting handles common linked assets, but complex CSS
url(...)rewriting is not fully implemented. - Browser history and URL bar are not treated like a normal hosted website.
- Debug terminal hotkey currently returns to the normal terminal view rather than opening a live terminal drawer inside the app.
- No cloud publish or share URL support yet.
- BoxedWine/Wine support is experimental and requires a compatible browser WASM asset bundle.
- No Lua App Mode runtime yet.
The loader is designed to grow into more runtimes without changing workspace-level controls:
- Lua runtime
- generic WebAssembly app runtime
- BoxedWine loader adapters for SDL/OpenGL, clipboard, and multi-window metadata
- richer static router behavior
- SPA history synchronization
- app-to-terminal debug drawer
- packaged app manifests
- exportable local app bundles