작업을 다시 열다 / original
DEVELOPMENT JOURNAL / 19
Photoshot
CODE × UI
무료 포토샵 개발기

작업을 다시 열다

무료 포토샵을 만드는 포토샷 개발기입니다. 이번에는 편집 결과를 계속 고칠 수 있도록 남기는 방법을 살핍니다. 18편의 AI 원본을 이어 사용해 레이어 이름과 불투명도를 바꾸고, 프로젝트 파일 재열기와 같은 브라우저의 자동 복구를 각각 확인했습니다.

PHOTOSHOT DEVELOPMENT JOURNAL / 19

AI 생성 시연용 사진. 편집 화면과 출력 이미지는 실제 포토샷에서 만들었습니다.

Photoshot무료 포토샵 개발기 / 19FOUNDATION

출처에서 시작하다.

PNG 출력과 프로젝트 저장은 다른 약속이다

PNG로 내보내면 보이는 결과를 이미지 한 장으로 전달할 수 있습니다. 하지만 다시 열어서 레이어별 이름·불투명도·마스크를 조정하려면 문서 구조가 필요합니다. 운영 편집기의 saveProject는 현재 문서를 JSON으로 직렬화한 .layerstudio 파일을 내려받습니다.

이 파일에 담기는 것은 현재 문서입니다. 열려 있는 모든 탭이나 실행 취소 기록 전체를 하나의 프로젝트 파일에 담는 방식이 아닙니다. PSD와의 상호 교환도 별도 경로이므로 .layerstudio에서 유지된 항목이 PSD에서도 모두 같다고 추정하지 않습니다.

자동 저장은 현재 작업 공간과 직전 사본을 보관한다

workspace-store.ts의 SavedWorkspace에는 activeId, 문서 탭들, savedAt이 있습니다. IndexedDB의 documents 저장소에 workspace를 기록하고, 기존 값이 있으면 workspace-recovery로 옮긴 뒤 새 값을 씁니다. 따라서 복구 사본은 직전 작업 공간 한 개이며 영구적인 버전 저장소가 아닙니다.

저장 작업은 Promise 큐로 순서대로 처리합니다. 불러올 때는 버전과 탭 배열, 최대 20개 제한, 활성 탭 존재를 검사하고 각 문서에 validateDoc를 적용합니다. 이름 있는 히스토리 스냅샷이 있다면 문서당 최대 5개와 이름·시간·문서 내용도 검증합니다.

app/studio/workspace-store.ts · SHA-256 8617c3f508c4 · L3–6포토샷 구현 코드 ↗
export type SmartParent={tabId:string;linkedId:string;sourceKey?:string;sourceHash?:string};
export type HistorySnapshot={id:string;name:string;createdAt:number;doc:StudioDoc};
export type SavedTab={id:string;doc:StudioDoc;smartParent?:SmartParent;artboardParent?:ArtboardParent;snapshots?:HistorySnapshot[]};
export type SavedWorkspace={version:1;activeId:string;tabs:SavedTab[];savedAt:number};
Photoshot무료 포토샵 개발기 / 19IMPLEMENTATION

계산의
안쪽으로.

자동 복구가 실행 취소 이력까지 돌려주지는 않는다

editor.tsx는 일반 작업 공간 읽기가 실패하면 복구 사본 읽기를 시도합니다. 복원된 세션을 만들 때 undo와 redo는 빈 배열로 초기화합니다. 현재 문서를 되찾는 것과 과거 편집 단계 전체를 재생하는 것은 다른 기능입니다.

화면에는 저장 중과 이 기기에 저장됨을 구분해 표시하며, 저장 실패 시 프로젝트 파일을 저장하라는 안내를 냅니다. 자동 저장은 같은 브라우저의 로컬 데이터입니다. 다른 기기로 옮기거나 브라우저 데이터를 지운 뒤에도 보관하려면 내려받은 프로젝트 파일이 필요합니다.

이름과 87% 불투명도가 다시 열렸다

2026년 10월 22일 18편의 같은 1536×1024 사진에서 레이어 이름을 Journal 19 recovery check로, 불투명도를 87%로 바꿨습니다. 저장 파일에서 이름과 opacity 0.87을 확인한 뒤 File → Open Project로 다시 열었습니다. 새 문서 탭에서 같은 이름과 87%가 표시되고 Undo는 비활성 상태였습니다.

이 기기에 저장됨을 확인한 후 별도의 편집기 창을 열어 자동 복구도 확인했습니다. 다섯 문서 탭과 활성 문서, 레이어 이름·불투명도가 복원됐고 Undo는 역시 비활성이었습니다. 검수용 창은 확인 후 닫았습니다. 아래 화면은 색 보정 전후가 아니라 저장된 문서 상태의 재확인입니다.

PHOTOSHOT / SOURCE NOTES19

코드를 읽다.

app/studio/workspace-store.ts · SHA-256 8617c3f508c4 · L9–10포토샷 구현 코드 ↗
export function saveWorkspace(value:SavedWorkspace){const snapshot=value;const job=queue.catch(()=>{}).then(async()=>{const db=await database();try{await new Promise<void>((resolve,reject)=>{const t=db.transaction('documents','readwrite'),s=t.objectStore('documents'),r=s.get('workspace');r.onsuccess=()=>{try{if(r.result)s.put(r.result,'workspace-recovery');s.put(snapshot,'workspace');const current=snapshot.tabs.find(t=>t.id===snapshot.activeId);if(current)s.put(current.doc,'current');}catch(error){try{t.abort();}catch{/* Already aborted. */}reject(error);}};t.oncomplete=()=>resolve();t.onabort=()=>reject(t.error??Error('Saving was cancelled.'));t.onerror=()=>reject(t.error);});}finally{db.close();}});queue=job;return job;}
export async function loadWorkspace(recovery=false):Promise<SavedWorkspace|null>{const db=await database();try{const v=await new Promise<SavedWorkspace|undefined>((resolve,reject)=>{const r=db.transaction('documents').objectStore('documents').get(recovery?'workspace-recovery':'workspace');r.onsuccess=()=>resolve(r.result);r.onerror=()=>reject(r.error);});if(!v)return null;if(v.version!==1||!Array.isArray(v.tabs)||v.tabs.length>20||!v.tabs.some(t=>t.id===v.activeId))throw Error('Invalid recovery workspace.');for(const t of v.tabs){validateDoc(t.doc);if(t.snapshots){if(!Array.isArray(t.snapshots)||t.snapshots.length>5)throw Error('Invalid history snapshots.');for(const s of t.snapshots){if(typeof s.id!=='string'||typeof s.name!=='string'||s.name.length>80||!Number.isFinite(s.createdAt))throw Error('Invalid history snapshot.');validateDoc(s.doc);}}}return v;}finally{db.close();}}
app/studio/editor.tsx · SHA-256 8cebf076ec90 · L248–248포토샷 구현 코드 ↗
  useEffect(()=>{let live=true;(async()=>{let saved:SavedWorkspace|null=null;try{saved=await loadWorkspace();}catch{saved=await loadWorkspace(true);}if(!live)return;if(saved){sessions.current=new Map(saved.tabs.map(t=>[t.id,{doc:t.doc,undo:[],redo:[],selected:t.doc.layers.at(-1)?.id??null,zoom:.65,smartParent:t.smartParent,artboardParent:t.artboardParent,snapshots:t.snapshots}]));const current=sessions.current.get(saved.activeId)!;tabIdRef.current=saved.activeId;setTabId(saved.activeId);setTabs(saved.tabs.map(t=>({id:t.id,name:t.doc.name})));setDoc(current.doc);docRef.current=current.doc;setSelected(current.selected);}else{const d=await localLoad();if(live&&d){setDoc(d);docRef.current=d;setSelected(d.layers.at(-1)?.id??null);}}if(live){setReady(true);setTimeout(()=>fit(),30);}})().catch(()=>{setReady(true);notify('Recovery is unavailable. Save a project file.');});return()=>{live=false;aiAbort.current?.abort();};},[]);
app/studio/editor.tsx · SHA-256 8cebf076ec90 · L268–269포토샷 구현 코드 ↗
  function saveProject(){download(new Blob([JSON.stringify(docRef.current)],{type:'application/json'}),`${docRef.current.name}.layerstudio`);notify('Project saved.');}
  async function openProject(file?:File){if(!file)return;try{if(file.size>100*1024*1024)throw Error('Project files must be 100 MB or smaller.');const next=validateDoc(JSON.parse(await file.text()));await Promise.all(next.layers.filter(l=>l.src).map(l=>loadImage(l.src!)));openDocument(next);}catch(e){notify((e as Error).message);}}
Photoshot무료 포토샵 개발기 / 19IN PRACTICE

편집 화면에서.

작업을 다시 열다 / workspace
실제 포토샷 작업 화면. 설정과 사진을 함께 확인할 수 있습니다.

아트보드와 보관된 검증의 범위를 남긴다

여러 문서 탭과 한 문서 안의 여러 아트보드는 구분해야 합니다. 문서 모델에는 artboards와 artboardWorkspace가 있고, 아트보드 화면은 같은 문서 안에서 영역을 만들고 이동·크기 변경을 처리합니다. 한 개의 프로젝트로 다시 열 수 있는 범위는 실제 문서 데이터와 포맷 경로에 달려 있습니다.

보관된 workspace-structure-ui 보고서에는 아트보드 그룹·내용 이동·복제 Undo/Redo·복구의 통과 기록이 있습니다. 파일 날짜는 2026년 10월 12일이며 오늘 다시 실행한 보고서가 아닙니다. 오늘은 프로젝트 재열기와 별도 창 자동 복구를 직접 확인했습니다. 저장 공간 부족, 강제 종료의 모든 순간, 다른 브라우저 간 이동까지 검증한 것은 아닙니다. 다음 20편은 PSD·PSB 입출력의 보존 범위를 따로 살펴봅니다.

Photoshot무료 포토샵 개발기 / 19BEFORE / AFTER

원본과 복원 화면.

작업을 다시 열다 / original
01 / AI 생성 시연 원본
작업을 다시 열다 / result
02 / 프로젝트 재열기 · 이름과 불투명도 87% 유지

AI 생성 시연용 사진. 편집 화면과 출력 이미지는 실제 포토샷에서 만들었습니다.