fix(materials): синхронизация легенды и маркеров на превью и презентации

Легенда и цифры на материале масштабируются от экрана игроков и не обрезаются жёлтой рамкой. Переключение сцен без записи project.json на диск.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-02 10:57:50 +08:00
parent ea718a26df
commit 153be98780
17 changed files with 531 additions and 128 deletions
+1 -5
View File
@@ -745,11 +745,7 @@ async function main() {
const open = projectStore.getOpenProject();
if (!open) throw new Error('No open project');
const gn = graphNodeId ? open.sceneGraphNodes.find((n) => n.id === graphNodeId) : null;
await projectStore.updateProject((p) => ({
...p,
currentGraphNodeId: graphNodeId,
currentSceneId: gn ? gn.sceneId : null,
}));
projectStore.setSessionPointer(graphNodeId, gn ? gn.sceneId : null);
effectsStore.clear();
materialsOverlayStore.clear();
npcsOverlayStore.clear();
@@ -105,6 +105,23 @@ void test('zipStore: listProjects skips unreadable archives', () => {
);
});
void test('zipStore: setSessionPointer updates pointer in memory without disk write', () => {
const src = fs.readFileSync(path.join(here, 'zipStore.ts'), 'utf8');
const m = src.match(/setSessionPointer\([\s\S]*?\n \}/);
assert.ok(m);
assert.doesNotMatch(m[0], /writeCacheProject|queueSave|enqueueProjectUpdate/);
});
void test('main index: setCurrentGraphNode uses setSessionPointer not updateProject', () => {
const src = fs.readFileSync(path.join(here, '..', 'index.ts'), 'utf8');
const m = src.match(
/registerHandler\(ipcChannels\.project\.setCurrentGraphNode[\s\S]*?emitSessionState\(\)/,
);
assert.ok(m);
assert.match(m[0], /setSessionPointer/);
assert.doesNotMatch(m[0], /updateProject/);
});
void test('atomicReplace: replaceFileAtomic must not rm destination before successful commit', () => {
const src = fs.readFileSync(path.join(here, 'atomicReplace.ts'), 'utf8');
const i = src.indexOf('export async function replaceFileAtomic');
+12
View File
@@ -411,6 +411,18 @@ export class ZipProjectStore {
return this.openProject?.project ?? null;
}
/** Только указатель сессии в памяти — без записи project.json на диск. */
setSessionPointer(currentGraphNodeId: GraphNodeId | null, currentSceneId: SceneId | null): Project {
const open = this.openProject;
if (!open) throw new Error('No open project');
open.project = {
...open.project,
currentGraphNodeId,
currentSceneId,
};
return open.project;
}
/** Публичный URL для ассетов — кастомная схема `dnd:` (см. `registerDndAssetProtocol`). */
getAssetFileUrl(assetId: AssetId): string | null {
if (!this.getAssetReadInfo(assetId)) return null;