
Building a free Photoshop alternative
Open the work again
This Photoshot journal examines how editing remains editable. Using the same AI portrait as Part 18, we changed a layer name and opacity, then separately checked reopening a project file and restoring the workspace in the same browser.
AI-generated study photograph. Editor captures and outputs show Photoshot in use.
From the source.
An exported image and a project make different promises
PNG delivers the visible composition as one image. Editing layer names, opacity or masks later requires document structure. In the production editor, saveProject serializes the current document as a downloaded JSON .layerstudio file.
It contains the current document, not every open tab or the complete undo history. PSD interchange uses a separate path. Preservation in .layerstudio does not imply that every property survives PSD interchange identically.
Auto-save retains the workspace and one predecessor
SavedWorkspace in workspace-store.ts includes activeId, document tabs and savedAt. It writes workspace to the IndexedDB documents store, first copying an existing value to workspace-recovery. That is one previous workspace, not permanent version history.
A Promise queue serializes writes. Loading validates the version, tab array, a 20-tab limit and the active tab, then validates every document. Named history snapshots, if present, are limited to five per document and checked for valid names, timestamps and documents.
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};Inside the
calculation.
Restoring work does not restore the entire undo stack
editor.tsx tries the recovery copy if reading the normal workspace throws. When rebuilding sessions it initializes undo and redo as empty arrays. Recovering the current document differs from replaying every earlier edit.
The UI distinguishes Saving from Saved on this device and advises saving a project file when auto-save fails. Auto-save is local browser data. A downloaded project is needed to move the work elsewhere or retain it after browser data is removed.
The layer name and 87% opacity returned
On October 22, 2026, we reused Part 18’s 1536×1024 portrait, renamed its layer Journal 19 recovery check and set opacity to 87%. The saved file contained that name and opacity 0.87. File → Open Project created another tab with the same name and 87% opacity, while Undo was disabled.
After Saved on this device appeared, a separate editor window restored five document tabs, the active document and the same layer properties. Undo was again disabled. We closed that verification window afterward. These screenshots demonstrate restored document state, not a photographic color adjustment.
Read the code.
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();}} 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();};},[]); 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);}}In the editor.

Keep artboards and historical evidence in scope
Multiple document tabs differ from multiple artboards inside one document. The model includes artboards and artboardWorkspace; the artboard UI creates, moves and resizes regions within the document. What a project can restore depends on its document data and serialization path.
The stored workspace-structure-ui report records passing artboard grouping, moving contents, duplicate Undo/Redo and recovery checks. Its file date is October 12, 2026; it was not rerun today. Today’s direct checks covered project reopening and auto-recovery in another window, not every storage-quota failure, forced shutdown moment or cross-browser transfer. Part 20 examines PSD and PSB preservation separately.
Original and restored work.


AI-generated study photograph. Editor captures and outputs show Photoshot in use.