screengraft 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -232,6 +232,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: there are tests, they
232
232
  run in CI, and a change to the compositing engine needs a measurement, not an
233
233
  opinion.
234
234
 
235
+
236
+ **Testing a change without reinstalling.** The desktop app snapshots an installed plugin per session and only refreshes it after *Check for updates*, so release → check → new session is a slow loop. Instead, run `scripts/dev-root.sh` once in your checkout: it writes `~/.screengraft/dev-root`, and from then on every session's `launch.sh` — including the installed copy's — runs the UI from your tree. The badge reads `dev <sha>+` while it does. `scripts/dev-root.sh off` restores the installed copy. Only `SKILL.md` and `mcp/server.py` changes still need a real update, because the app loads those itself.
237
+
235
238
  ## Licence
236
239
 
237
240
  MIT. The bundled Mona Sans subset is SIL OFL — see `ui/fonts/OFL.txt`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screengraft",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
5
5
  "keywords": [
6
6
  "mockup",
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env zsh
2
+ # Point the installed plugin at a checkout, or stop doing so.
3
+ #
4
+ # scripts/dev-root.sh -> use THIS checkout (the one the script is in)
5
+ # scripts/dev-root.sh /some/tree -> use that one
6
+ # scripts/dev-root.sh off -> back to the installed copy
7
+ # scripts/dev-root.sh status -> say which
8
+ #
9
+ # Writes ~/.screengraft/dev-root, which launch.sh reads. See launch.sh for why.
10
+ set -euo pipefail
11
+ FILE="$HOME/.screengraft/dev-root"
12
+ case "${1:-}" in
13
+ off) rm -f "$FILE"; echo "dev root off — launch.sh uses the installed copy" ;;
14
+ status) if [ -s "$FILE" ]; then echo "dev root: $(head -n1 "$FILE")"; else echo "dev root off"; fi ;;
15
+ *)
16
+ TREE="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
17
+ TREE="$(cd "$TREE" && pwd)"
18
+ [ -f "$TREE/scripts/ui.py" ] || { echo "error: $TREE has no scripts/ui.py" >&2; exit 2; }
19
+ mkdir -p "$(dirname "$FILE")"
20
+ printf '%s\n' "$TREE" > "$FILE"
21
+ echo "dev root: $TREE — every session's launch.sh now runs this tree; the badge will read 'dev <sha>'"
22
+ ;;
23
+ esac
package/scripts/launch.sh CHANGED
@@ -18,6 +18,26 @@
18
18
  # the same reason a Terminal window did, and there is no window to clean up.
19
19
  set -euo pipefail
20
20
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
21
+ # Dev root: run the UI from a checkout instead of the installed copy.
22
+ # `~/.screengraft/dev-root` holds one line, the path to a screengraft tree.
23
+ # The desktop app snapshots every installed plugin per session and only
24
+ # refreshes that snapshot after "Check for updates", so testing a change used
25
+ # to mean release -> check -> new session. With the file in place any session,
26
+ # including one already open, runs whatever is in the tree right now; the
27
+ # badge reads "dev <sha>+" so it can never pass for a release. Opt-in by the
28
+ # file's existence; a path that is not a tree is ignored with a warning, so
29
+ # a stale file cannot silently break a launch. `scripts/dev-root.sh` writes it.
30
+ DEV_ROOT_FILE="$HOME/.screengraft/dev-root"
31
+ if [ -s "$DEV_ROOT_FILE" ]; then
32
+ DEV_ROOT="$(head -n1 "$DEV_ROOT_FILE")"
33
+ DEV_ROOT="${DEV_ROOT/#\~/$HOME}"
34
+ if [ -f "$DEV_ROOT/scripts/ui.py" ]; then
35
+ ROOT="$DEV_ROOT"
36
+ echo "dev root: running from $ROOT (remove $DEV_ROOT_FILE to use the installed copy)" >&2
37
+ else
38
+ echo "warning: $DEV_ROOT_FILE points at $DEV_ROOT, which has no scripts/ui.py — using the installed copy" >&2
39
+ fi
40
+ fi
21
41
  PORT=0
22
42
  OUT_DIR=""
23
43
 
@@ -5,7 +5,7 @@ description: Injects a UI screenshot OR a screen recording onto a photographed d
5
5
 
6
6
  # Inject a screenshot onto a photographed device
7
7
 
8
- **What ships (v0.48):** a local browser UI (`scripts/ui.py`) that walks the designer through the whole job — pick the photo and the screen source, which may be an image **or a video** (recent Desktop/Downloads images, drag-drop, browse, path, or a **Figma frame link**), auto-detect the screen as a starting position — and when detection cannot tell which region is a screen, **Point at screen**: one click inside it and the detector uses that point — or, if this photograph has been fitted before, **the fit it was saved with comes back** as the starting position instead of a detection, recognised by the photo's own pixels so a rename or a drag-drop still match — and every save also writes a **portable `.fit.json` beside the mockup** that can be dropped back onto the page later, which is how a fit survives a re-export, another machine, or someone else's hands — then **match the four edges** (drag an edge's middle to slide it, near an end to pivot; corners still draggable) with canvas navigation that follows the usual conventions — **hold ⌘ and scroll to zoom to the pointer, hold space and drag to pan** — and a rectified strip loupe. The fit and the composite sit **side by side and always have** — the result pane re-renders as you drag, which is how a corner gets judged, so it is the layout rather than a mode you can switch off. Then an on-by-default realism pass that colour-matches the source to the photo's light, **Save** (or **Render**, for a video) into the project folder (`--out-dir`), and a **Send to Claude** button that reaches you through the plugin's own MCP server. The UI is a hand port of the project's Figma design file — dark only.
8
+ **What ships (v0.49):** a local browser UI (`scripts/ui.py`) that walks the designer through the whole job — pick the photo and the screen source, which may be an image **or a video** (recent Desktop/Downloads images, drag-drop, browse, path, or a **Figma frame link**), auto-detect the screen as a starting position — and when detection cannot tell which region is a screen, **Point at screen**: one click inside it and the detector uses that point — or, if this photograph has been fitted before, **the fit it was saved with comes back** as the starting position instead of a detection, recognised by the photo's own pixels so a rename or a drag-drop still match — and every save also writes a **portable `.fit.json` beside the mockup** that can be dropped back onto the page later, which is how a fit survives a re-export, another machine, or someone else's hands — then **match the four edges** (drag an edge's middle to slide it, near an end to pivot; corners still draggable) with canvas navigation that follows the usual conventions — **hold ⌘ and scroll to zoom to the pointer, hold space and drag to pan** — and a rectified strip loupe. The fit and the composite sit **side by side and always have** — the result pane re-renders as you drag, which is how a corner gets judged, so it is the layout rather than a mode you can switch off. Then an on-by-default realism pass that colour-matches the source to the photo's light, **Save** (or **Render**, for a video) into the project folder (`--out-dir`), and a **Send to Claude** button that reaches you through the plugin's own MCP server. The UI is a hand port of the project's Figma design file — dark only.
9
9
 
10
10
  The geometry is exact (`warp.py`); the detection is advisory (`detect.py`) and the human corrects it. **When a detection is wrong and you want to know why**, ask for the candidate list: `POST /api/detect {"trace": true}` writes `<session>/candidates.json`, or run `python3 scripts/detect.py --photo P --out-corners /tmp/c.json --trace /tmp/t.json` (add `--click X,Y`). Every candidate quad is in there with its score and whether it was accepted, rejected, never reached, or filtered out by the click — which is what separates "the screen was never proposed" from "it was proposed and something else won".
11
11
 
@@ -54,6 +54,8 @@ scripts/launch.sh --out-dir "<the user's current project folder>/mockups"
54
54
 
55
55
  **Do not launch `ui.py` directly with `nohup ... &` from your own shell.** That was tried on 3 Sep 2026 and the server died silently between turns, twice, losing whatever the user had already entered — the shell session that launches it can be torn down out from under a merely-backgrounded child. `launch.sh` starts it with `--daemon`, which double-forks and calls `setsid`, putting the server in its own session with no controlling terminal; it survives the launching shell and opens no window. It self-checks (waits for startup output, then confirms the server responds) before returning.
56
56
 
57
+ **If `launch.sh` prints `dev root: running from <path>`**, the UI is running from a source checkout, not from this installed copy — the developer has opted in with `~/.screengraft/dev-root` (see `scripts/dev-root.sh`). Say so in one line, and treat the badge's `dev <sha>+` as expected. Anything the user reports is then about that tree, not about the release.
58
+
57
59
  A Terminal.app window was the previous fix for the same problem. It worked but left a dead "[Process completed]" window behind after every session, and closing those from AppleScript proved unreliable — Terminal reports stale ttys for dead windows and leaves zero-tab husks that `close` reports success on. The daemon removes the window entirely, so there is nothing to clean up.
58
60
 
59
61
  It prints one JSON line — `url`, `session`, `job`, `result`, `out_dir` — and opens the browser tab itself. If it exits non-zero, read the error and the log path it names before telling the user anything is ready.
@@ -140,6 +142,7 @@ The server is a detached daemon with no window. **Stop it with `scripts/stop.sh`
140
142
  | `scripts/preflight.py` | Dependency report; `--install` builds the venv |
141
143
  | `scripts/launch.sh` | Starts `ui.py` as a detached daemon (no window) and verifies it's alive — use this, not `ui.py` directly |
142
144
  | `scripts/stop.sh` | Stops the UI with SIGTERM so the session pointer is cleared — use this instead of `pkill` |
145
+ | `scripts/dev-root.sh` | Developer opt-in: point every session's `launch.sh` at a checkout (`on` by default, `off`, `status`). Testing a change then needs no release and no plugin update. |
143
146
  | `scripts/ui.py` | Local server + browser UI; calls the two below |
144
147
  | `scripts/detect.py` | Advisory screen-quad + corner-radius detection (no ML) |
145
148
  | `scripts/warp.py` | The engine: `compose()` and a CLI for scripted use |