Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
ae787ca060
commit
4c1c6f31d5
@ -10,12 +10,13 @@ Deliver one installable macOS app that:
|
||||
- launches an embedded backend executable from app resources
|
||||
- opens the web UI only inside `WKWebView`
|
||||
- persists settings locally and supports native-to-web sync
|
||||
- provides native app-level help/onboarding (not only in the web UI)
|
||||
- can be packaged as a DMG
|
||||
|
||||
## Layout Standard
|
||||
Use this layout for new projects:
|
||||
- `web/` for web backend source (`app.py`, modules, requirements)
|
||||
- `mac/<AppName>Mac/` for Xcode project and SwiftUI shell
|
||||
- `web/src/` for web backend source (`app.py`, modules, requirements)
|
||||
- `mac/src/` for Xcode project and SwiftUI shell sources
|
||||
- `scripts/` for build and packaging automation
|
||||
- `docs/` for architecture and onboarding docs
|
||||
|
||||
@ -34,35 +35,52 @@ Detailed naming guidance: `references/layout-and-naming.md`
|
||||
|
||||
3. Build backend into a single executable.
|
||||
- Add `scripts/build_embedded_backend.sh` that compiles backend into a one-file executable and copies it into the mac target resource folder.
|
||||
- Use generic discovery:
|
||||
- discover `*.xcodeproj` under `mac/src`
|
||||
- discover scheme via `xcodebuild -list -json` (fallback to project name)
|
||||
- discover/create `EmbeddedBackend` folder under selected project sources
|
||||
- Include all required web files/modules in build inputs.
|
||||
- Default backend name should be stable (for example `WebBackend`) and configurable via env.
|
||||
|
||||
4. Implement SwiftUI host shell.
|
||||
- Use `@Observable` host state (no `ObservableObject`/Combine).
|
||||
- Start/stop backend process, detect available local port, and handle retries.
|
||||
- Render URL in `WKWebView` only.
|
||||
- Keep app responsive when backend fails and surface actionable status.
|
||||
- Resolve backend executable by checking both current and legacy names during migration.
|
||||
- Add a native toolbar `Help` button that opens bundled help content in-app.
|
||||
|
||||
5. Define settings sync contract.
|
||||
- Use shared settings file (for example `~/.<app>/settings.json`) as source of truth.
|
||||
- Normalize settings both in web app and native app.
|
||||
- Pass effective settings via URL query params on launch/reload/restart.
|
||||
- Keep onboarding limited to starter fields; preserve advanced fields.
|
||||
- Add web-only fallback access to help/onboarding (for users not using the mac wrapper).
|
||||
|
||||
6. Automate packaging.
|
||||
- Add `scripts/build_selfcontained_mac_app.sh` to build embedded backend then Xcode app.
|
||||
- Add `scripts/create_installer_dmg.sh` for distributable DMG.
|
||||
- Use generic app bundle discovery for DMG defaults where possible.
|
||||
- Standardize installer artifacts under `build/dmg/` (not repo root).
|
||||
- Ensure `.gitignore` excludes generated artifacts (`build/`, `*.dmg`, temp `rw.*.dmg`).
|
||||
|
||||
7. Validate.
|
||||
- Python syntax: `python -m py_compile` on web entrypoint.
|
||||
- Tests: `PYTHONPATH=web/src pytest -q web/src/tests`.
|
||||
- Xcode build: `xcodebuild ... build`.
|
||||
- Runtime check: no external browser opens, webview loads locally, settings persist across relaunch.
|
||||
- Verify help works in both modes:
|
||||
- native toolbar help popup in mac app
|
||||
- sidebar help fallback in web-only mode
|
||||
|
||||
## Required Deliverables
|
||||
- Embedded backend build script
|
||||
- macOS app host that launches backend + WKWebView
|
||||
- Shared settings sync path
|
||||
- Native help/onboarding popup + web fallback help entry
|
||||
- README section for local build + DMG workflow
|
||||
- Verified build commands and final artifact locations
|
||||
- `.gitignore` updated for build/installer outputs
|
||||
|
||||
## References
|
||||
- Layout and migration rules: `references/layout-and-naming.md`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "macOS Self-Contained Web App"
|
||||
short_description: "Embed web backend in local Mac app."
|
||||
default_prompt: "Create or refactor this project into a self-contained macOS app wrapper for a local web backend running inside WKWebView."
|
||||
short_description: "Embed a local web backend in a self-contained macOS WKWebView app."
|
||||
default_prompt: "Create or refactor this project into a self-contained macOS app wrapper with web/src + mac/src layout, embedded backend binary, native Help popup, DMG output in build/dmg, and proper .gitignore rules."
|
||||
|
||||
@ -7,30 +7,41 @@
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
WEB_SRC_DIR="$ROOT_DIR/web/src"
|
||||
MAC_SRC_DIR="${MAC_SRC_DIR:-$ROOT_DIR/mac/src}"
|
||||
PYTHON_BIN="$ROOT_DIR/.venv/bin/python"
|
||||
APP_NAME="TraderBackend"
|
||||
BACKEND_BIN_NAME="${BACKEND_BIN_NAME:-WebBackend}"
|
||||
BUILD_ROOT="$ROOT_DIR/dist-backend-build"
|
||||
DIST_PATH="$BUILD_ROOT/dist"
|
||||
WORK_PATH="$BUILD_ROOT/build"
|
||||
SPEC_PATH="$BUILD_ROOT/spec"
|
||||
TARGET_DIR="$ROOT_DIR/mac/TraderMac/TraderMac/EmbeddedBackend"
|
||||
PROJECT_PATH="${MAC_PROJECT_PATH:-}"
|
||||
SCHEME="${MAC_SCHEME:-}"
|
||||
TARGET_DIR="${EMBEDDED_BACKEND_DIR:-}"
|
||||
|
||||
if [[ -z "$PROJECT_PATH" ]]; then
|
||||
PROJECT_PATH="$(find "$MAC_SRC_DIR" -maxdepth 4 -name "*.xcodeproj" | sort | head -n 1)"
|
||||
fi
|
||||
PROJECT_DIR="$(dirname "$PROJECT_PATH")"
|
||||
if [[ -z "$TARGET_DIR" ]]; then
|
||||
TARGET_DIR="$(find "$PROJECT_DIR" -maxdepth 4 -type d -name EmbeddedBackend | head -n 1)"
|
||||
fi
|
||||
mkdir -p "$DIST_PATH" "$WORK_PATH" "$SPEC_PATH" "$TARGET_DIR"
|
||||
|
||||
"$PYTHON_BIN" -m pip install -q pyinstaller
|
||||
|
||||
"$PYTHON_BIN" -m PyInstaller \
|
||||
--noconfirm --clean --onefile \
|
||||
--name "$APP_NAME" \
|
||||
--name "$BACKEND_BIN_NAME" \
|
||||
--distpath "$DIST_PATH" \
|
||||
--workpath "$WORK_PATH" \
|
||||
--specpath "$SPEC_PATH" \
|
||||
--add-data "$ROOT_DIR/web/app.py:." \
|
||||
--add-data "$ROOT_DIR/web/<module_dir>:<module_dir>" \
|
||||
"$ROOT_DIR/web/backend_embedded_launcher.py"
|
||||
--add-data "$WEB_SRC_DIR/app.py:." \
|
||||
--add-data "$WEB_SRC_DIR/<module_dir>:<module_dir>" \
|
||||
"$WEB_SRC_DIR/backend_embedded_launcher.py"
|
||||
|
||||
cp "$DIST_PATH/$APP_NAME" "$TARGET_DIR/$APP_NAME"
|
||||
chmod +x "$TARGET_DIR/$APP_NAME"
|
||||
cp "$DIST_PATH/$BACKEND_BIN_NAME" "$TARGET_DIR/$BACKEND_BIN_NAME"
|
||||
chmod +x "$TARGET_DIR/$BACKEND_BIN_NAME"
|
||||
```
|
||||
|
||||
## SwiftUI Host Requirements
|
||||
@ -40,6 +51,7 @@ chmod +x "$TARGET_DIR/$APP_NAME"
|
||||
- Render backend URL in `WKWebView`.
|
||||
- Retry provisional load failure after short delay.
|
||||
- Keep debug controls behind `#if DEBUG`.
|
||||
- Add toolbar Help action that opens a bundled local `help.html` in a sheet/webview.
|
||||
|
||||
## Settings Sync Contract
|
||||
- Shared path: `~/.<app>/settings.json`
|
||||
@ -47,6 +59,7 @@ chmod +x "$TARGET_DIR/$APP_NAME"
|
||||
- Load shared file before app launch.
|
||||
- Push normalized fields as query params when launching/reloading webview.
|
||||
- Persist setup-sheet changes back to shared file.
|
||||
- Keep a legacy fallback path for migration when needed.
|
||||
|
||||
## Packaging Commands
|
||||
- Build app:
|
||||
@ -61,9 +74,26 @@ chmod +x "$TARGET_DIR/$APP_NAME"
|
||||
APP_BUNDLE_PATH="dist-mac/<timestamp>/<AppName>Mac.app" ./scripts/create_installer_dmg.sh
|
||||
```
|
||||
|
||||
Expected DMG output:
|
||||
|
||||
```text
|
||||
build/dmg/<AppName>-<timestamp>.dmg
|
||||
```
|
||||
|
||||
## Git Ignore Baseline
|
||||
|
||||
```gitignore
|
||||
build/
|
||||
*.dmg
|
||||
rw.*.dmg
|
||||
```
|
||||
|
||||
## Verification Checklist
|
||||
- No external browser window opens.
|
||||
- App starts with embedded backend from app resources.
|
||||
- `WKWebView` loads local URL.
|
||||
- Settings persist across relaunch and remain in sync.
|
||||
- Native Help popup renders bundled content.
|
||||
- Web-only run has sidebar help fallback.
|
||||
- DMG installs and runs on a second machine.
|
||||
- DMG is produced under `build/dmg/` and repo root stays clean.
|
||||
|
||||
@ -5,44 +5,68 @@
|
||||
```text
|
||||
<repo-root>/
|
||||
web/
|
||||
run.sh
|
||||
src/
|
||||
app.py
|
||||
<web modules>
|
||||
requirements.txt
|
||||
ONBOARDING.md
|
||||
mac/
|
||||
<AppName>Mac/
|
||||
<AppName>Mac.xcodeproj/
|
||||
<AppName>Mac/
|
||||
src/
|
||||
<Project>.xcodeproj/
|
||||
App/
|
||||
ContentView.swift
|
||||
<AppName>MacApp.swift
|
||||
<AppMain>.swift
|
||||
EmbeddedBackend/
|
||||
<AppName>Backend
|
||||
WebBackend
|
||||
Help/
|
||||
help.html
|
||||
AppTests/
|
||||
AppUITests/
|
||||
scripts/
|
||||
build_embedded_backend.sh
|
||||
build_selfcontained_mac_app.sh
|
||||
create_installer_dmg.sh
|
||||
build/
|
||||
dmg/
|
||||
<AppName>-<timestamp>.dmg
|
||||
docs/
|
||||
architecture.md
|
||||
onboarding.md
|
||||
```
|
||||
|
||||
## Naming Rules
|
||||
- Repo: lowercase hyphenated (`trader-desktop-shell`).
|
||||
- macOS app target/project: PascalCase with `Mac` suffix (`TraderMac`).
|
||||
- Embedded backend binary: PascalCase + `Backend` (`TraderBackend`).
|
||||
- macOS target/scheme: PascalCase (`TraderMac` or project-defined), but source folders should stay generic (`App`, `AppTests`, `AppUITests`).
|
||||
- Embedded backend binary: stable generic default (`WebBackend`) with env override support.
|
||||
- Settings directory: lowercase snake or kebab (`~/.trader_app`).
|
||||
- Environment port var: uppercase snake (`TRADER_PORT`).
|
||||
- Environment port var: uppercase snake (keep legacy fallbacks during migration).
|
||||
|
||||
## Script Discovery Rules
|
||||
- Build scripts should discover the first `*.xcodeproj` under `mac/src` unless `MAC_PROJECT_PATH` is provided.
|
||||
- Build scripts should discover scheme via `xcodebuild -list -json` unless `MAC_SCHEME` is provided.
|
||||
- Embedded backend target dir should be derived from selected project sources or `EMBEDDED_BACKEND_DIR`.
|
||||
- Python tests should run with `PYTHONPATH=web/src`.
|
||||
- DMG scripts should write to `build/dmg/` and clean temporary staging folders.
|
||||
|
||||
## Ignore Rules
|
||||
Add these to root `.gitignore`:
|
||||
- `build/`
|
||||
- `*.dmg`
|
||||
- `rw.*.dmg`
|
||||
|
||||
## Migration Rules For Existing Projects
|
||||
1. Do not rename everything in one commit.
|
||||
2. First, add new folders and compatibility references.
|
||||
3. Move scripts and docs next.
|
||||
4. Move web source only after build scripts are updated.
|
||||
5. Rename Xcode project/target last, with a dedicated verification commit.
|
||||
5. Move mac source into `mac/src/App*` before optional target/project renames.
|
||||
6. Add compatibility lookup for old backend binary names and old settings paths during transition.
|
||||
|
||||
## Commit Strategy
|
||||
1. `chore(layout): add canonical folders`
|
||||
2. `build(backend): add embedded binary build`
|
||||
3. `feat(mac-shell): host local backend in webview`
|
||||
4. `feat(sync): add shared settings contract`
|
||||
5. `build(packaging): add self-contained app + dmg scripts`
|
||||
6. `chore(rename): finalize naming migration`
|
||||
5. `feat(help): add native help popup + web fallback`
|
||||
6. `build(packaging): add self-contained app + dmg scripts`
|
||||
7. `chore(rename): finalize naming migration`
|
||||
|
||||
@ -1,28 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: $0 <repo-root> <app-name-pascal>"
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <repo-root> [app-name]"
|
||||
echo "Example: $0 ~/Code/trader-app Trader"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_ROOT="$1"
|
||||
APP_NAME="$2"
|
||||
MAC_APP_NAME="${APP_NAME}Mac"
|
||||
BACKEND_NAME="${APP_NAME}Backend"
|
||||
APP_NAME="${2:-WebShellApp}"
|
||||
|
||||
mkdir -p "$REPO_ROOT/web"
|
||||
mkdir -p "$REPO_ROOT/mac/$MAC_APP_NAME/$MAC_APP_NAME/EmbeddedBackend"
|
||||
mkdir -p "$REPO_ROOT/web/src"
|
||||
mkdir -p "$REPO_ROOT/web/src/web_core"
|
||||
mkdir -p "$REPO_ROOT/web/src/tests"
|
||||
mkdir -p "$REPO_ROOT/mac/src/App/EmbeddedBackend"
|
||||
mkdir -p "$REPO_ROOT/mac/src/App/Help"
|
||||
mkdir -p "$REPO_ROOT/mac/src/AppTests"
|
||||
mkdir -p "$REPO_ROOT/mac/src/AppUITests"
|
||||
mkdir -p "$REPO_ROOT/scripts"
|
||||
mkdir -p "$REPO_ROOT/docs"
|
||||
|
||||
cat > "$REPO_ROOT/docs/architecture.md" <<DOC
|
||||
# ${APP_NAME} Architecture
|
||||
|
||||
- web backend source: ./web
|
||||
- mac shell source: ./mac/${MAC_APP_NAME}
|
||||
- embedded backend binary: ./mac/${MAC_APP_NAME}/${MAC_APP_NAME}/EmbeddedBackend/${BACKEND_NAME}
|
||||
- web backend source: ./web/src
|
||||
- mac shell source: ./mac/src
|
||||
- embedded backend binary: ./mac/src/App/EmbeddedBackend/WebBackend
|
||||
- native help page: ./mac/src/App/Help/help.html
|
||||
DOC
|
||||
|
||||
cat > "$REPO_ROOT/scripts/README.build.md" <<DOC
|
||||
@ -32,10 +36,64 @@ Add:
|
||||
- build_embedded_backend.sh
|
||||
- build_selfcontained_mac_app.sh
|
||||
- create_installer_dmg.sh
|
||||
|
||||
Suggested script behaviors:
|
||||
- Discover \`*.xcodeproj\` under \`mac/src\` unless \`MAC_PROJECT_PATH\` is provided.
|
||||
- Discover scheme via \`xcodebuild -list -json\` unless \`MAC_SCHEME\` is provided.
|
||||
- Default backend binary name: \`WebBackend\` (override with \`BACKEND_BIN_NAME\`).
|
||||
- Write DMG artifacts to \`build/dmg/\`.
|
||||
DOC
|
||||
|
||||
cat > "$REPO_ROOT/web/src/ONBOARDING.md" <<DOC
|
||||
# ${APP_NAME} Onboarding
|
||||
|
||||
Add your web help/onboarding content here.
|
||||
DOC
|
||||
|
||||
cat > "$REPO_ROOT/mac/src/App/Help/help.html" <<DOC
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Help</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${APP_NAME} Help</h1>
|
||||
<p>Replace this with your quick start and onboarding content.</p>
|
||||
</body>
|
||||
</html>
|
||||
DOC
|
||||
|
||||
cat > "$REPO_ROOT/web/run.sh" <<'DOC'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VENV_DIR="$ROOT_DIR/.venv"
|
||||
WEB_SRC_DIR="$ROOT_DIR/web/src"
|
||||
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
python3 -m venv "$VENV_DIR"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install -r "$WEB_SRC_DIR/requirements.txt"
|
||||
exec streamlit run "$WEB_SRC_DIR/app.py"
|
||||
DOC
|
||||
chmod +x "$REPO_ROOT/web/run.sh"
|
||||
|
||||
touch "$REPO_ROOT/.gitignore"
|
||||
for line in "build/" "*.dmg" "rw.*.dmg"; do
|
||||
if ! grep -Fxq "$line" "$REPO_ROOT/.gitignore"; then
|
||||
printf "%s\n" "$line" >> "$REPO_ROOT/.gitignore"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Created layout for ${APP_NAME}:"
|
||||
echo "- $REPO_ROOT/web"
|
||||
echo "- $REPO_ROOT/mac/$MAC_APP_NAME"
|
||||
echo "- $REPO_ROOT/web/src"
|
||||
echo "- $REPO_ROOT/mac/src"
|
||||
echo "- $REPO_ROOT/scripts"
|
||||
echo "- $REPO_ROOT/docs"
|
||||
echo "- $REPO_ROOT/.gitignore (updated with build/DMG ignore rules)"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user