{
  "sourceKind": "separate production-editor working copy; not a public GitHub commit",
  "reviewedAt": "2026-10-22",
  "excerpts": [
    {
      "file": "app/studio/duotone.ts",
      "start": 9,
      "end": 14,
      "code": "export type Duotone={inks:DuotoneInk[];overprints?:Record<string,string>;original?:string};\nexport const inkInputs=[0,5,10,20,30,40,50,60,70,80,90,95,100];\nexport const nullInkCurve=()=>[0,...Array(11).fill(-1),1000];\nexport const defaultDuotone=():Duotone=>({inks:[{name:'Black',color:'#000000',curve:nullInkCurve()},{name:'Warm Brown',color:'#98552e',curve:[0,...Array(11).fill(-1),650]}]});\nconst hex=(v:number[])=>'#'+v.map(n=>Math.round(Math.max(0,Math.min(1,n))*255).toString(16).padStart(2,'0')).join(''),rgb=(s:string)=>[1,3,5].map(i=>parseInt(s.slice(i,i+2),16)/255);\nexport function validateDuotone(d:Duotone){if(d?.original&&(typeof d.original!=='string'||d.original.length>87384))throw Error('Invalid preserved Duotone options.');if(!d||!Array.isArray(d.inks)||d.inks.length<1||d.inks.length>4)throw Error('Choose one to four inks.');for(const i of d.inks){if(typeof i.name!=='string'||!i.name.trim()||i.name.length>63||!/^#[0-9a-f]{6}$/i.test(i.color)||!Array.isArray(i.curve)||i.curve.length!==13||i.curve.some((n,j)=>!Number.isInteger(n)||n<((j===0||j===12)?0:-1)||n>1000))throw Error('Invalid ink name, color or curve.');}for(const [key,color] of Object.entries(d.overprints??{}))if(!Number.isInteger(+key)||+key<3||+key>15||!/^#[0-9a-f]{6}$/i.test(color))throw Error('Invalid overprint color.');}",
      "ref": "working-copy-2026-10-22",
      "sha256": "b822fb75df4a8b45873587cf4953f655c45bb6829a225c25fe28234345582a46"
    },
    {
      "file": "app/studio/precision-psd.ts",
      "start": 19,
      "end": 25,
      "code": "export function precisionCompositePixels(bytes:Uint8Array,space:PrecisionSpace,depth:PrecisionDepth,alpha:boolean,profile?:Uint8Array):PrecisionRaster{\n const n=precisionChannels(space),info=compositeInfo(bytes),{width,height,channels,large,compression}=info,size=width*height,needed=n+(alpha?1:0);\n // Duotone stores one 8-bit grayscale plane while keeping PSD color mode 8.\n const duotoneGray=info.colorMode===8&&space==='Gray'&&depth===8&&channels===1&&!alpha;\n if(size>16000000||channels<needed||info.depth!==depth||info.colorMode!==({RGB:3,Gray:1,CMYK:4,Lab:9})[space]&&!duotoneGray||depth===32&&!['RGB','Gray'].includes(space))throw Error('Unsupported flattened PSD dimensions, color mode, depth or channels.');\n const {planes}=compositePayloads(bytes,Array.from({length:needed},(_,i)=>i)),data=new Float32Array(size*(n+1));for(let i=n;i<data.length;i+=n+1)data[i]=1;\n for(let c=0;c<needed;c++){const payload=planes.get(c)!,values=decodePrecisionPlane(payload.data,compression,width,height,depth,large,payload.inflated);for(let i=0;i<size;i++)data[i*(n+1)+c]=space==='CMYK'&&c<n?1-values[i]:values[i];}",
      "ref": "working-copy-2026-10-22",
      "sha256": "abedaf5b02999f574cbc65fff648eaa98c90fc61b29a2ddcbecb2655a97a0c25"
    },
    {
      "file": "app/studio/duotone.ts",
      "start": 19,
      "end": 24,
      "code": "/** Piecewise-linear ink curves provide predictable control without clipping intermediate knots. */\nexport function inkCoverage(curve:number[],input:number){const points=curve.flatMap((v,i)=>v<0?[]:[{x:inkInputs[i]/100,y:v/1000}]);const x=Math.max(0,Math.min(1,input));let i=1;while(i<points.length-1&&x>points[i].x)i++;const a=points[i-1],b=points[i];return a.y+(b.y-a.y)*(x-a.x)/(b.x-a.x);}\nexport function duotoneCanvas(r:PrecisionRaster,d:Duotone){validateDuotone(d);if(r.space!=='Gray')throw Error('Duotone needs a grayscale source.');const c=surface(r.width,r.height),g=c.getContext('2d')!,im=g.createImageData(r.width,r.height),inks=d.inks.map(i=>rgb(i.color)),colors=Array.from({length:1<<inks.length},(_,mask)=>{if(d.overprints?.[mask])return rgb(d.overprints[mask]);return [0,1,2].map(k=>inks.reduce((n,ink,j)=>mask&1<<j?n*ink[k]:n,1));}),table=Array.from({length:256},(_,tone)=>{const coverage=d.inks.map(i=>inkCoverage(i.curve,tone/255)),out=[0,0,0];for(let m=0;m<colors.length;m++){const weight=coverage.reduce((n,t,j)=>n*(m&1<<j?t:1-t),1);for(let k=0;k<3;k++)out[k]+=colors[m][k]*weight;}return out;});for(let i=0;i<r.width*r.height;i++){const gray=r.data[i*2]*r.data[i*2+1]+1-r.data[i*2+1],color=table[Math.round(Math.max(0,Math.min(1,1-gray))*255)];for(let k=0;k<3;k++)im.data[i*4+k]=color[k]*255;im.data[i*4+3]=255;}g.putImageData(im,0,0);return c;}\nexport async function convertDuotone(doc:StudioDoc,settings:Duotone,wasm?:Uint8Array):Promise<StudioDoc>{validateDuotone(settings);if(doc.duotone)return {...doc,duotone:structuredClone(settings)};const image=await composite(doc,true),pixels=await convertPrecision(floatCanvas(image),'Gray',8,undefined,wasm),src=(await precisionCanvas(pixels,wasm)).toDataURL();return {...doc,version:6,duotone:structuredClone(settings),precision:{space:'Gray',depth:8},layers:[makeLayer({kind:'image',name:'Duotone',width:doc.width,height:doc.height,src,precision:packPrecision(pixels),precisionSource:precisionSource(src)})],groups:undefined,artboards:undefined,artboardWorkspace:undefined,psdData:undefined,selection:undefined,savedSelections:undefined,spotChannels:undefined,layerComps:undefined,activeLayerCompId:undefined};}\nexport async function readDuotonePSD(bytes:Uint8Array,name:string,base:StudioDoc){const v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength);if(v.getUint16(22)!==8)throw Error('Duotone PSD requires 8-bit grayscale.');const length=v.getUint32(26);if(length>65536||30+length>bytes.length)throw Error('Invalid Duotone data length.');const resolution=readResource(bytes,1005),ppi=resolution&&resolution.length>=4?new DataView(resolution.buffer,resolution.byteOffset,resolution.byteLength).getUint32(0)/65536:72,duotone=readDuotoneOptions(bytes.subarray(30,30+length)),pixels=precisionCompositePixels(bytes,'Gray',8,false),src=(await precisionCanvas(pixels)).toDataURL();return {...base,resolution:ppi,name:name.replace(/\\.(psd|psb)$/i,''),width:pixels.width,height:pixels.height,version:6 as const,duotone,precision:{space:'Gray' as const,depth:8 as const},layers:[makeLayer({kind:'image',name:'Duotone',width:pixels.width,height:pixels.height,src,precision:packPrecision(pixels),precisionSource:precisionSource(src)})],sourceFile:{name,mode:8,depth:8,warnings:[]}};}\nexport async function writeDuotonePSD(doc:StudioDoc,psb:boolean){if(!doc.duotone||doc.precision?.space!=='Gray'||doc.precision.depth!==8)throw Error('Duotone requires an 8-bit grayscale document.');const options=writeDuotoneOptions(doc.duotone),r=await compositePrecisionDocument(doc);for(let i=0;i<r.width*r.height;i++){r.data[i*2]=r.data[i*2]*r.data[i*2+1]+1-r.data[i*2+1];r.data[i*2+1]=1;}const header=new Uint8Array(30),v=new DataView(header.buffer);header.set(new TextEncoder().encode('8BPS'));v.setUint16(4,psb?2:1);v.setUint16(12,1);v.setUint32(14,r.height);v.setUint32(18,r.width);v.setUint16(22,8);v.setUint16(24,8);v.setUint32(26,options.length);const tail=new Uint8Array(4+(psb?8:4)+2),pixels=channelBytes(r,0),out=new Uint8Array(header.length+options.length+tail.length+pixels.length);out.set(header);out.set(options,header.length);out.set(tail,header.length+options.length);out.set(pixels,header.length+options.length+tail.length);const resolution=new Uint8Array(16),rv=new DataView(resolution.buffer),ppi=Math.round((doc.resolution??72)*65536);rv.setUint32(0,ppi);rv.setUint16(4,1);rv.setUint16(6,1);rv.setUint32(8,ppi);rv.setUint16(12,1);rv.setUint16(14,1);return writeResource(out,1005,resolution);}",
      "ref": "working-copy-2026-10-22",
      "sha256": "b822fb75df4a8b45873587cf4953f655c45bb6829a225c25fe28234345582a46"
    },
    {
      "file": "app/studio/color-output.ts",
      "start": 10,
      "end": 17,
      "code": "export function inkSummary(output:ColorOutput){if(output.profile.space!=='CMYK')return undefined;let max=0,mean=0;for(let i=0;i<output.pixels.length;i+=4){const sum=(output.pixels[i]+output.pixels[i+1]+output.pixels[i+2]+output.pixels[i+3])/65535*100;max=Math.max(max,sum);mean+=sum;}return {max,mean:mean/(output.width*output.height)};}\nexport async function colorOutputPreview(output:ColorOutput,plate=-1,wasmBinary?:Uint8Array){const cmyk=output.profile.space==='CMYK',out=new Uint8ClampedArray(output.width*output.height*4);if(cmyk&&plate>=0){if(!Number.isInteger(plate)||plate>3)throw Error('Choose a CMYK plate.');for(let i=0;i<out.length;i+=4){out[i]=out[i+1]=out[i+2]=255-output.pixels[i+plate]/257;out[i+3]=255;}return out;}const m=await loadICCEngine(wasmBinary),a=m.cmsOpenProfileFromMem(output.profile.bytes,output.profile.bytes.length),b=m.cmsCreate_sRGBProfile();let t=0;try{const n=cmyk?4:3;t=m.cmsCreateTransform(a,((cmyk?6:4)<<16)|(n<<3)|2,b,(4<<16)|(3<<3)|1,1,0x2000);if(!t)throw Error('This output profile cannot be previewed.');for(let start=0;start<output.width*output.height;start+=8192){const count=Math.min(8192,output.width*output.height-start),input=new Uint16Array(count*n);for(let i=0;i<count;i++)for(let k=0;k<n;k++)input[i*n+k]=output.pixels[(start+i)*4+k];const rgb=m.cmsDoTransform(t,input,count);for(let i=0;i<count;i++){out[(start+i)*4]=rgb[i*3];out[(start+i)*4+1]=rgb[i*3+1];out[(start+i)*4+2]=rgb[i*3+2];out[(start+i)*4+3]=cmyk?255:output.pixels[(start+i)*4+3]/257;}await new Promise(r=>setTimeout(r,0));}return out;}finally{if(t)m.cmsDeleteTransform(t);if(a)m.cmsCloseProfile(a);if(b)m.cmsCloseProfile(b);}}\n/** Single-channel uncompressed 16-bit TIFF, BlackIsZero; ink 100% is black. */\nexport function separationTiff(output:ColorOutput,plate:number,dpi:number){if(output.profile.space!=='CMYK'||!Number.isInteger(plate)||plate<0||plate>3||!Number.isFinite(dpi)||dpi<1||dpi>1200)throw Error('Invalid separation output.');return gray16Tiff(Uint16Array.from({length:output.width*output.height},(_,i)=>65535-output.pixels[i*4+plate]),output.width,output.height,dpi);}\nexport function saveColorOutput(output:ColorOutput,dpi:number,plate=-1){return plate>=0?separationTiff(output,plate,dpi):tiff16(output.pixels,output.width,output.height,dpi,output.profile.bytes,output.profile.space==='CMYK');}\nexport const defaultOutputProfile=():PrintProfile=>({name:'sRGB IEC61966-2.1',bytes:srgbProfile(),space:'RGB '});\n\nexport async function convertDocumentColorOutput(doc:StudioDoc,profile:PrintProfile,options:ColorOutputOptions,cancelled:()=>boolean=()=>false,maxEdge?:number,wasmBinary?:Uint8Array):Promise<ColorOutput>{if(!doc.precision||doc.duotone){const c=await composite(doc,false,{cancelled,maxEdge});return convertColorOutput(c.getContext('2d')!.getImageData(0,0,c.width,c.height).data,c.width,c.height,profile,options,cancelled,wasmBinary);}validatePrintProfile(profile.bytes,profile.name);if(![0,1,2,3].includes(options.intent)||!Number.isFinite(options.dpi)||options.dpi<1||options.dpi>1200)throw Error('Invalid output settings.');const check=()=>{if(cancelled())throw new DOMException('Export cancelled','AbortError');};check();let raster=await compositePrecisionDocument(doc,wasmBinary);const n=precisionChannels(raster.space);for(const [name,spot] of Object.entries(doc.spotChannels??{})){if(spot.visible===false||!spot.solidity)continue;const plate=doc.savedSelections?.[name];if(!plate)continue;const rgb=spot.color.slice(1).match(/../g)!.map(v=>parseInt(v,16)/255),color=await convertPrecision({width:1,height:1,space:'RGB',depth:8,data:new Float32Array([...rgb,1])},raster.space,raster.depth,profileBytes(doc.precision.profile),wasmBinary),ink={...raster,data:new Float32Array(raster.data.length)};for(let i=0;i<raster.width*raster.height;i++){ink.data.set(color.data.subarray(0,n),i*(n+1));ink.data[i*(n+1)+n]=(1-plate.alpha[i]/255)*spot.solidity/100;}raster=compositePrecision(raster,ink);}if(maxEdge&&Math.max(raster.width,raster.height)>maxEdge){const ratio=maxEdge/Math.max(raster.width,raster.height),width=Math.max(1,Math.round(raster.width*ratio)),height=Math.max(1,Math.round(raster.height*ratio)),original=raster;raster=resamplePrecision(original,width,height,(x,y)=>({x:x*original.width/width,y:y*original.height/height}));}check();const cmyk=profile.space==='CMYK',converted=await convertPrecision(raster,cmyk?'CMYK':'RGB',16,profile.bytes,wasmBinary,{intent:options.intent,blackPoint:options.blackPoint}),channels=cmyk?4:3,pixels=new Uint16Array(converted.width*converted.height*4);for(let i=0;i<converted.width*converted.height;i++){const a=converted.data[i*(channels+1)+channels];for(let c=0;c<channels;c++)pixels[i*4+c]=Math.round(Math.max(0,Math.min(1,converted.data[i*(channels+1)+c]*(cmyk?a:1)))*65535);if(!cmyk)pixels[i*4+3]=Math.round(a*65535);}check();return {pixels,width:converted.width,height:converted.height,profile};}",
      "ref": "working-copy-2026-10-22",
      "sha256": "8e4b90dbd19b83a64ab529d6ed063bbbd55669c2627b77cc237f9b2dc5a34d5b"
    }
  ],
  "productionUI": {
    "episode": 31,
    "checkedAt": "2026-10-22",
    "duotone": {
      "inks": [
        {
          "name": "Black",
          "color": "#000000",
          "curve": [
            0,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            -1,
            1000
          ]
        },
        {
          "name": "Warm Brown",
          "color": "#98552e",
          "curve": [
            0,
            -1,
            -1,
            -1,
            -1,
            -1,
            350,
            -1,
            -1,
            -1,
            -1,
            -1,
            650
          ]
        }
      ],
      "adoBytes": 524,
      "psdMode": 8,
      "depth": 8,
      "presetMatchesPSDOptions": true,
      "publicPSDReopenSucceeded": true,
      "inkMetadataPreserved": true,
      "grayscaleMaxQuantization": 0.0019608139991760254,
      "brownSeparationMatchesCurveExactly": true
    },
    "CMYK": {
      "profile": "RSWOP.icm",
      "profileSHA256": "48d1cfdccf06dabfecf0c2b535eacd8a5f49560f0182691cdd713b6389a30510",
      "embeddedProfileExact": true,
      "size": [
        1536,
        1024
      ],
      "bitsPerChannel": 16,
      "ppi": 300,
      "blackPlateExact": true,
      "totalInkMax": 170.00534065766385,
      "totalInkMean": 122.61693676764487,
      "previewMax": 171.8,
      "previewMean": 122.6,
      "previewNote": "Downsampled preview statistics differ from full exported raster."
    },
    "bugFix": {
      "cause": "Duotone mode 8 was rejected by the Gray mode 1 composite guard",
      "scope": "Allow mode 8 only for Gray, 8 bits, one channel and no alpha",
      "regression": {
        "passed": true,
        "checks": [
          {
            "format": "PSD",
            "inkMetadata": true,
            "pixelsExact": true
          },
          {
            "format": "PSB",
            "inkMetadata": true,
            "pixelsExact": true
          },
          {
            "file": "rgb32-plus1.psd",
            "modePreserved": true
          },
          {
            "file": "lab16.psd",
            "modePreserved": true
          }
        ],
        "rejectedInvalidCombinations": 4
      },
      "production": {
        "id": "47d91921-479e-4aca-870b-6c4cb999a615",
        "url": "https://47d91921.genielab.pages.dev",
        "previous": "92ee51c6-4227-40c2-b895-f6b0053f314c",
        "files": 2749,
        "untouched": 2741,
        "serverPreserved": true,
        "configurationPreserved": true,
        "passed": true
      }
    },
    "printPreview": {
      "paper": "A4",
      "marginMM": 10,
      "scaleToFit": true,
      "displayedSizeMM": [
        190,
        126.7
      ],
      "physicalPrintPerformed": false
    },
    "passed": true
  },
  "historicalReports": [
    {
      "file": "proof-render-results.json",
      "sha256": "37f206c4ab74b3eeb57849ce50667f8dfc036796cb5fc6f772581f6ff88d195c",
      "results": {
        "passed": true,
        "checks": [
          "sRGB proof identity, disabled bypass, alpha and source immutability",
          "Installed CMYK device proof, four intents, paper simulation and actual LittleCMS gamut alarm",
          "Project setup restoration, export compositor unchanged, cancellation and invalid/device-link rejection"
        ],
        "outOfGamut": 3,
        "profileFixture": "Read-only installed RSWOP.icm; not redistributed"
      },
      "freshRun": false
    },
    {
      "file": "precision-output-native-results.json",
      "sha256": "34ae69608c36a8640a8ba0a58835df8bc7b30097becadbf5eb770921eb3a76ab",
      "results": {
        "passed": true,
        "checks": [
          {
            "kind": "rgb16",
            "maxError": 0,
            "profileExact": true
          },
          {
            "kind": "rgb32",
            "maxError": 1.5288591384887695e-05,
            "profileExact": true
          },
          {
            "kind": "cmyk16",
            "maxError": 5.960464477539063e-08,
            "profileExact": true
          },
          {
            "kind": "lab16",
            "maxError": 1.531839370727539e-05,
            "profileExact": true
          },
          {
            "artboard": "PSD",
            "depth": 32,
            "nativeBoundsPreserved": true
          },
          {
            "artboard": "PSB",
            "depth": 32,
            "nativeBoundsPreserved": true
          }
        ]
      },
      "freshRun": false
    },
    {
      "file": "duotone-native-results.json",
      "sha256": "8fe619278d2a5ace2bda4287516a1c9d667ee75c8e54c97777e4402fd9c20bd6",
      "results": {
        "passed": true,
        "checks": [
          "duotone-1.psd",
          "duotone-1-psb.psd",
          "duotone-2.psd",
          "duotone-2-psb.psd",
          "duotone-3.psd",
          "duotone-3-psb.psd",
          "duotone-4.psd",
          "duotone-4-psb.psd"
        ],
        "nativeMode": 8,
        "nativeResolution": 300,
        "nativeGraySamplesUnchanged": true,
        "documentsPreserved": true
      },
      "freshRun": false
    }
  ]
}