Skip to content

Harden native window frame restore; add Vite+React, SvelteKit, Astro recipes#15

Merged
Christian-Katzmann merged 3 commits into
Christian-Katzmann:mainfrom
SohamKela:harden-window-add-framework-recipes
Jun 2, 2026
Merged

Harden native window frame restore; add Vite+React, SvelteKit, Astro recipes#15
Christian-Katzmann merged 3 commits into
Christian-Katzmann:mainfrom
SohamKela:harden-window-add-framework-recipes

Conversation

@SohamKela
Copy link
Copy Markdown
Contributor

@SohamKela SohamKela commented Jun 1, 2026

Summary

  • Harden the native Swift/WebKit window and add three framework dev recipes.

Native Window Fix

  • Edge case: AppKit can restore a window frame saved on a now-disconnected
    display, or a sub-minimum frame saved before the shell had a min size —
    opening off-screen or postage-stamp sized.
  • Fix: restoreUsableWindowFrame clamps the restored frame into a visible
    screen and enforces a 720×480 minimum before first paint. Applied to both
    app-it and app-it-static wrappers (kept byte-identical per the drift guard).

Recipes Added

  • Vite + React: detect vite+react+react-dom+@vitejs/plugin-react(-swc); port 5173; npm run dev -- --host 127.0.0.1 --port "$PORT" --strictPort.
  • SvelteKit: detect @sveltejs/kit+@sveltejs/vite-plugin-svelte+svelte+vite; port 5173; same Vite-backed flags.
  • Astro: detect astro; port 4321; npm run dev -- --host 127.0.0.1 --port "$PORT".
  • Launchers: keep $PORT/$API_PORT literal (single-quoted heredoc) until the daemon spawn selects the runtime port.

Tests Run

  • ./scripts/validate.sh — passed (incl. swiftc -typecheck, bash -n on templates, drift guard).

Fresh App Verification

  • Detection via inspect.sh on fresh npm create vite/astro + sv create apps — one correct candidate each, no false matches.
  • Each recipe spawned on a deliberately non-default port (5310 / 5320 / 4310) via the launcher's PORT=N bash -c "$START_COMMAND" path → bound the exact port, HTTP 200, killed tree → no orphaned listener.
  • strictPort probe: occupying the target port made Vite exit with "Port already in use" instead of silently bumping.

Files Changed

  • plugins/app-it/skills/app-it/templates/wrapper.swift + app-it-static copy: frame clamp + min size.
  • plugins/app-it/skills/app-it/templates/{run-template,run-template-chrome,run-template-multiserver}.sh: literal-$PORT heredoc.
  • plugins/app-it/skills/app-it/templates/inspect.sh: framework recipe detection.
  • plugins/app-it/skills/app-it/SKILL.md, README.md, docs/COMPATIBILITY.md, CHANGELOG.md: docs.

Remaining Notes

  • The Swift frame-restore passes typecheck and the logic is sound, but the on-screen behavior was not driven in a live GUI session — worth one manual launch to confirm.

Summary by Sourcery

Harden the macOS Swift WebKit window frame restoration logic and add framework-specific dev server recipes and launcher handling for modern JS frameworks.

New Features:

  • Add automatic detection and recipe suggestions for Vite + React, SvelteKit, and Astro projects in the inspection script and documentation.
  • Document framework dev recipes, preferred ports, and recommended START_COMMAND flags for common setups including Vite + React, SvelteKit, and Astro.

Bug Fixes:

  • Ensure restored native windows are always visible and usable by clamping saved frames to the active display and enforcing a minimum window size.
  • Preserve literal $PORT/$API_PORT placeholders in generated run scripts until runtime so dev servers receive the correct dynamically chosen ports.

Enhancements:

  • Align README and compatibility docs to highlight first-class support for Vite + React, SvelteKit, and Astro as common targets.

…recipes

Native window: clamp restored WKWebView frames into a visible screen and
enforce a minimum first-launch size, so a frame saved on a now-disconnected
display or a pre-min-size tiny frame no longer opens off-screen. Applied to
both the app-it and app-it-static wrappers (kept byte-identical per the drift
guard).

Recipes: document and disk-detect Vite + React (5173), SvelteKit (5173), and
Astro (4321) with loopback-only start commands in SKILL.md and inspect.sh;
update README and COMPATIBILITY targets.

Launchers: keep $PORT/$API_PORT literal in the generated run scripts (via a
single-quoted heredoc) until the daemon spawn selects the runtime port, so the
new Vite-backed recipes' --port "$PORT" flags expand at exec time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 1, 2026

Reviewer's Guide

Hardened the macOS Swift/WebKit window frame restoration logic for both app-it shells and added first-class dev-server recipes for Vite+React, SvelteKit, and Astro, including literal-$PORT handling in launcher scripts and updated docs.

Flow diagram for hardened Swift window frame restoration

flowchart TD
    A[AppDelegate.applicationDidFinishLaunching] --> B[Create NSWindow with DEFAULT_WIDTH x DEFAULT_HEIGHT]
    B --> C[Set window.minSize using MIN_WIDTH x MIN_HEIGHT]
    C --> D[Call restoreUsableWindowFrame]

    D --> E{window.setFrameUsingName succeeds?}
    E -- yes --> F[Use restored frame]
    E -- no --> G[window.center]

    F --> H[bestScreen for window.frame]
    G --> H

    H --> I{Screen found?}
    I -- no --> J[Return without changes]
    I -- yes --> K[Clamp frame size to MIN and screen.visibleFrame]

    K --> L[Adjust frame.position within screen.visibleFrame bounds]
    L --> M[window.setFrame display:false]
    M --> N[WKWebView first paint with usable window]
Loading

Flow diagram for Vite React, SvelteKit, and Astro recipe detection

flowchart TD
    A[inspect.sh reads package.json] --> B[Merge dependencies, devDependencies, optionalDependencies into deps]
    B --> C{deps has vite, react, react-dom, and @vitejs/plugin-react*?}
    B --> E{deps has @sveltejs/kit, @sveltejs/vite-plugin-svelte, svelte, vite?}
    B --> G{deps has astro?}

    C -- yes --> D[Add Vite + React recipe
start_command '<pm> run dev -- --host 127.0.0.1 --port $PORT --strictPort']
    C -- no --> E

    E -- yes --> F[Add SvelteKit recipe
start_command '<pm> run dev -- --host 127.0.0.1 --port $PORT --strictPort']
    E -- no --> G

    G -- yes --> H[Add Astro recipe
start_command '<pm> run dev -- --host 127.0.0.1 --port $PORT']
    G -- no --> I[No framework recipes]

    D --> J{recipes list non-empty?}
    F --> J
    H --> J
    I --> J

    J -- yes --> K[Print framework recipe candidates]
    J -- no --> L[Skip recipe section in output]
Loading

File-Level Changes

Change Details Files
Harden Swift/WebKit window frame restore behavior and enforce a minimum usable window size.
  • Introduce MIN_WIDTH/MIN_HEIGHT constants and set NSWindow.minSize to 720×480.
  • Replace direct window.center() with restoreUsableWindowFrame(named:) using an autosave name derived from the app name.
  • Implement restoreUsableWindowFrame to call setFrameUsingName, clamp restored frames to the visible screen, and adjust origin to keep the window on-screen before first paint.
  • Add bestScreen(for:) helper to pick the best NSScreen based on intersection with the window frame, falling back to main or first screen.
plugins/app-it/skills/app-it/templates/wrapper.swift
plugins/app-it-static/skills/app-it-static/templates/wrapper.swift
Add framework-specific dev recipes for Vite+React, SvelteKit, and Astro in project inspection and documentation.
  • Extend inspect.sh to scan for astro.config.* and svelte.config.* files in the project type signals list.
  • Parse package.json dependency sections into a deps map and detect Vite+React, SvelteKit, and Astro by package combinations.
  • Emit human-readable recipe suggestions including preferred ports and START_COMMAND templates using $PORT for each detected framework.
  • Update Vite guidance in inspect.sh to recommend passing --host 127.0.0.1, --port $PORT, and --strictPort via START_COMMAND.
  • Document new framework dev recipes, detection signals, ports, and START_COMMAND patterns in SKILL.md, and update compatibility and README marketing copy to list Vite+React, SvelteKit, and Astro as common targets.
  • Update SKILL.md framework behavior table to consolidate Vite guidance and add explicit SvelteKit and Astro rows with CLI flag recommendations.
plugins/app-it/skills/app-it/templates/inspect.sh
plugins/app-it/skills/app-it/SKILL.md
README.md
docs/COMPATIBILITY.md
Preserve literal $PORT/$API_PORT and shell syntax in generated run scripts until runtime.
  • Replace simple double-quoted START_COMMAND assignments with single-quoted heredoc constructions that defer variable expansion.
  • Apply the same heredoc pattern to BACKEND_START_COMMAND in the multiserver launcher template.
  • Add comments explaining that this preserves $PORT/$API_PORT until the daemon selects runtime ports, which is required for the new Vite/SvelteKit recipes.
plugins/app-it/skills/app-it/templates/run-template.sh
plugins/app-it/skills/app-it/templates/run-template-chrome.sh
plugins/app-it/skills/app-it/templates/run-template-multiserver.sh
Record the new behavior and recipes in the changelog.
  • Add changelog entries summarizing the added dev-server recipes, literal-port preservation in run scripts, and hardened window frame restoration.
CHANGELOG.md

Possibly linked issues

  • #N/A: PR fixes unusable restored Swift/WebKit window frames, matching the issue’s request to harden window edge cases.
  • #0: PR implements the requested Vite + React recipe, including port handling, detection, and fresh Vite app tests.
  • #(unknown): PR implements the requested Astro recipe, including detection, dev command, port 4321, and documentation updates.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="plugins/app-it-static/skills/app-it-static/templates/wrapper.swift" line_range="146-155" />
<code_context>
+    private func restoreUsableWindowFrame(named autosaveName: String) {
</code_context>
<issue_to_address>
**suggestion:** Consider using the screen with the largest intersection rather than the first intersecting screen when choosing `bestScreen`.

In multi-monitor setups where a window straddles two displays, returning the first intersecting screen means the chosen `bestScreen` can be arbitrary and not where most of the window actually is. Prefer selecting the screen with the largest intersection area with the frame, and only fall back to `NSScreen.main` (or the first screen) when there’s no intersection at all.

Suggested implementation:

```
func bestScreen(for frame: NSRect) -> NSScreen? {
    let screens = NSScreen.screens

    // Prefer the screen whose visibleFrame has the largest intersection
    // with the given frame. Fall back to NSScreen.main (or the first screen)
    // if there is no intersection at all.
    var bestScreen: NSScreen?
    var largestIntersectionArea: CGFloat = 0

    for screen in screens {
        let intersection = screen.visibleFrame.intersection(frame)
        guard !intersection.isNull else { continue }

        let area = intersection.width * intersection.height
        if area > largestIntersectionArea {
            largestIntersectionArea = area
            bestScreen = screen
        }
    }

    if let bestScreen {
        return bestScreen
    }

    return NSScreen.main ?? screens.first
}

```

I assumed an existing implementation of `bestScreen(for:)` that returned the first intersecting screen. If the current implementation differs, adapt the `SEARCH` block to match your actual `bestScreen(for:)` body so the replacement compiles cleanly. The call site (`guard let screen = bestScreen(for: window.frame) else { return }`) does not need to change.
</issue_to_address>

### Comment 2
<location path="plugins/app-it/skills/app-it/templates/run-template-chrome.sh" line_range="30" />
<code_context>
-START_COMMAND="__START_COMMAND__"
 POLYFILL_PATH="__POLYFILL_PATH__"

+# Keep `$PORT` and other shell syntax literal until the daemon spawn below.
+# A plain double-quoted assignment here would expand `$PORT` before the
+# launcher has selected its runtime port, breaking Vite/SvelteKit recipes.
</code_context>
<issue_to_address>
**nitpick (typo):** Minor grammar tweak in the new comment for readability.

Please change `until the daemon spawn below` to `until the daemon spawns below` or `until the daemon spawn below occurs` for grammatical correctness, and apply the same wording to the corresponding comment in `run-template-multiserver.sh`.

Suggested implementation:

```
# Keep `$PORT` and other shell syntax literal until the daemon spawns below.
# A plain double-quoted assignment here would expand `$PORT` before the
# launcher has selected its runtime port, breaking Vite/SvelteKit recipes.

```

In `plugins/app-it/skills/app-it/templates/run-template-multiserver.sh`, locate the corresponding comment (it should currently read something like `# Keep \`$PORT\` and other shell syntax literal until the daemon spawn below.`) and update it to the same wording:

`# Keep \`$PORT\` and other shell syntax literal until the daemon spawns below.`

No other logic changes are needed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread plugins/app-it-static/skills/app-it-static/templates/wrapper.swift
Comment thread plugins/app-it/skills/app-it/templates/run-template-chrome.sh Outdated
SohamKela and others added 2 commits June 1, 2026 20:02
bestScreen now selects the display holding the largest slice of the window
frame instead of the first intersecting screen, so a frame straddling two
monitors clamps to where most of the window actually is. Falls back to
main/first only when nothing intersects. Synced byte-identical to the
app-it-static wrapper.

Also fix "daemon spawn below" -> "spawns below" in the run-template and
chrome launcher comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Christian-Katzmann Christian-Katzmann merged commit 2dea82b into Christian-Katzmann:main Jun 2, 2026
3 checks passed
@Christian-Katzmann
Copy link
Copy Markdown
Owner

Merged 🎉 — thank you, @SohamKela! Your CI ran green across macOS and Windows, the drift guard kept both wrapper.swift copies in sync, and the $PORT heredoc fix is exactly right for the new Vite/SvelteKit recipes. Really appreciate the careful write-up and the honest note about the live-GUI check — much appreciated first contribution to app-it.

Christian-Katzmann added a commit that referenced this pull request Jun 2, 2026
…ork-recipes PR (#15)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Christian-Katzmann added a commit that referenced this pull request Jun 2, 2026
…#15)

The fixture start commands escaped the port as `\$PORT`, which only worked
under the launcher's old plain-quoted `START_COMMAND="..."` assignment (the
backslash kept `$PORT` literal at assignment, and `bash -c` expanded it at
spawn). PR #15 changed the launchers to store START_COMMAND via a *quoted*
heredoc so a real `$PORT` survives literally until the daemon spawns. A quoted
heredoc preserves the backslash, so `\$PORT` reached `bash -c` as the literal
text `$PORT` instead of the chosen port — the stub server got an unparseable
port and refused to start, failing every runtime fixture (smoke run, ownership,
warm-reattach, teardown) once the two changes were integrated.

Real app-it configs never escape it (SKILL.md: `--port $PORT`), so the fixtures
were the outlier. Unescape them to mirror real configs; the suite is green
again (92 passed) against the integrated launchers. Caught by re-running the
fixture suite after the merge — exactly its job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SohamKela SohamKela deleted the harden-window-add-framework-recipes branch June 3, 2026 01:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants