{
  "sourceKind": "separate production-editor working copy; not a public GitHub commit",
  "reviewedAt": "2026-10-22",
  "excerpts": [
    {
      "file": "app/studio/shapes.ts",
      "start": 6,
      "end": 9,
      "code": "export type Shape={gradient?:ShapeGradient;strokeGradient?:ShapeGradient;dash?:number[];dashOffset?:number;lineCap?:CanvasLineCap;lineJoin?:CanvasLineJoin;paths?:PathShape[];type:'rectangle'|'ellipse'|'polygon'|'line';fill:string;stroke:string;strokeWidth:number;radius:number;sides:number};\nexport const defaultShape:Shape={type:'rectangle',fill:'#bfff50',stroke:'#000000',strokeWidth:0,radius:0,sides:5};\nexport function validateShape(s:Shape){if(s)validateShapePaint(s);if(s?.paths)validatePaths([{id:'shape',name:'Shape',paths:s.paths}]);if(!s||!['rectangle','ellipse','polygon','line'].includes(s.type)||![s.fill,s.stroke].every(c=>c==='none'||/^#[0-9a-f]{6}$/i.test(c))||!Number.isFinite(s.strokeWidth)||s.strokeWidth<0||s.strokeWidth>300||!Number.isFinite(s.radius)||s.radius<0||s.radius>8192||!Number.isInteger(s.sides)||s.sides<3||s.sides>100)throw Error('Invalid shape settings.');}\nexport function paintShape(ctx:CanvasRenderingContext2D,l:Layer){const s=l.shape!;validateShape(s);if(s.paths){paintCompoundShape(ctx,l);return;}ctx.save();ctx.beginPath();for(const p of shapePaths(l)){if(!p.knots.length)continue;ctx.moveTo(p.knots[0].points[2],p.knots[0].points[3]);for(let i=1;i<p.knots.length+(p.open?0:1);i++){const a=p.knots[i-1].points,b=p.knots[i%p.knots.length].points;ctx.bezierCurveTo(a[4],a[5],b[0],b[1],b[2],b[3]);}if(!p.open)ctx.closePath();}if(s.fill!=='none'&&s.type!=='line'){ctx.fillStyle=shapePaint(ctx,s,'fill',l.width,l.height);ctx.fill();}if(s.stroke!=='none'&&s.strokeWidth){ctx.strokeStyle=shapePaint(ctx,s,'stroke',l.width,l.height);setupShapeStroke(ctx,s);ctx.stroke();}ctx.restore();}",
      "ref": "working-copy-2026-10-22",
      "sha256": "0a8c6ebfe3f86ef519790243d72fe3dc5e48bda77781768eb4451b7b026e74fc"
    },
    {
      "file": "app/studio/shape-boolean.ts",
      "start": 2,
      "end": 3,
      "code": "export type ShapeOperation='combine'|'subtract'|'intersect'|'exclude';\nexport function combineShapes(doc:StudioDoc,ids:string[],operation:ShapeOperation):StudioDoc{if(!['combine','subtract','intersect','exclude'].includes(operation))throw Error('Invalid shape operation.');const layers=doc.layers.filter(l=>ids.includes(l.id));if(layers.length<2||layers.length>32)throw Error('Select 2 to 32 shape layers.');for(const l of layers)if(!l.shape||l.shape.type==='line'||l.shape.paths?.some(p=>p.open)||layerLocked(doc,l)||l.mask||l.vectorMask||l.gridWarp||l.puppet||l.strokes.length||l.clipped)throw Error('Select unlocked, unmasked filled shape layers.');if(new Set(layers.map(l=>l.groupId??'')).size!==1)throw Error('Choose shapes in the same group.');const paths:PathShape[]=[];for(let index=0;index<layers.length;index++){const l=layers[index],matrix=homography(quadPoints(l)),local=shapePaths(l).map(p=>({...p,knots:p.knots.map(k=>({...k,points:k.points.map((n,i)=>i%2?(l.flipY?1-n/l.height:n/l.height):(l.flipX?1-n/l.width:n/l.width))}))}));if(local.length>1&&(operation!=='combine'||index>0&&local.some(p=>p.operation&&p.operation!=='combine')))throw Error('For subtraction/intersection, use single-path shapes. Compound unions remain editable.');for(const p of local)paths.push({...projectPath(p,matrix),operation:index===0?p.operation??'combine':operation});}const values=paths.flatMap(p=>p.knots.flatMap(k=>[0,2,4].map(i=>({x:k.points[i],y:k.points[i+1]})))),pad=layers[0].shape!.strokeWidth/2,left=Math.floor(Math.min(...values.map(p=>p.x))-pad),top=Math.floor(Math.min(...values.map(p=>p.y))-pad),right=Math.ceil(Math.max(...values.map(p=>p.x))+pad),bottom=Math.ceil(Math.max(...values.map(p=>p.y))+pad),width=Math.max(1,right-left),height=Math.max(1,bottom-top);if(width*height>16000000)throw Error('Combined shapes currently support up to 16 MP.');const base=layers[0],shape={...base.shape!,radius:0,paths:paths.map(p=>({...p,knots:p.knots.map(k=>({...k,points:k.points.map((n,i)=>i%2?(n-top)/height:(n-left)/width)}))}))},result=makeLayer({name:'Combined Shape',kind:'paint',x:left,y:top,width,height,shape,groupId:base.groupId,opacity:base.opacity,fill:base.fill,blend:base.blend,effects:base.effects}),first=doc.layers.indexOf(base),remaining=doc.layers.filter(l=>!ids.includes(l.id));remaining.splice(first,0,result);const next={...doc,layers:remaining};validateDoc(next);return next;}",
      "ref": "working-copy-2026-10-22",
      "sha256": "e95c26997f59810f2e904da8e94a9e30b0afb556a9b07cddb709edf485147ab5"
    },
    {
      "file": "app/studio/shape-paths.ts",
      "start": 1,
      "end": 2,
      "code": "import type {Layer} from './document.ts';import type {PathShape} from './paths.ts';\nexport function shapePaths(layer:Layer):PathShape[]{const s=layer.shape!;if(s.paths)return s.paths.map(p=>({...p,knots:p.knots.map(k=>({...k,points:k.points.map((n,i)=>n*(i%2?layer.height:layer.width))}))}));const inset=s.strokeWidth/2,w=Math.max(0,layer.width-s.strokeWidth),h=Math.max(0,layer.height-s.strokeWidth),x=inset,y=inset,k=.5522847498307936,knot=(x:number,y:number,ix=x,iy=y,ox=x,oy=y)=>({linked:true,points:[ix,iy,x,y,ox,oy]}),path:PathShape={open:s.type==='line',operation:'combine',fillRule:'non-zero',knots:[]};if(s.type==='ellipse'){const cx=x+w/2,cy=y+h/2,rx=w/2,ry=h/2;path.knots=[knot(cx,y,cx-k*rx,y,cx+k*rx,y),knot(x+w,cy,x+w,cy-k*ry,x+w,cy+k*ry),knot(cx,y+h,cx+k*rx,y+h,cx-k*rx,y+h),knot(x,cy,x,cy+k*ry,x,cy-k*ry)];}else if(s.type==='polygon'){path.knots=Array.from({length:s.sides},(_,i)=>{const a=-Math.PI/2+i*Math.PI*2/s.sides;return knot(layer.width/2+Math.cos(a)*w/2,layer.height/2+Math.sin(a)*h/2);});}else if(s.type==='line')path.knots=[knot(x,y),knot(x+w,y+h)];else{const r=Math.min(s.radius,w/2,h/2);path.knots=r?[knot(x+r,y,x+r-k*r,y),knot(x+w-r,y,x+w-r,y,x+w-r+k*r,y),knot(x+w,y+r,x+w,y+r-k*r),knot(x+w,y+h-r,x+w,y+h-r,x+w,y+h-r+k*r),knot(x+w-r,y+h,x+w-r+k*r,y+h),knot(x+r,y+h,x+r,y+h,x+r-k*r,y+h),knot(x,y+h-r,x,y+h-r+k*r),knot(x,y+r,x,y+r,x,y+r-k*r)]:[knot(x,y),knot(x+w,y),knot(x+w,y+h),knot(x,y+h)];}return [path];}",
      "ref": "working-copy-2026-10-22",
      "sha256": "f5509e36cabfc4674d8762a7559295cbe908e44129cb19430fcc94c2d1bcc8f8"
    },
    {
      "file": "app/studio/shape-paint-style.ts",
      "start": 2,
      "end": 4,
      "code": "export type ShapeGradient={type:'linear'|'radial'|'reflected';angle:number;scale:number;reverse:boolean;stops:{location:number;color:string;opacity:number}[]};\nexport const defaultShapeGradient=():ShapeGradient=>({type:'linear',angle:0,scale:100,reverse:false,stops:[{location:0,color:'#000000',opacity:1},{location:1,color:'#ffffff',opacity:1}]});\nexport function validateShapePaint(s:Shape){for(const g of [s.gradient,s.strokeGradient])if(g){if(!['linear','radial','reflected'].includes(g.type)||!Number.isFinite(g.angle)||Math.abs(g.angle)>360||!Number.isFinite(g.scale)||g.scale<1||g.scale>1000||typeof g.reverse!=='boolean'||!Array.isArray(g.stops)||g.stops.length<2||g.stops.length>32||g.stops.some((p,i)=>!Number.isFinite(p.location)||p.location<0||p.location>1||i>0&&p.location<g.stops[i-1].location||!/^#[0-9a-f]{6}$/i.test(p.color)||!Number.isFinite(p.opacity)||p.opacity<0||p.opacity>1))throw Error('Invalid shape gradient.');}if(s.dash&&(s.dash.length>16||s.dash.some(n=>!Number.isFinite(n)||n<.1||n>10000)))throw Error('Dash lengths must be between 0.1 and 10000 pixels.');if(s.dashOffset!==undefined&&(!Number.isFinite(s.dashOffset)||Math.abs(s.dashOffset)>10000))throw Error('Invalid dash offset.');if(s.lineCap&&!['butt','round','square'].includes(s.lineCap)||s.lineJoin&&!['round','bevel','miter'].includes(s.lineJoin))throw Error('Invalid shape stroke style.');}",
      "ref": "working-copy-2026-10-22",
      "sha256": "719b2c995bfec80a0f2595bcd00fe19d460c0ddbc9eee389e42b06dd99e2a698"
    }
  ],
  "productionUI": {
    "date": "2026-10-22",
    "passed": true,
    "route": "UI shape creation, subtraction, path inspection and project reopen",
    "shape": {
      "type": "ellipse",
      "fill": "#bfff50",
      "stroke": "#000000",
      "strokeWidth": 6,
      "radius": 0,
      "sides": 5,
      "gradient": {
        "type": "linear",
        "angle": 0,
        "scale": 100,
        "reverse": false,
        "stops": [
          {
            "location": 0,
            "color": "#000000",
            "opacity": 1
          },
          {
            "location": 1,
            "color": "#ffffff",
            "opacity": 1
          }
        ]
      },
      "paths": [
        {
          "open": false,
          "operation": "combine",
          "fillRule": "non-zero",
          "knots": [
            {
              "linked": true,
              "points": [
                0.18920680251068692,
                0.006521739130434782,
                0.4159132007233273,
                0.006521739130434782,
                0.6426195989359677,
                0.006521739130434782
              ]
            },
            {
              "linked": true,
              "points": [
                0.8264014466546112,
                0.22745948214871714,
                0.8264014466546112,
                0.5,
                0.8264014466546112,
                0.7725405178512829
              ]
            },
            {
              "linked": true,
              "points": [
                0.6426195989359677,
                0.9934782608695653,
                0.4159132007233273,
                0.9934782608695653,
                0.18920680251068692,
                0.9934782608695653
              ]
            },
            {
              "linked": true,
              "points": [
                0.0054249547920434,
                0.7725405178512829,
                0.0054249547920434,
                0.5,
                0.0054249547920434,
                0.22745948214871714
              ]
            }
          ]
        },
        {
          "open": false,
          "operation": "subtract",
          "fillRule": "non-zero",
          "knots": [
            {
              "linked": true,
              "points": [
                0.45207956600361665,
                0.2608695652173913,
                0.45207956600361665,
                0.2608695652173913,
                0.45207956600361665,
                0.2608695652173913
              ]
            },
            {
              "linked": true,
              "points": [
                0.9945750452079566,
                0.2608695652173913,
                0.9945750452079566,
                0.2608695652173913,
                0.9945750452079566,
                0.2608695652173913
              ]
            },
            {
              "linked": true,
              "points": [
                0.9945750452079566,
                0.7391304347826086,
                0.9945750452079566,
                0.7391304347826086,
                0.9945750452079566,
                0.7391304347826086
              ]
            },
            {
              "linked": true,
              "points": [
                0.45207956600361665,
                0.7391304347826086,
                0.45207956600361665,
                0.7391304347826086,
                0.45207956600361665,
                0.7391304347826086
              ]
            }
          ]
        }
      ]
    },
    "entireCompoundLayerExactMatch": true,
    "freshAdobeTest": false,
    "individualAnchorDragged": false
  },
  "historicalReports": [
    {
      "file": "shape-boolean-render-results.json",
      "sha256": "b8eeeb15062721ce5ada327ed05e6c333add17395470c6d2c0f954ad242bbc6c",
      "results": {
        "passed": true,
        "checks": [
          "Union/subtract/intersection/xor pixels, editable path components, native PSD/PSB fill and paths, external fill guard",
          "Combined shape strokes follow the outer boundary without interior seams",
          "Native editable basic shape geometry and stroke metadata, lock protection",
          "Rasterization, Smart Object conversion and painted shapes clear outer native fills while retaining pixels and embedded vector contents"
        ]
      },
      "freshRun": false
    },
    {
      "file": "shape-paint-native-results.json",
      "sha256": "f4540525ae6c4debf6b20c4e931c7469df422ff9305efd27d143bd00abae7d46",
      "results": {
        "passed": true,
        "editableNativeVectors": true,
        "comparisons": [
          {
            "name": "shape-paint-linear.psd",
            "mae": 0.646875,
            "max": 2
          },
          {
            "name": "shape-paint-linear.psb",
            "mae": 0.646875,
            "max": 2
          },
          {
            "name": "shape-paint-radial.psd",
            "mae": 1.059234375,
            "max": 4
          },
          {
            "name": "shape-paint-radial.psb",
            "mae": 1.059234375,
            "max": 4
          },
          {
            "name": "shape-paint-reflected.psd",
            "mae": 1.1953125,
            "max": 2
          },
          {
            "name": "shape-paint-reflected.psb",
            "mae": 1.1953125,
            "max": 2
          },
          {
            "name": "shape-paint-dashed.psd",
            "mae": 0.152578125,
            "max": 255
          },
          {
            "name": "shape-paint-dashed.psb",
            "mae": 0.152578125,
            "max": 255
          }
        ],
        "documentsBefore": 0,
        "documentsAfter": 0
      },
      "freshRun": false
    }
  ]
}