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.
- package/README.md +7 -5
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css +1 -0
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css.br +0 -0
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css.gz +0 -0
- package/build/client/_app/immutable/assets/{3.BuCXJbRG.css → 3.D6PF4wEW.css} +1 -1
- package/build/client/_app/immutable/assets/3.D6PF4wEW.css.br +0 -0
- package/build/client/_app/immutable/assets/3.D6PF4wEW.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css +1 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.gz +0 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css +1 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.br +0 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.gz +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js +1 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.br +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js +1 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js +1 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DFRlZOKz.js → BufZeTod.js} +2 -2
- package/build/client/_app/immutable/chunks/BufZeTod.js.br +0 -0
- package/build/client/_app/immutable/chunks/BufZeTod.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js +1 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DpFtnEPx.js → CjB5zQ4_.js} +1 -1
- package/build/client/_app/immutable/chunks/CjB5zQ4_.js.br +0 -0
- package/build/client/_app/immutable/chunks/CjB5zQ4_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js +108 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js.br +0 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BMORe2Gb.js → YpiA5Xry.js} +1 -1
- package/build/client/_app/immutable/chunks/YpiA5Xry.js.br +0 -0
- package/build/client/_app/immutable/chunks/{BMORe2Gb.js.gz → YpiA5Xry.js.gz} +0 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js +2 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js +1 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.br +0 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js +1 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js +1 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js +3 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js +1 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js +2 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-BJfk3ZiR.js → context.js-BNdRFspW.js} +100 -1
- package/build/server/chunks/chunks/context.js-BNdRFspW.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js → exports.js-CUFgHhPL.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js.map → exports.js-CUFgHhPL.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js → internal.js-DUquB_BJ.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js.map → internal.js-DUquB_BJ.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js → shared.js-BXL1A-vX.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js.map → shared.js-BXL1A-vX.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-6HSqnONB.js → utils.js-xC62cOm2.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-6HSqnONB.js.map → utils.js-xC62cOm2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js → _server.ts.js-DEfBMmVd.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js.map → _server.ts.js-DEfBMmVd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js → _server.ts.js-BEJcx3ya.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js.map → _server.ts.js-BEJcx3ya.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-B2JoGCzy.js → _server.ts.js-CibkO-4G.js} +2 -2
- 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
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js → _server.ts.js-BdisCQk6.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js.map → _server.ts.js-BdisCQk6.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js → _server.ts.js-Bk2hojus.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js.map → _server.ts.js-Bk2hojus.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js → _server.ts.js-CTeQlqUL.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js.map → _server.ts.js-CTeQlqUL.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js → _server.ts.js-DLxdefrX.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js.map → _server.ts.js-DLxdefrX.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js → _server.ts.js-Ceq1tMTz.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js.map → _server.ts.js-Ceq1tMTz.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js → _server.ts.js-DxAWqf9V.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js.map → _server.ts.js-DxAWqf9V.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js → _server.ts.js-BB_6WywB.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js.map → _server.ts.js-BB_6WywB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js → _server.ts.js-BLRkduAp.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js.map → _server.ts.js-BLRkduAp.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-C-NuN3JJ.js → _server.ts.js-Ba94-p0V.js} +2 -2
- 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
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js → _server.ts.js-CNhj0R4-.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js.map → _server.ts.js-CNhj0R4-.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js → _server.ts.js-DWEV8mgx.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js.map → _server.ts.js-DWEV8mgx.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js → _server.ts.js-BnDnZdPy.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js.map → _server.ts.js-BnDnZdPy.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js → _server.ts.js-1y2Lu6HB.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js.map → _server.ts.js-1y2Lu6HB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js → _server.ts.js-CnLJo2iZ.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js.map → _server.ts.js-CnLJo2iZ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js → _server.ts.js-Dqs6d6ul.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js.map → _server.ts.js-Dqs6d6ul.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js → _server.ts.js-BHdIopR2.js} +2 -2
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js.map → _server.ts.js-BHdIopR2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js → _server.ts.js-CFWfVYjg.js} +2 -2
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js.map → _server.ts.js-CFWfVYjg.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js → hooks.server.js-BT84TVOj.js} +2 -2
- package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js.map → hooks.server.js-BT84TVOj.js.map} +1 -1
- package/build/server/chunks/{handler-rG16rikT.js → handler-ChRrsqKi.js} +5 -5
- package/build/server/chunks/{handler-rG16rikT.js.map → handler-ChRrsqKi.js.map} +1 -1
- package/build/server/chunks/{index.js-DYK_SfL5.js → index.js-1EfZPKuZ.js} +5 -5
- package/build/server/chunks/{index.js-DYK_SfL5.js.map → index.js-1EfZPKuZ.js.map} +1 -1
- package/build/server/chunks/{manifest.js-h2lDpEPs.js → manifest.js-DR0qLyIe.js} +27 -27
- package/build/server/chunks/{manifest.js-h2lDpEPs.js.map → manifest.js-DR0qLyIe.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-Db28RwnA.js → 0.js-CHb0mVEc.js} +2 -2
- package/build/server/chunks/nodes/{0.js-Db28RwnA.js.map → 0.js-CHb0mVEc.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-DXh-Y7sw.js +7 -0
- package/build/server/chunks/nodes/{1.js-D26Uv3YT.js.map → 1.js-DXh-Y7sw.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-BSEFm6uL.js +7 -0
- package/build/server/chunks/nodes/2.js-BSEFm6uL.js.map +1 -0
- package/build/server/chunks/nodes/3.js-Cob8rFik.js +7 -0
- package/build/server/chunks/nodes/3.js-Cob8rFik.js.map +1 -0
- package/build/server/chunks/nodes/4.js-CsI1onmR.js +7 -0
- package/build/server/chunks/nodes/4.js-CsI1onmR.js.map +1 -0
- package/dist/core/api-types.d.ts +2 -0
- package/dist/runtime/chroma-key-video-lifecycle.d.ts +2 -2
- package/dist/runtime/chroma-key-video-lifecycle.js +220 -124
- package/dist/runtime/components/ChromaKeyVideo.svelte +13 -2
- package/dist/runtime/components/Slice.svelte +22 -12
- package/dist/runtime/components/Slice.svelte.d.ts +3 -6
- package/dist/runtime/components/SliceCanvas.svelte +51 -40
- package/dist/runtime/components/SliceCanvas.svelte.d.ts +2 -2
- package/dist/runtime/components/SliceDom.svelte +96 -42
- package/dist/runtime/components/SliceDom.svelte.d.ts +2 -2
- package/dist/runtime/slice-source-lifecycle.d.ts +14 -0
- package/dist/runtime/slice-source-lifecycle.js +70 -0
- package/dist/server/check.d.ts +1 -1
- package/dist/server/check.js +16 -2
- package/dist/server/cli-commands.d.ts +1 -1
- package/dist/server/cli-commands.js +2 -1
- package/dist/server/image-duplicates.d.ts +18 -0
- package/dist/server/image-duplicates.js +138 -0
- package/dist/server/operations.js +4 -0
- package/package.json +4 -1
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css +0 -1
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css.br +0 -0
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.BuCXJbRG.css.br +0 -0
- package/build/client/_app/immutable/assets/3.BuCXJbRG.css.gz +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css +0 -1
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.br +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css +0 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.gz +0 -0
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css +0 -1
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.br +0 -0
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BAva9YXm.js +0 -1
- package/build/client/_app/immutable/chunks/BAva9YXm.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAva9YXm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BGAUmugt.js +0 -1
- package/build/client/_app/immutable/chunks/BGAUmugt.js.br +0 -0
- package/build/client/_app/immutable/chunks/BGAUmugt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BMORe2Gb.js.br +0 -0
- package/build/client/_app/immutable/chunks/CBygkKLq.js +0 -1
- package/build/client/_app/immutable/chunks/CBygkKLq.js.br +0 -0
- package/build/client/_app/immutable/chunks/CBygkKLq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cil0r9BR.js +0 -108
- package/build/client/_app/immutable/chunks/Cil0r9BR.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cil0r9BR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-p-QSzz.js +0 -1
- package/build/client/_app/immutable/chunks/D-p-QSzz.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-p-QSzz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D15yxLq9.js +0 -1
- package/build/client/_app/immutable/chunks/D15yxLq9.js.br +0 -0
- package/build/client/_app/immutable/chunks/D15yxLq9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DFRlZOKz.js.br +0 -0
- package/build/client/_app/immutable/chunks/DFRlZOKz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DpFtnEPx.js.br +0 -4
- package/build/client/_app/immutable/chunks/DpFtnEPx.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.BaP08B0_.js +0 -2
- package/build/client/_app/immutable/entry/app.BaP08B0_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BaP08B0_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DntfygIK.js +0 -1
- package/build/client/_app/immutable/entry/start.DntfygIK.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DntfygIK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js +0 -1
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js +0 -1
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js +0 -3
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js +0 -1
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js +0 -2
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-BJfk3ZiR.js.map +0 -1
- package/build/server/chunks/nodes/1.js-D26Uv3YT.js +0 -7
- package/build/server/chunks/nodes/2.js-BeRlEFid.js +0 -7
- package/build/server/chunks/nodes/2.js-BeRlEFid.js.map +0 -1
- package/build/server/chunks/nodes/3.js-BRUNmCyG.js +0 -7
- package/build/server/chunks/nodes/3.js-BRUNmCyG.js.map +0 -1
- package/build/server/chunks/nodes/4.js-CTHWqDf1.js +0 -7
- 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
|
|
16
|
+
size — exactly where the DOM backend positions its border-image paint.
|
|
17
17
|
-->
|
|
18
18
|
<script lang="ts">
|
|
19
|
-
import
|
|
20
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
89
|
+
// Redraw only when a committed snapshot, the box, DPR, or window changes.
|
|
89
90
|
$effect(() => {
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
100
|
-
if (!
|
|
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 =
|
|
108
|
-
element.height =
|
|
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(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
130
|
+
current.image,
|
|
120
131
|
cell.sx,
|
|
121
132
|
cell.sy,
|
|
122
133
|
cell.sw,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
|
|
2
2
|
interface Props {
|
|
3
|
-
|
|
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
|
|
9
|
-
slice
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
With overdraw the
|
|
14
|
-
root switches to overflow: visible and the baked shadow/glow draws
|
|
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
|
-
|
|
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 {
|
|
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
|
|
107
|
+
const entry = $derived(snapshot?.entry ?? null);
|
|
108
|
+
const paint = $derived(entry ? computeBorderImagePaint(entry, boxWidth, boxHeight) : null);
|
|
45
109
|
const hasOverdraw = $derived.by(() => {
|
|
46
|
-
|
|
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
|
-
{#
|
|
123
|
+
{#if entry && paint}
|
|
59
124
|
<div
|
|
60
|
-
class="fundus-
|
|
61
|
-
style:left="{
|
|
62
|
-
style:top="{
|
|
63
|
-
style:width="{
|
|
64
|
-
style:height="{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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-
|
|
93
|
-
position: absolute;
|
|
94
|
-
overflow: hidden;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.fundus-slice__cell img {
|
|
150
|
+
.fundus-slice__paint {
|
|
98
151
|
position: absolute;
|
|
99
|
-
|
|
100
|
-
|
|
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 {
|
|
1
|
+
import type { LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
|
|
2
2
|
interface Props {
|
|
3
|
-
|
|
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
|
+
}
|
package/dist/server/check.d.ts
CHANGED
|
@@ -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;
|
package/dist/server/check.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
+
}
|