diff --git a/.lane/memory/apps/desktop/native/live.m/01M5YZZPH2ADYVC2F2ACMCP7B2-a-closed-window-s-scstream-s.md b/.lane/memory/apps/desktop/native/live.m/01M5YZZPH2ADYVC2F2ACMCP7B2-a-closed-window-s-scstream-s.md index 53b1f8c4..0d0f9d2c 100644 --- a/.lane/memory/apps/desktop/native/live.m/01M5YZZPH2ADYVC2F2ACMCP7B2-a-closed-window-s-scstream-s.md +++ b/.lane/memory/apps/desktop/native/live.m/01M5YZZPH2ADYVC2F2ACMCP7B2-a-closed-window-s-scstream-s.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-27T15:34:41Z norm: '1' sig: f5f542a9730901ae -body_hash: 0d3d7801d2eeec11 -raw_hash: b7c0ca684f0d3c15 +body_hash: 9d55ad18b75658fb +raw_hash: 31da00fc4410d54a +vouched: 2026-10-27T15:52:56Z lines: 1-249 --- diff --git a/.lane/memory/apps/desktop/native/live.m/01M5Z10R62MZBHB2W46VYCP5ND-a-relaunch-must-be-followed.md b/.lane/memory/apps/desktop/native/live.m/01M5Z10R62MZBHB2W46VYCP5ND-a-relaunch-must-be-followed.md new file mode 100644 index 00000000..948ab7f0 --- /dev/null +++ b/.lane/memory/apps/desktop/native/live.m/01M5Z10R62MZBHB2W46VYCP5ND-a-relaunch-must-be-followed.md @@ -0,0 +1,12 @@ +--- +id: 01M5Z10R62MZBHB2W46VYCP5ND +anchor: '@file' +created: 2026-10-27T15:52:44Z +norm: '1' +sig: f5f542a9730901ae +body_hash: 9d55ad18b75658fb +raw_hash: 31da00fc4410d54a +lines: 1-266 +--- + +a relaunch must be followed without a click, since make run restarts the app on every change diff --git a/apps/app/src/panels/live.tsx b/apps/app/src/panels/live.tsx index c15c3d57..108b394b 100644 --- a/apps/app/src/panels/live.tsx +++ b/apps/app/src/panels/live.tsx @@ -21,13 +21,21 @@ export function Live( ) { const ref = useRef(null); const relink = useRef<{ app?: string }>(undefined); + // After Ace restarts, only the tab knows the app, so a lost window tries it once on its own. + const tried = useRef(false); const [lost, setLost] = useState(false); const report = useEffectEvent((status: LiveStatus | null) => { if (!status) return; + if (status.state === "lost" && app && !tried.current) { + tried.current = true; + relink.current = { app }; + return; + } setLost(status.state === "lost"); // A relink moves the tab to the app's new window, and the app is kept for the next one. if (status.state !== "live") return; + tried.current = false; if (status.window !== window || status.app !== app) { update({ window: status.window, app: status.app }); } diff --git a/apps/desktop/native/live.m b/apps/desktop/native/live.m index 45c98048..addf1cfb 100644 --- a/apps/desktop/native/live.m +++ b/apps/desktop/native/live.m @@ -17,6 +17,8 @@ @interface AceLiveView : NSView @property(nonatomic, strong) NSTimer *watch; @property(nonatomic) BOOL stopped; @property(nonatomic) BOOL lost; +/** Until when a closed window's app is followed to its next window, as after a rebuild. */ +@property(nonatomic, strong) NSDate *deadline; /** Bumped by each start or relink, so a slow lookup can't attach a superseded window. */ @property(nonatomic) NSUInteger generation; @end @@ -58,6 +60,8 @@ - (void)start:(CGWindowID)target { [self stop]; self.target = target; self.bundle = nil; + self.deadline = nil; + self.layer.contents = nil; [self attach:nil]; } @@ -72,7 +76,6 @@ - (void)relink:(NSString *)app { - (void)attach:(NSString *)app { self.stopped = NO; self.lost = NO; - self.layer.contents = nil; NSUInteger generation = ++self.generation; CGWindowID target = self.target; [SCShareableContent getShareableContentExcludingDesktopWindows:YES @@ -101,6 +104,7 @@ - (void)attach:(NSString *)app { - (void)capture:(SCWindow *)window { self.bundle = window.owningApplication.bundleIdentifier; self.current = window.windowID; + self.deadline = nil; SCContentFilter *filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:window]; SCStreamConfiguration *config = [SCStreamConfiguration new]; CGFloat scale = filter.pointPixelScale; @@ -137,9 +141,22 @@ - (void)lost:(SCStream *)stream { }); } -// Shows the page's relink prompt underneath; the app may have quit or relaunched. +// A rebuilt app relaunches with a new window ID, so its app is followed for a while, keeping the +// last frame, before the page's relink prompt shows underneath. - (void)lose { [self stop]; + if (!self.bundle) return [self give]; + if (!self.deadline) self.deadline = [NSDate dateWithTimeIntervalSinceNow:10]; + if (self.deadline.timeIntervalSinceNow <= 0) return [self give]; + self.stopped = NO; + NSUInteger generation = self.generation; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2), dispatch_get_main_queue(), ^{ + if (!self.stopped && self.generation == generation) [self attach:self.bundle]; + }); +} + +- (void)give { + self.deadline = nil; self.lost = YES; self.layer.contents = nil; }