fundus 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +7 -5
  2. package/build/client/_app/immutable/assets/2.DOKgeL9V.css +1 -0
  3. package/build/client/_app/immutable/assets/2.DOKgeL9V.css.br +0 -0
  4. package/build/client/_app/immutable/assets/2.DOKgeL9V.css.gz +0 -0
  5. package/build/client/_app/immutable/assets/{3.BuCXJbRG.css → 3.D6PF4wEW.css} +1 -1
  6. package/build/client/_app/immutable/assets/3.D6PF4wEW.css.br +0 -0
  7. package/build/client/_app/immutable/assets/3.D6PF4wEW.css.gz +0 -0
  8. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css +1 -0
  9. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.br +0 -0
  10. package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.gz +0 -0
  11. package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css +1 -0
  12. package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.br +0 -0
  13. package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.gz +0 -0
  14. package/build/client/_app/immutable/chunks/B9HdDE5n.js +1 -0
  15. package/build/client/_app/immutable/chunks/B9HdDE5n.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/B9HdDE5n.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/BfQXIsM7.js +1 -0
  18. package/build/client/_app/immutable/chunks/BfQXIsM7.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/BfQXIsM7.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/Bj31DJwy.js +1 -0
  21. package/build/client/_app/immutable/chunks/Bj31DJwy.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/Bj31DJwy.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/{DFRlZOKz.js → BufZeTod.js} +2 -2
  24. package/build/client/_app/immutable/chunks/BufZeTod.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/BufZeTod.js.gz +0 -0
  26. package/build/client/_app/immutable/chunks/BxCbL__p.js +1 -0
  27. package/build/client/_app/immutable/chunks/BxCbL__p.js.br +0 -0
  28. package/build/client/_app/immutable/chunks/BxCbL__p.js.gz +0 -0
  29. package/build/client/_app/immutable/chunks/{DpFtnEPx.js → CjB5zQ4_.js} +1 -1
  30. package/build/client/_app/immutable/chunks/CjB5zQ4_.js.br +0 -0
  31. package/build/client/_app/immutable/chunks/CjB5zQ4_.js.gz +0 -0
  32. package/build/client/_app/immutable/chunks/DuXWzgb4.js +108 -0
  33. package/build/client/_app/immutable/chunks/DuXWzgb4.js.br +0 -0
  34. package/build/client/_app/immutable/chunks/DuXWzgb4.js.gz +0 -0
  35. package/build/client/_app/immutable/chunks/{BMORe2Gb.js → YpiA5Xry.js} +1 -1
  36. package/build/client/_app/immutable/chunks/YpiA5Xry.js.br +0 -0
  37. package/build/client/_app/immutable/chunks/{BMORe2Gb.js.gz → YpiA5Xry.js.gz} +0 -0
  38. package/build/client/_app/immutable/entry/app.DHoRwUAh.js +2 -0
  39. package/build/client/_app/immutable/entry/app.DHoRwUAh.js.br +0 -0
  40. package/build/client/_app/immutable/entry/app.DHoRwUAh.js.gz +0 -0
  41. package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js +1 -0
  42. package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.br +0 -0
  43. package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/0.Df_skSaZ.js +1 -0
  45. package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js +1 -0
  48. package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js +3 -0
  51. package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.gz +0 -0
  53. package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js +1 -0
  54. package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.br +0 -0
  55. package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.gz +0 -0
  56. package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js +2 -0
  57. package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.br +0 -0
  58. package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.gz +0 -0
  59. package/build/client/_app/version.json +1 -1
  60. package/build/client/_app/version.json.br +0 -0
  61. package/build/client/_app/version.json.gz +0 -0
  62. package/build/handler.js +7 -7
  63. package/build/index.js +7 -7
  64. package/build/server/chunks/chunks/{context.js-BJfk3ZiR.js → context.js-BNdRFspW.js} +100 -1
  65. package/build/server/chunks/chunks/context.js-BNdRFspW.js.map +1 -0
  66. package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js → exports.js-CUFgHhPL.js} +2 -2
  67. package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js.map → exports.js-CUFgHhPL.js.map} +1 -1
  68. package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js → internal.js-DUquB_BJ.js} +3 -3
  69. package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js.map → internal.js-DUquB_BJ.js.map} +1 -1
  70. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  71. package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js → shared.js-BXL1A-vX.js} +2 -2
  72. package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js.map → shared.js-BXL1A-vX.js.map} +1 -1
  73. package/build/server/chunks/chunks/{utils.js-6HSqnONB.js → utils.js-xC62cOm2.js} +3 -3
  74. package/build/server/chunks/chunks/{utils.js-6HSqnONB.js.map → utils.js-xC62cOm2.js.map} +1 -1
  75. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js → _server.ts.js-DEfBMmVd.js} +2 -2
  76. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js.map → _server.ts.js-DEfBMmVd.js.map} +1 -1
  77. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js → _server.ts.js-BEJcx3ya.js} +2 -2
  78. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js.map → _server.ts.js-BEJcx3ya.js.map} +1 -1
  79. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-B2JoGCzy.js → _server.ts.js-CibkO-4G.js} +2 -2
  80. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-B2JoGCzy.js.map → _server.ts.js-CibkO-4G.js.map} +1 -1
  81. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js → _server.ts.js-BdisCQk6.js} +2 -2
  82. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js.map → _server.ts.js-BdisCQk6.js.map} +1 -1
  83. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js → _server.ts.js-Bk2hojus.js} +2 -2
  84. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js.map → _server.ts.js-Bk2hojus.js.map} +1 -1
  85. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js → _server.ts.js-CTeQlqUL.js} +2 -2
  86. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js.map → _server.ts.js-CTeQlqUL.js.map} +1 -1
  87. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js → _server.ts.js-DLxdefrX.js} +2 -2
  88. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js.map → _server.ts.js-DLxdefrX.js.map} +1 -1
  89. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js → _server.ts.js-Ceq1tMTz.js} +2 -2
  90. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js.map → _server.ts.js-Ceq1tMTz.js.map} +1 -1
  91. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js → _server.ts.js-DxAWqf9V.js} +2 -2
  92. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js.map → _server.ts.js-DxAWqf9V.js.map} +1 -1
  93. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js → _server.ts.js-BB_6WywB.js} +2 -2
  94. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js.map → _server.ts.js-BB_6WywB.js.map} +1 -1
  95. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js → _server.ts.js-BLRkduAp.js} +2 -2
  96. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js.map → _server.ts.js-BLRkduAp.js.map} +1 -1
  97. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-C-NuN3JJ.js → _server.ts.js-Ba94-p0V.js} +2 -2
  98. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-C-NuN3JJ.js.map → _server.ts.js-Ba94-p0V.js.map} +1 -1
  99. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js → _server.ts.js-CNhj0R4-.js} +2 -2
  100. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js.map → _server.ts.js-CNhj0R4-.js.map} +1 -1
  101. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js → _server.ts.js-DWEV8mgx.js} +2 -2
  102. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js.map → _server.ts.js-DWEV8mgx.js.map} +1 -1
  103. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js → _server.ts.js-BnDnZdPy.js} +2 -2
  104. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js.map → _server.ts.js-BnDnZdPy.js.map} +1 -1
  105. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js → _server.ts.js-1y2Lu6HB.js} +2 -2
  106. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js.map → _server.ts.js-1y2Lu6HB.js.map} +1 -1
  107. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js → _server.ts.js-CnLJo2iZ.js} +2 -2
  108. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js.map → _server.ts.js-CnLJo2iZ.js.map} +1 -1
  109. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js → _server.ts.js-Dqs6d6ul.js} +2 -2
  110. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js.map → _server.ts.js-Dqs6d6ul.js.map} +1 -1
  111. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js → _server.ts.js-BHdIopR2.js} +2 -2
  112. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js.map → _server.ts.js-BHdIopR2.js.map} +1 -1
  113. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js → _server.ts.js-CFWfVYjg.js} +2 -2
  114. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js.map → _server.ts.js-CFWfVYjg.js.map} +1 -1
  115. package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js → hooks.server.js-BT84TVOj.js} +2 -2
  116. package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js.map → hooks.server.js-BT84TVOj.js.map} +1 -1
  117. package/build/server/chunks/{handler-rG16rikT.js → handler-ChRrsqKi.js} +5 -5
  118. package/build/server/chunks/{handler-rG16rikT.js.map → handler-ChRrsqKi.js.map} +1 -1
  119. package/build/server/chunks/{index.js-DYK_SfL5.js → index.js-1EfZPKuZ.js} +5 -5
  120. package/build/server/chunks/{index.js-DYK_SfL5.js.map → index.js-1EfZPKuZ.js.map} +1 -1
  121. package/build/server/chunks/{manifest.js-h2lDpEPs.js → manifest.js-DR0qLyIe.js} +27 -27
  122. package/build/server/chunks/{manifest.js-h2lDpEPs.js.map → manifest.js-DR0qLyIe.js.map} +1 -1
  123. package/build/server/chunks/nodes/{0.js-Db28RwnA.js → 0.js-CHb0mVEc.js} +2 -2
  124. package/build/server/chunks/nodes/{0.js-Db28RwnA.js.map → 0.js-CHb0mVEc.js.map} +1 -1
  125. package/build/server/chunks/nodes/1.js-DXh-Y7sw.js +7 -0
  126. package/build/server/chunks/nodes/{1.js-D26Uv3YT.js.map → 1.js-DXh-Y7sw.js.map} +1 -1
  127. package/build/server/chunks/nodes/2.js-BSEFm6uL.js +7 -0
  128. package/build/server/chunks/nodes/2.js-BSEFm6uL.js.map +1 -0
  129. package/build/server/chunks/nodes/3.js-Cob8rFik.js +7 -0
  130. package/build/server/chunks/nodes/3.js-Cob8rFik.js.map +1 -0
  131. package/build/server/chunks/nodes/4.js-CsI1onmR.js +7 -0
  132. package/build/server/chunks/nodes/4.js-CsI1onmR.js.map +1 -0
  133. package/dist/core/api-types.d.ts +2 -0
  134. package/dist/runtime/chroma-key-video-lifecycle.d.ts +2 -2
  135. package/dist/runtime/chroma-key-video-lifecycle.js +220 -124
  136. package/dist/runtime/components/ChromaKeyVideo.svelte +13 -2
  137. package/dist/runtime/components/Slice.svelte +22 -12
  138. package/dist/runtime/components/Slice.svelte.d.ts +3 -6
  139. package/dist/runtime/components/SliceCanvas.svelte +51 -40
  140. package/dist/runtime/components/SliceCanvas.svelte.d.ts +2 -2
  141. package/dist/runtime/components/SliceDom.svelte +96 -42
  142. package/dist/runtime/components/SliceDom.svelte.d.ts +2 -2
  143. package/dist/runtime/slice-source-lifecycle.d.ts +14 -0
  144. package/dist/runtime/slice-source-lifecycle.js +70 -0
  145. package/dist/server/check.d.ts +1 -1
  146. package/dist/server/check.js +16 -2
  147. package/dist/server/cli-commands.d.ts +1 -1
  148. package/dist/server/cli-commands.js +2 -1
  149. package/dist/server/image-duplicates.d.ts +18 -0
  150. package/dist/server/image-duplicates.js +138 -0
  151. package/dist/server/operations.js +4 -0
  152. package/package.json +4 -1
  153. package/build/client/_app/immutable/assets/2.6TBn1n3x.css +0 -1
  154. package/build/client/_app/immutable/assets/2.6TBn1n3x.css.br +0 -0
  155. package/build/client/_app/immutable/assets/2.6TBn1n3x.css.gz +0 -0
  156. package/build/client/_app/immutable/assets/3.BuCXJbRG.css.br +0 -0
  157. package/build/client/_app/immutable/assets/3.BuCXJbRG.css.gz +0 -0
  158. package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css +0 -1
  159. package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.br +0 -0
  160. package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.gz +0 -0
  161. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css +0 -1
  162. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.br +0 -0
  163. package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.gz +0 -0
  164. package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css +0 -1
  165. package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.br +0 -0
  166. package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.gz +0 -0
  167. package/build/client/_app/immutable/chunks/BAva9YXm.js +0 -1
  168. package/build/client/_app/immutable/chunks/BAva9YXm.js.br +0 -0
  169. package/build/client/_app/immutable/chunks/BAva9YXm.js.gz +0 -0
  170. package/build/client/_app/immutable/chunks/BGAUmugt.js +0 -1
  171. package/build/client/_app/immutable/chunks/BGAUmugt.js.br +0 -0
  172. package/build/client/_app/immutable/chunks/BGAUmugt.js.gz +0 -0
  173. package/build/client/_app/immutable/chunks/BMORe2Gb.js.br +0 -0
  174. package/build/client/_app/immutable/chunks/CBygkKLq.js +0 -1
  175. package/build/client/_app/immutable/chunks/CBygkKLq.js.br +0 -0
  176. package/build/client/_app/immutable/chunks/CBygkKLq.js.gz +0 -0
  177. package/build/client/_app/immutable/chunks/Cil0r9BR.js +0 -108
  178. package/build/client/_app/immutable/chunks/Cil0r9BR.js.br +0 -0
  179. package/build/client/_app/immutable/chunks/Cil0r9BR.js.gz +0 -0
  180. package/build/client/_app/immutable/chunks/D-p-QSzz.js +0 -1
  181. package/build/client/_app/immutable/chunks/D-p-QSzz.js.br +0 -0
  182. package/build/client/_app/immutable/chunks/D-p-QSzz.js.gz +0 -0
  183. package/build/client/_app/immutable/chunks/D15yxLq9.js +0 -1
  184. package/build/client/_app/immutable/chunks/D15yxLq9.js.br +0 -0
  185. package/build/client/_app/immutable/chunks/D15yxLq9.js.gz +0 -0
  186. package/build/client/_app/immutable/chunks/DFRlZOKz.js.br +0 -0
  187. package/build/client/_app/immutable/chunks/DFRlZOKz.js.gz +0 -0
  188. package/build/client/_app/immutable/chunks/DpFtnEPx.js.br +0 -4
  189. package/build/client/_app/immutable/chunks/DpFtnEPx.js.gz +0 -0
  190. package/build/client/_app/immutable/entry/app.BaP08B0_.js +0 -2
  191. package/build/client/_app/immutable/entry/app.BaP08B0_.js.br +0 -0
  192. package/build/client/_app/immutable/entry/app.BaP08B0_.js.gz +0 -0
  193. package/build/client/_app/immutable/entry/start.DntfygIK.js +0 -1
  194. package/build/client/_app/immutable/entry/start.DntfygIK.js.br +0 -0
  195. package/build/client/_app/immutable/entry/start.DntfygIK.js.gz +0 -0
  196. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js +0 -1
  197. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.br +0 -0
  198. package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.gz +0 -0
  199. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js +0 -1
  200. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.br +0 -0
  201. package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.gz +0 -0
  202. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js +0 -3
  203. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.br +0 -0
  204. package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.gz +0 -0
  205. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js +0 -1
  206. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.br +0 -0
  207. package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.gz +0 -0
  208. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js +0 -2
  209. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.br +0 -0
  210. package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.gz +0 -0
  211. package/build/server/chunks/chunks/context.js-BJfk3ZiR.js.map +0 -1
  212. package/build/server/chunks/nodes/1.js-D26Uv3YT.js +0 -7
  213. package/build/server/chunks/nodes/2.js-BeRlEFid.js +0 -7
  214. package/build/server/chunks/nodes/2.js-BeRlEFid.js.map +0 -1
  215. package/build/server/chunks/nodes/3.js-BRUNmCyG.js +0 -7
  216. package/build/server/chunks/nodes/3.js-BRUNmCyG.js.map +0 -1
  217. package/build/server/chunks/nodes/4.js-CTHWqDf1.js +0 -7
  218. package/build/server/chunks/nodes/4.js-CTHWqDf1.js.map +0 -1
@@ -13,25 +13,28 @@
13
13
  canvas is enlarged by the overdraw's destination size and offset negatively
14
14
  (see `computeCanvasPaintLayout`), so the baked shadow/glow paints outside
15
15
  the layout box while the wrapper (and therefore the layout) keeps the box
16
- size — exactly where the DOM backend positions its overdraw cells.
16
+ size — exactly where the DOM backend positions its border-image paint.
17
17
  -->
18
18
  <script lang="ts">
19
- import type { SliceManifestEntry } from '../../core/index.js';
20
- import { computeCanvasPaintLayout, computeRenderCells } from '../geometry.js';
19
+ import {
20
+ computeCanvasPaintLayout,
21
+ computeRenderCells,
22
+ type CanvasPaintLayout
23
+ } from '../geometry.js';
21
24
  import { observeBorderBoxSize } from '../observe-border-box-size.js';
25
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
22
26
 
23
27
  interface Props {
24
- src: SliceManifestEntry;
28
+ snapshot: LoadedSliceSnapshot | null;
25
29
  /** Accessible label for the rendered graphic. */
26
30
  alt?: string;
27
31
  class?: string;
28
32
  }
29
33
 
30
- let { src, alt = '', class: className = '' }: Props = $props();
34
+ let { snapshot, alt = '', class: className = '' }: Props = $props();
31
35
 
32
36
  let root = $state<HTMLDivElement>();
33
37
  let canvas = $state<HTMLCanvasElement>();
34
- let image = $state<HTMLImageElement>();
35
38
  let boxWidth = $state(0);
36
39
  let boxHeight = $state(0);
37
40
  let devicePixelRatio = $state(1);
@@ -39,23 +42,6 @@
39
42
  // box nor the entry changed (belt and braces alongside the ResizeObserver).
40
43
  let redrawTick = $state(0);
41
44
 
42
- // Load the source image once per entry `src` — the same plain URL the DOM
43
- // backend paints (slices are image-backed: the browser cache owns the bytes,
44
- // preload retains nothing to resolve).
45
- $effect(() => {
46
- const sourceUrl = src.src;
47
- const next = new Image();
48
- let cancelled = false;
49
- next.onload = () => {
50
- if (!cancelled) image = next;
51
- };
52
- next.src = sourceUrl;
53
- return () => {
54
- cancelled = true;
55
- next.onload = null;
56
- };
57
- });
58
-
59
45
  // Track the display's devicePixelRatio: a resolution media query fires
60
46
  // exactly when the DPR leaves the tracked value (browser zoom, moving the
61
47
  // window to another display). The tracked state must be READ first — a
@@ -83,40 +69,65 @@
83
69
  });
84
70
  });
85
71
 
86
- const layout = $derived(computeCanvasPaintLayout(src, boxWidth, boxHeight, devicePixelRatio));
72
+ const emptyLayout: CanvasPaintLayout = {
73
+ cssLeft: 0,
74
+ cssTop: 0,
75
+ cssWidth: 0,
76
+ cssHeight: 0,
77
+ backingWidth: 0,
78
+ backingHeight: 0,
79
+ originX: 0,
80
+ originY: 0
81
+ };
82
+ const layout = $derived.by(() => {
83
+ const current = snapshot;
84
+ return current
85
+ ? computeCanvasPaintLayout(current.entry, boxWidth, boxHeight, devicePixelRatio)
86
+ : emptyLayout;
87
+ });
87
88
 
88
- // Redraw when the box size, image, entry, DPR, or window changes.
89
+ // Redraw only when a committed snapshot, the box, DPR, or window changes.
89
90
  $effect(() => {
90
- void image;
91
- void src;
92
- void layout;
91
+ const current = snapshot;
92
+ const currentLayout = layout;
93
93
  void redrawTick;
94
- draw();
94
+ draw(current, currentLayout);
95
95
  });
96
96
 
97
- function draw(): void {
97
+ function draw(current: LoadedSliceSnapshot | null, currentLayout: CanvasPaintLayout): void {
98
98
  const element = canvas;
99
- const source = image;
100
- if (!element || !source) return;
101
- if (boxWidth === 0 || boxHeight === 0) {
99
+ if (!element) return;
100
+ if (!current || boxWidth === 0 || boxHeight === 0) {
102
101
  element.width = 0;
103
102
  element.height = 0;
104
103
  return;
105
104
  }
106
105
 
107
- element.width = layout.backingWidth;
108
- element.height = layout.backingHeight;
106
+ element.width = currentLayout.backingWidth;
107
+ element.height = currentLayout.backingHeight;
109
108
 
110
109
  const context = element.getContext('2d');
111
110
  if (!context) return;
112
111
  // Origin at the layout box's top-left, so the (possibly negative) cell
113
112
  // coordinates from computeRenderCells apply directly.
114
- context.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, layout.originX, layout.originY);
115
- context.clearRect(layout.cssLeft, layout.cssTop, layout.cssWidth, layout.cssHeight);
116
-
117
- for (const cell of computeRenderCells(src, boxWidth, boxHeight)) {
113
+ context.setTransform(
114
+ devicePixelRatio,
115
+ 0,
116
+ 0,
117
+ devicePixelRatio,
118
+ currentLayout.originX,
119
+ currentLayout.originY
120
+ );
121
+ context.clearRect(
122
+ currentLayout.cssLeft,
123
+ currentLayout.cssTop,
124
+ currentLayout.cssWidth,
125
+ currentLayout.cssHeight
126
+ );
127
+
128
+ for (const cell of computeRenderCells(current.entry, boxWidth, boxHeight)) {
118
129
  context.drawImage(
119
- source,
130
+ current.image,
120
131
  cell.sx,
121
132
  cell.sy,
122
133
  cell.sw,
@@ -1,6 +1,6 @@
1
- import type { SliceManifestEntry } from '../../core/index.js';
1
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
2
2
  interface Props {
3
- src: SliceManifestEntry;
3
+ snapshot: LoadedSliceSnapshot | null;
4
4
  /** Accessible label for the rendered graphic. */
5
5
  alt?: string;
6
6
  class?: string;
@@ -5,33 +5,96 @@
5
5
  in the entry. Private: hosts render `Slice.svelte`, which dispatches between
6
6
  this and the canvas backend.
7
7
 
8
- Fills its container and measures it (ResizeObserver), then paints one cell per
9
- slice region as an absolutely-positioned window onto the source image: a full
10
- copy of the image is scaled and offset (percentages relative to the cell) so
11
- the cell shows exactly its source region. Cells abut on integer pixels; corners
12
- / caps keep their aspect ratio (no distortion), the stretch bands fill the rest.
13
- With overdraw the cells extend past the container (negative offsets), so the
14
- root switches to overflow: visible and the baked shadow/glow draws outside the
15
- layout box.
8
+ Fills its container and measures it (ResizeObserver), then paints the complete
9
+ slice through one CSS border-image. The browser still stretches the same source
10
+ regions computed by `computeRenderCells`, but rasterizes them as one border
11
+ image instead of separate clipped elements. Shared slice edges therefore cannot
12
+ expose the background under fractional transforms.
13
+ With overdraw the paint element extends past the container (negative offsets),
14
+ so the root switches to overflow: visible and the baked shadow/glow draws
15
+ outside the layout box.
16
16
  -->
17
17
  <script lang="ts">
18
- import type { SliceManifestEntry } from '../../core/index.js';
19
- import { computeRenderCells, effectiveOverdraw } from '../geometry.js';
18
+ import type { SliceInsets, SliceManifestEntry } from '../../core/index.js';
19
+ import { computeRenderCells, effectiveInsets, effectiveOverdraw } from '../geometry.js';
20
20
  import { observeBorderBoxSize } from '../observe-border-box-size.js';
21
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
21
22
 
22
23
  interface Props {
23
- src: SliceManifestEntry;
24
+ snapshot: LoadedSliceSnapshot | null;
24
25
  /** Accessible label for the rendered graphic. */
25
26
  alt?: string;
26
27
  class?: string;
27
28
  }
28
29
 
29
- let { src, alt = '', class: className = '' }: Props = $props();
30
+ let { snapshot, alt = '', class: className = '' }: Props = $props();
30
31
 
31
32
  let root = $state<HTMLDivElement>();
32
33
  let boxWidth = $state(0);
33
34
  let boxHeight = $state(0);
34
35
 
36
+ interface BorderImagePaint {
37
+ left: number;
38
+ top: number;
39
+ width: number;
40
+ height: number;
41
+ slices: SliceInsets;
42
+ widths: SliceInsets;
43
+ }
44
+
45
+ function clamp(value: number, min: number, max: number): number {
46
+ return Math.max(min, Math.min(max, value));
47
+ }
48
+
49
+ function computeBorderImagePaint(
50
+ entry: SliceManifestEntry,
51
+ width: number,
52
+ height: number
53
+ ): BorderImagePaint | null {
54
+ const cells = computeRenderCells(entry, width, height);
55
+ if (cells.length === 0) return null;
56
+
57
+ const effective = effectiveInsets(entry);
58
+ const leftSlice = clamp(effective.left, 0, entry.sourceWidth);
59
+ const rightSlice = clamp(effective.right, 0, entry.sourceWidth - leftSlice);
60
+ const topSlice = clamp(effective.top, 0, entry.sourceHeight);
61
+ const bottomSlice = clamp(effective.bottom, 0, entry.sourceHeight - topSlice);
62
+ const slices = {
63
+ top: topSlice,
64
+ right: rightSlice,
65
+ bottom: bottomSlice,
66
+ left: leftSlice
67
+ };
68
+ const left = Math.min(...cells.map((cell) => cell.dx));
69
+ const top = Math.min(...cells.map((cell) => cell.dy));
70
+ const right = Math.max(...cells.map((cell) => cell.dx + cell.dw));
71
+ const bottom = Math.max(...cells.map((cell) => cell.dy + cell.dh));
72
+
73
+ return {
74
+ left,
75
+ top,
76
+ width: right - left,
77
+ height: bottom - top,
78
+ slices,
79
+ widths: {
80
+ top: cells.find((cell) => topSlice > 0 && cell.sy === 0 && cell.sh === topSlice)?.dh ?? 0,
81
+ right:
82
+ cells.find(
83
+ (cell) =>
84
+ rightSlice > 0 && cell.sx === entry.sourceWidth - rightSlice && cell.sw === rightSlice
85
+ )?.dw ?? 0,
86
+ bottom:
87
+ cells.find(
88
+ (cell) =>
89
+ bottomSlice > 0 &&
90
+ cell.sy === entry.sourceHeight - bottomSlice &&
91
+ cell.sh === bottomSlice
92
+ )?.dh ?? 0,
93
+ left: cells.find((cell) => leftSlice > 0 && cell.sx === 0 && cell.sw === leftSlice)?.dw ?? 0
94
+ }
95
+ };
96
+ }
97
+
35
98
  $effect(() => {
36
99
  const element = root;
37
100
  if (!element) return;
@@ -41,9 +104,11 @@
41
104
  });
42
105
  });
43
106
 
44
- const cells = $derived(computeRenderCells(src, boxWidth, boxHeight));
107
+ const entry = $derived(snapshot?.entry ?? null);
108
+ const paint = $derived(entry ? computeBorderImagePaint(entry, boxWidth, boxHeight) : null);
45
109
  const hasOverdraw = $derived.by(() => {
46
- const overdraw = effectiveOverdraw(src);
110
+ if (!entry) return false;
111
+ const overdraw = effectiveOverdraw(entry);
47
112
  return overdraw.top > 0 || overdraw.right > 0 || overdraw.bottom > 0 || overdraw.left > 0;
48
113
  });
49
114
  </script>
@@ -55,25 +120,18 @@
55
120
  role={alt ? 'img' : undefined}
56
121
  aria-label={alt || undefined}
57
122
  >
58
- {#each cells as cell (cell.key)}
123
+ {#if entry && paint}
59
124
  <div
60
- class="fundus-slice__cell"
61
- style:left="{cell.dx}px"
62
- style:top="{cell.dy}px"
63
- style:width="{cell.dw}px"
64
- style:height="{cell.dh}px"
65
- >
66
- <img
67
- src={src.src}
68
- alt=""
69
- draggable="false"
70
- style:width="{(src.sourceWidth / cell.sw) * 100}%"
71
- style:height="{(src.sourceHeight / cell.sh) * 100}%"
72
- style:left="{(-cell.sx / cell.sw) * 100}%"
73
- style:top="{(-cell.sy / cell.sh) * 100}%"
74
- />
75
- </div>
76
- {/each}
125
+ class="fundus-slice__paint"
126
+ style:left="{paint.left}px"
127
+ style:top="{paint.top}px"
128
+ style:width="{paint.width}px"
129
+ style:height="{paint.height}px"
130
+ style:border-image-source={`url(${JSON.stringify(entry.src)})`}
131
+ style:border-image-slice={`${paint.slices.top} ${paint.slices.right} ${paint.slices.bottom} ${paint.slices.left} fill`}
132
+ style:border-image-width={`${paint.widths.top}px ${paint.widths.right}px ${paint.widths.bottom}px ${paint.widths.left}px`}
133
+ ></div>
134
+ {/if}
77
135
  </div>
78
136
 
79
137
  <style>
@@ -84,21 +142,17 @@
84
142
  overflow: hidden;
85
143
  }
86
144
 
87
- /* Overdraw cells deliberately extend past the box — don't clip them. */
145
+ /* Overdraw deliberately extends the paint past the box — don't clip it. */
88
146
  .fundus-slice--overdraw {
89
147
  overflow: visible;
90
148
  }
91
149
 
92
- .fundus-slice__cell {
93
- position: absolute;
94
- overflow: hidden;
95
- }
96
-
97
- .fundus-slice__cell img {
150
+ .fundus-slice__paint {
98
151
  position: absolute;
99
- max-width: none;
100
- max-height: none;
152
+ box-sizing: border-box;
153
+ border-width: 0;
154
+ border-style: solid;
155
+ border-image-repeat: stretch;
101
156
  pointer-events: none;
102
- user-select: none;
103
157
  }
104
158
  </style>
@@ -1,6 +1,6 @@
1
- import type { SliceManifestEntry } from '../../core/index.js';
1
+ import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
2
2
  interface Props {
3
- src: SliceManifestEntry;
3
+ snapshot: LoadedSliceSnapshot | null;
4
4
  /** Accessible label for the rendered graphic. */
5
5
  alt?: string;
6
6
  class?: string;
@@ -0,0 +1,14 @@
1
+ import type { SliceManifestEntry } from '../core/index.js';
2
+ export interface LoadedSliceSnapshot {
3
+ entry: SliceManifestEntry;
4
+ image: HTMLImageElement;
5
+ }
6
+ export type SliceImageLoader = (url: string) => Promise<HTMLImageElement>;
7
+ export declare function loadSliceImage(url: string): Promise<HTMLImageElement>;
8
+ /** Coordinates requested slice entries with the last image-ready snapshot. */
9
+ export declare class SliceSourceLifecycle {
10
+ #private;
11
+ constructor(commit: (snapshot: LoadedSliceSnapshot) => void, loadImage?: SliceImageLoader);
12
+ request(entry: SliceManifestEntry): void;
13
+ dispose(): void;
14
+ }
@@ -0,0 +1,70 @@
1
+ export function loadSliceImage(url) {
2
+ const image = new Image();
3
+ image.decoding = 'async';
4
+ image.src = url;
5
+ return image.decode().then(() => image);
6
+ }
7
+ /** Coordinates requested slice entries with the last image-ready snapshot. */
8
+ export class SliceSourceLifecycle {
9
+ #loadImage;
10
+ #commit;
11
+ #requestedEntry = null;
12
+ #displayedSnapshot = null;
13
+ #inFlight = new Map();
14
+ #disposed = false;
15
+ constructor(commit, loadImage = loadSliceImage) {
16
+ this.#commit = commit;
17
+ this.#loadImage = loadImage;
18
+ }
19
+ request(entry) {
20
+ if (this.#disposed)
21
+ return;
22
+ this.#requestedEntry = entry;
23
+ const displayed = this.#displayedSnapshot;
24
+ if (displayed?.entry.src === entry.src) {
25
+ this.#commitSnapshot({ entry, image: displayed.image });
26
+ return;
27
+ }
28
+ if (this.#inFlight.has(entry.src))
29
+ return;
30
+ let promise;
31
+ try {
32
+ promise = Promise.resolve(this.#loadImage(entry.src));
33
+ }
34
+ catch (error) {
35
+ promise = Promise.reject(error);
36
+ }
37
+ const load = { promise };
38
+ this.#inFlight.set(entry.src, load);
39
+ void promise.then((image) => this.#settleSuccess(entry.src, load, image), () => this.#settleFailure(entry.src, load));
40
+ }
41
+ dispose() {
42
+ if (this.#disposed)
43
+ return;
44
+ this.#disposed = true;
45
+ this.#requestedEntry = null;
46
+ this.#displayedSnapshot = null;
47
+ this.#inFlight.clear();
48
+ }
49
+ #settleSuccess(url, load, image) {
50
+ if (this.#inFlight.get(url) !== load)
51
+ return;
52
+ this.#inFlight.delete(url);
53
+ if (this.#disposed)
54
+ return;
55
+ const requested = this.#requestedEntry;
56
+ if (!requested || requested.src !== url)
57
+ return;
58
+ this.#commitSnapshot({ entry: requested, image });
59
+ }
60
+ #settleFailure(url, load) {
61
+ if (this.#inFlight.get(url) === load)
62
+ this.#inFlight.delete(url);
63
+ }
64
+ #commitSnapshot(snapshot) {
65
+ if (this.#disposed)
66
+ return;
67
+ this.#displayedSnapshot = snapshot;
68
+ this.#commit(snapshot);
69
+ }
70
+ }
@@ -4,7 +4,7 @@ import type { ServerPluginRegistry } from './plugins/contract.js';
4
4
  /** One machine-readable check finding (the `--json` failure-list shape, M9). */
5
5
  export interface CheckFinding {
6
6
  /** The failure category. */
7
- kind: 'validation' | 'proxy-missing' | 'proxy-unresolvable' | 'proxy-orphaned' | 'module-missing' | 'module-out-of-sync' | 'module-stray' | 'module-regeneration-failed' | 'unreachable-asset' | 'raw-hash-drift' | 'raw-type-incompatible' | 'raw-scan-incomplete' | 'raw-unmanaged';
7
+ kind: 'validation' | 'proxy-missing' | 'proxy-unresolvable' | 'proxy-orphaned' | 'module-missing' | 'module-out-of-sync' | 'module-stray' | 'module-regeneration-failed' | 'unreachable-asset' | 'potential-image-duplicate' | 'raw-hash-drift' | 'raw-type-incompatible' | 'raw-scan-incomplete' | 'raw-unmanaged';
8
8
  message: string;
9
9
  /** The asset the finding concerns, where applicable. */
10
10
  assetId?: string;
@@ -2,7 +2,8 @@
2
2
  * `fundus check` — the verify-only CI gate: no writes, ever. It
3
3
  * fails when the library does not validate, when a committed proxy is stale,
4
4
  * missing, or orphaned, or when a generated manifest module differs from an
5
- * in-memory regeneration. Warnings (unreachable assets) are
5
+ * in-memory regeneration. Warnings (unreachable assets and potential image
6
+ * duplicates) are
6
7
  * treated as errors unless `--allow-warnings` downgrades them — errors are
7
8
  * never downgradable.
8
9
  */
@@ -17,6 +18,7 @@ import { buildManifestEntries, manifestModuleFileName, renderManifestModule, str
17
18
  import { descriptorsByType, serverPluginsByType } from './plugins.js';
18
19
  import { deliveredAssetIds, expectedProxyRelativePath, isProxyFileName, readCurrentProxy } from './processing/processor.js';
19
20
  import { listRelativeFiles } from './folder-filesystem.js';
21
+ import { ImageDuplicateDetector } from './image-duplicates.js';
20
22
  /** The human rendering of a finding — `[assetId] message`, as `fundus check` prints it. */
21
23
  export function formatCheckFinding(finding) {
22
24
  return `${finding.assetId ? `[${finding.assetId}] ` : ''}${finding.message}`;
@@ -109,12 +111,24 @@ export async function runCheck(projectRoot, registry = serverPluginsByType) {
109
111
  await checkGeneratedModules(report, library, config, proxies, registry);
110
112
  await checkStrayModules(report, library, config);
111
113
  }
112
- // 4. Warnings: unreachable assets only. Raw drift is always a hard CI failure.
114
+ // 4. Advisory warnings. Raw drift is always a hard CI failure.
113
115
  collectUnreachableAssets(report, library, delivered);
116
+ collectPotentialImageDuplicates(report, await new ImageDuplicateDetector().find(library, config.rawDir));
114
117
  report.errors.sort((left, right) => compareCodeUnits(`${left.fileName ?? ''}|${left.assetId ?? ''}|${left.kind}|${left.message}`, `${right.fileName ?? ''}|${right.assetId ?? ''}|${right.kind}|${right.message}`));
115
118
  report.warnings.sort((left, right) => compareCodeUnits(`${left.assetId ?? ''}|${left.kind}|${left.message}`, `${right.assetId ?? ''}|${right.kind}|${right.message}`));
116
119
  return report;
117
120
  }
121
+ function collectPotentialImageDuplicates(report, duplicatesByAsset) {
122
+ for (const [assetId, duplicateIds] of duplicatesByAsset) {
123
+ report.warnings.push({
124
+ kind: 'potential-image-duplicate',
125
+ message: duplicateIds.length === 1
126
+ ? `Potential image duplicate of "${duplicateIds[0]}".`
127
+ : `Potential image duplicates: ${duplicateIds.map((id) => `"${id}"`).join(', ')}.`,
128
+ assetId
129
+ });
130
+ }
131
+ }
118
132
  /** Pure expansion of a raw classification into canonical hard check findings. */
119
133
  export function rawCheckFindings(library, raw, registry, validationIssues) {
120
134
  const findings = [];
@@ -25,5 +25,5 @@ export declare const MANIFEST_PALETTE_HELP = "fundus manifest palette \u2014 lis
25
25
  export declare function runManifestCommand(argumentList: string[]): Promise<void>;
26
26
  export declare const BUILD_HELP = "fundus build \u2014 run the headless pipeline\n\nUsage: fundus build [--json]\n\nValidates the library, processes every stale or missing proxy, prunes\norphaned proxies, and regenerates all manifest modules. Deterministic\nand idempotent: a second run on an unchanged project processes nothing\nand rewrites byte-identical modules. Run it in CI, postinstall, or\nbefore the host build; commit the result.\n\nFlags:\n --json print {\"ok\": true, \"processed\": [\u2026], \"upToDate\": [\u2026],\n \"prunedProxies\": [\u2026], \"modulesWritten\": [\u2026],\n \"modulesRemoved\": [\u2026]} \u2014 asset ids for processing,\n file names for pruning/codegen\n --help this help\n\nA validation failure processes nothing and exits 1 (with --json:\n{\"error\": {\"message\": \u2026}}).\n\nExample:\n fundus build --json | jq '.processed'\n";
27
27
  export declare function runBuildCommand(argumentList: string[]): Promise<void>;
28
- export declare const CHECK_HELP = "fundus check \u2014 verify the committed state (the CI gate)\n\nUsage: fundus check [--allow-warnings] [--json]\n\nVerify-only, no writes: the library must validate, every delivered\nasset's committed proxy must exist under its expected name (staleness\nis a name check), the proxy directory must hold no orphans, and every\ngenerated manifest module must be byte-identical to an in-memory\nregeneration. Warnings (such as unreachable assets) fail the check\nunless --allow-warnings downgrades them. New, missing, edited, or\ninvalid entries in the raw directory are hard errors:\nopen or refocus the Fundus editor and resolve its ingestion queue.\nRaw-file errors never downgrade.\n\nFlags:\n --allow-warnings exit 0 despite warnings (errors still fail)\n --json print one report document:\n {\"ok\": <pass/fail>, \"assetCount\": \u2026, \"manifestCount\": \u2026,\n \"errors\": [<finding>, \u2026], \"warnings\": [<finding>, \u2026],\n \"advisories\": [<non-fatal integration guidance>, \u2026]}\n where a finding is {\"kind\", \"message\"} plus\n \"assetId\" / \"manifest\" / \"fileName\" where applicable\n --help this help\n\nExit codes: 0 = clean (or warnings allowed), 1 = failed. A failure to\neven run the check (e.g. no fundus.config.ts) exits 1 with the usual\n{\"error\": {\"message\": \u2026}} envelope under --json.\n\nExample:\n fundus check --json | jq '.errors[].kind'\n";
28
+ export declare const CHECK_HELP = "fundus check \u2014 verify the committed state (the CI gate)\n\nUsage: fundus check [--allow-warnings] [--json]\n\nVerify-only, no writes: the library must validate, every delivered\nasset's committed proxy must exist under its expected name (staleness\nis a name check), the proxy directory must hold no orphans, and every\ngenerated manifest module must be byte-identical to an in-memory\nregeneration. Warnings (such as unreachable assets or potential image\nduplicates) fail the check\nunless --allow-warnings downgrades them. New, missing, edited, or\ninvalid entries in the raw directory are hard errors:\nopen or refocus the Fundus editor and resolve its ingestion queue.\nRaw-file errors never downgrade.\n\nFlags:\n --allow-warnings exit 0 despite warnings (errors still fail)\n --json print one report document:\n {\"ok\": <pass/fail>, \"assetCount\": \u2026, \"manifestCount\": \u2026,\n \"errors\": [<finding>, \u2026], \"warnings\": [<finding>, \u2026],\n \"advisories\": [<non-fatal integration guidance>, \u2026]}\n where a finding is {\"kind\", \"message\"} plus\n \"assetId\" / \"manifest\" / \"fileName\" where applicable\n --help this help\n\nExit codes: 0 = clean (or warnings allowed), 1 = failed. A failure to\neven run the check (e.g. no fundus.config.ts) exits 1 with the usual\n{\"error\": {\"message\": \u2026}} envelope under --json.\n\nExample:\n fundus check --json | jq '.errors[].kind'\n";
29
29
  export declare function runCheckCommand(argumentList: string[]): Promise<void>;
@@ -1502,7 +1502,8 @@ Verify-only, no writes: the library must validate, every delivered
1502
1502
  asset's committed proxy must exist under its expected name (staleness
1503
1503
  is a name check), the proxy directory must hold no orphans, and every
1504
1504
  generated manifest module must be byte-identical to an in-memory
1505
- regeneration. Warnings (such as unreachable assets) fail the check
1505
+ regeneration. Warnings (such as unreachable assets or potential image
1506
+ duplicates) fail the check
1506
1507
  unless --allow-warnings downgrades them. New, missing, edited, or
1507
1508
  invalid entries in the raw directory are hard errors:
1508
1509
  open or refocus the Fundus editor and resolve its ingestion queue.
@@ -0,0 +1,18 @@
1
+ import { type Library } from '../core/index.js';
2
+ export declare const IMAGE_DUPLICATE_HASH_SIZE = 8;
3
+ export declare const IMAGE_DUPLICATE_MAX_DISTANCE = 5;
4
+ export declare const IMAGE_DUPLICATE_MAX_COLOR_DISTANCE = 32;
5
+ export declare const IMAGE_DUPLICATE_MAX_ALPHA_DISTANCE = 0.125;
6
+ /** Compute the 64-bit horizontal difference hash described by the dHash algorithm. */
7
+ export declare function imageDifferenceHash(filePath: string, hashSize?: number): Promise<Uint8Array>;
8
+ /** Count differing bits between two equally sized perceptual hashes. */
9
+ export declare function hammingDistance(left: Uint8Array, right: Uint8Array): number;
10
+ /**
11
+ * Stateful so the editor can reuse fingerprints by content hash between state
12
+ * snapshots. Unreadable raws are omitted: duplicate detection is advisory and
13
+ * must not hide the editor or replace the pipeline's canonical diagnostics.
14
+ */
15
+ export declare class ImageDuplicateDetector {
16
+ #private;
17
+ find(library: Library, rawDir: string): Promise<Map<string, string[]>>;
18
+ }