Harden native window frame restore; add Vite+React, SvelteKit, Astro recipes#15
Merged
Christian-Katzmann merged 3 commits intoJun 2, 2026
Conversation
…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>
Reviewer's GuideHardened 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 restorationflowchart 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]
Flow diagram for Vite React, SvelteKit, and Astro recipe detectionflowchart 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]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
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>
Owner
|
Merged 🎉 — thank you, @SohamKela! Your CI ran green across macOS and Windows, the drift guard kept both |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Native Window Fix
display, or a sub-minimum frame saved before the shell had a min size —
opening off-screen or postage-stamp sized.
restoreUsableWindowFrameclamps the restored frame into a visiblescreen and enforces a 720×480 minimum before first paint. Applied to both
app-itandapp-it-staticwrappers (kept byte-identical per the drift guard).Recipes Added
vite+react+react-dom+@vitejs/plugin-react(-swc); port 5173;npm run dev -- --host 127.0.0.1 --port "$PORT" --strictPort.@sveltejs/kit+@sveltejs/vite-plugin-svelte+svelte+vite; port 5173; same Vite-backed flags.astro; port 4321;npm run dev -- --host 127.0.0.1 --port "$PORT".$PORT/$API_PORTliteral (single-quoted heredoc) until the daemon spawn selects the runtime port.Tests Run
./scripts/validate.sh— passed (incl.swiftc -typecheck,bash -non templates, drift guard).Fresh App Verification
inspect.shon freshnpm create vite/astro+sv createapps — one correct candidate each, no false matches.PORT=N bash -c "$START_COMMAND"path → bound the exact port, HTTP 200, killed tree → no orphaned listener.Files Changed
plugins/app-it/skills/app-it/templates/wrapper.swift+app-it-staticcopy: frame clamp + min size.plugins/app-it/skills/app-it/templates/{run-template,run-template-chrome,run-template-multiserver}.sh: literal-$PORTheredoc.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
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:
Bug Fixes:
Enhancements: