diff --git a/skills/macos-selfcontained-webapp/SKILL.md b/skills/macos-selfcontained-webapp/SKILL.md index 26e6806..f27fc68 100644 --- a/skills/macos-selfcontained-webapp/SKILL.md +++ b/skills/macos-selfcontained-webapp/SKILL.md @@ -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/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 `~/./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` diff --git a/skills/macos-selfcontained-webapp/agents/openai.yaml b/skills/macos-selfcontained-webapp/agents/openai.yaml index 3921dcd..a90c050 100644 --- a/skills/macos-selfcontained-webapp/agents/openai.yaml +++ b/skills/macos-selfcontained-webapp/agents/openai.yaml @@ -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." diff --git a/skills/macos-selfcontained-webapp/references/implementation-blueprint.md b/skills/macos-selfcontained-webapp/references/implementation-blueprint.md index fb77de3..508d921 100644 --- a/skills/macos-selfcontained-webapp/references/implementation-blueprint.md +++ b/skills/macos-selfcontained-webapp/references/implementation-blueprint.md @@ -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/:" \ - "$ROOT_DIR/web/backend_embedded_launcher.py" + --add-data "$WEB_SRC_DIR/app.py:." \ + --add-data "$WEB_SRC_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: `~/./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//Mac.app" ./scripts/create_installer_dmg.sh ``` +Expected DMG output: + +```text +build/dmg/-.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. diff --git a/skills/macos-selfcontained-webapp/references/layout-and-naming.md b/skills/macos-selfcontained-webapp/references/layout-and-naming.md index 6c49801..3969810 100644 --- a/skills/macos-selfcontained-webapp/references/layout-and-naming.md +++ b/skills/macos-selfcontained-webapp/references/layout-and-naming.md @@ -5,44 +5,68 @@ ```text / web/ - app.py - - requirements.txt + run.sh + src/ + app.py + + requirements.txt + ONBOARDING.md mac/ - Mac/ - Mac.xcodeproj/ - Mac/ + src/ + .xcodeproj/ + App/ ContentView.swift - MacApp.swift + .swift EmbeddedBackend/ - Backend + WebBackend + Help/ + help.html + AppTests/ + AppUITests/ scripts/ build_embedded_backend.sh build_selfcontained_mac_app.sh create_installer_dmg.sh + build/ + dmg/ + -.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` diff --git a/skills/macos-selfcontained-webapp/scripts/scaffold_web_mac_layout.sh b/skills/macos-selfcontained-webapp/scripts/scaffold_web_mac_layout.sh index 0557bd9..3cfa003 100755 --- a/skills/macos-selfcontained-webapp/scripts/scaffold_web_mac_layout.sh +++ b/skills/macos-selfcontained-webapp/scripts/scaffold_web_mac_layout.sh @@ -1,28 +1,32 @@ #!/usr/bin/env bash set -euo pipefail -if [[ $# -lt 2 ]]; then - echo "Usage: $0 " +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [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" < "$REPO_ROOT/scripts/README.build.md" < "$REPO_ROOT/web/src/ONBOARDING.md" < "$REPO_ROOT/mac/src/App/Help/help.html" < + + + + + Help + + +

${APP_NAME} Help

+

Replace this with your quick start and onboarding content.

+ + +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)"