feat(scenes): add scene darkness reveal and fix project reopen crash

Add darkenScene with Opening brush in presentation, persist reveal strokes per scene during a session, and close projects properly on return to home. Harden dnd asset streaming to avoid Windows main-process crashes when reopening projects.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-02 19:41:26 +08:00
parent d54e9ed02d
commit 4631a1bece
19 changed files with 526 additions and 11 deletions
+23
View File
@@ -8,6 +8,8 @@ import type {
Project,
ProjectId,
Scene,
SceneDarknessEvent,
SceneDarknessState,
SceneId,
VideoPlaybackEvent,
VideoPlaybackState,
@@ -27,6 +29,7 @@ export const ipcChannels = {
list: 'project.list',
create: 'project.create',
open: 'project.open',
close: 'project.close',
saveNow: 'project.saveNow',
get: 'project.get',
updateScene: 'project.updateScene',
@@ -68,6 +71,11 @@ export const ipcChannels = {
dispatch: 'effects.dispatch',
stateChanged: 'effects.stateChanged',
},
sceneDarkness: {
getState: 'sceneDarkness.getState',
dispatch: 'sceneDarkness.dispatch',
stateChanged: 'sceneDarkness.stateChanged',
},
video: {
getState: 'video.getState',
dispatch: 'video.dispatch',
@@ -117,6 +125,7 @@ export type UpdaterProgressEvent = {
export type IpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: { snapshot: LicenseSnapshot };
[ipcChannels.windows.multiWindowStateChanged]: { open: boolean };
@@ -154,6 +163,10 @@ export type IpcInvokeMap = {
req: { projectId: ProjectId };
res: { project: Project };
};
[ipcChannels.project.close]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.project.get]: {
req: Record<string, never>;
res: { project: Project | null };
@@ -270,6 +283,14 @@ export type IpcInvokeMap = {
req: { event: EffectsEvent };
res: { ok: true };
};
[ipcChannels.sceneDarkness.getState]: {
req: Record<string, never>;
res: { state: SceneDarknessState };
};
[ipcChannels.sceneDarkness.dispatch]: {
req: { event: SceneDarknessEvent };
res: { ok: true };
};
[ipcChannels.video.getState]: {
req: Record<string, never>;
res: { state: VideoPlaybackState };
@@ -304,6 +325,7 @@ export type SessionState = {
export type LegacyIpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: Record<string, never>;
};
@@ -316,6 +338,7 @@ export type ScenePatch = {
previewThumbAssetId?: AssetId | null;
previewVideoAutostart?: boolean;
previewRotationDeg?: 0 | 90 | 180 | 270;
darkenScene?: boolean;
settings?: Partial<Scene['settings']>;
media?: Partial<Scene['media']>;
layout?: Partial<Scene['layout']>;