diff --git a/bun.lock b/bun.lock
index 104e57f..c598666 100644
--- a/bun.lock
+++ b/bun.lock
@@ -215,11 +215,11 @@
"@effect/experimental": ["@effect/experimental@0.41.4", "", { "dependencies": { "msgpackr": "^1.10.2", "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.77.4", "@effect/platform-node": "^0.73.4", "effect": "^3.13.4", "ioredis": "^5", "lmdb": "^3", "ws": "^8" }, "optionalPeers": ["@effect/platform-node", "ioredis", "lmdb", "ws"] }, "sha512-celrEhl/K2Eis906d3/oOv/3/w+M1jcONvrChG5qQgWYo6CJjSJm4xXopcTbiBhxAhvkd06zABkB3tr/lptUCA=="],
- "@effect/platform": ["@effect/platform@0.78.0", "", { "dependencies": { "find-my-way-ts": "^0.1.5", "multipasta": "^0.2.5" }, "peerDependencies": { "effect": "^3.13.7" } }, "sha512-2vGIfBtPO7zPioVy4wmSbeF0VWfj3GLpB9+6JT7K0lZVeYogHF2GTuPDB87xMILw86Ecz9iR98JuA9Co7PgxKQ=="],
+ "@effect/platform": ["@effect/platform@0.79.0", "", { "dependencies": { "find-my-way-ts": "^0.1.5", "multipasta": "^0.2.5" }, "peerDependencies": { "effect": "^3.13.9" } }, "sha512-xqnHRsCIvCsGuEIL9Ob4FZsMUU1ziIjZs+2owOGHt3Y3pseBho2A7pIMfUhOPwRrZRRV4/Jp4xpY7Mz5xbQOsw=="],
"@effect/sql": ["@effect/sql@0.30.4", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.25.1", "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.41.4", "@effect/platform": "^0.77.4", "effect": "^3.13.4" } }, "sha512-hyGOZsNRlw09yqBcXCTr1o2+vVIVxEnBMNZzi0ZX5JbS8JBT05O7xAu3OYEzSU6bC5i+uVpJJUgdx2zDlOLkeA=="],
- "@effect/sql-drizzle": ["@effect/sql-drizzle@0.30.0", "", { "peerDependencies": { "@effect/sql": "^0.31.0", "drizzle-orm": "^0.31", "effect": "^3.13.7" } }, "sha512-99qHAHQe6f5d8xeIknen4b+QZroEdjem3tVIfbsbpMsgOnowyomxjFm0Op8PFd1IegGxufC1C7KwGcfaty+l7g=="],
+ "@effect/sql-drizzle": ["@effect/sql-drizzle@0.31.0", "", { "peerDependencies": { "@effect/sql": "^0.32.0", "drizzle-orm": "^0.31", "effect": "^3.13.9" } }, "sha512-PN7BJIX6d/qeBFkXLBJfNjMar2s46AFbzihAtJ7biFDocamq++9SYvpVfUr01go+2MVZ/7CD7kHfOMm1o+YHrg=="],
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
@@ -373,57 +373,57 @@
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
- "@thi.ng/api": ["@thi.ng/api@8.11.21", "", {}, "sha512-J6BUdUtFtwZirL3M9tkCiqBXj228z7zkxWOaDWTymwBeqY9s02vJP3mQV8l5p+YPDIRmYx/q7XVuLW1UTJRN/A=="],
+ "@thi.ng/api": ["@thi.ng/api@8.11.22", "", {}, "sha512-SbgnYcuyIKvJF+bdpFNZJxggYa6wBt7xh4OjWZwvmbZvibrjvk4xwLaJ8vm/bJoBYK/Oe631CApssa6kumZeNg=="],
- "@thi.ng/arrays": ["@thi.ng/arrays@2.10.18", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/checks": "^3.7.1", "@thi.ng/compare": "^2.4.13", "@thi.ng/equiv": "^2.1.77", "@thi.ng/errors": "^2.5.27", "@thi.ng/random": "^4.1.12" } }, "sha512-4cJCVm67MqtrnwOWNdgeyM/WYAfEYeFtCh1x/3VptV6Ct8DSYyVbSK6RLYgsM04KQqAxEQniCl5/S+ZS3GFd+Q=="],
+ "@thi.ng/arrays": ["@thi.ng/arrays@2.10.19", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/checks": "^3.7.2", "@thi.ng/compare": "^2.4.14", "@thi.ng/equiv": "^2.1.78", "@thi.ng/errors": "^2.5.28", "@thi.ng/random": "^4.1.13" } }, "sha512-wC1Jkvej8zDfSXZmIGp6qMg452mjt4BExMWSaVqaLbMKYszGLgMHJAa8PTUfHrK5A2wQXn35uZwpVbWd+jeubA=="],
- "@thi.ng/base-n": ["@thi.ng/base-n@2.7.33", "", {}, "sha512-WAVoTt1ZIYpDUs/+FauMh0p25MsG2BK0twNmJKWqAcPdP0zxb8TSQJCNrej/vTeq5c5XVsNpfGXIEa2WiXy5XQ=="],
+ "@thi.ng/base-n": ["@thi.ng/base-n@2.7.34", "", {}, "sha512-LX7X9uaFIedAtDwzt6PzzaiHRPYGRjdVuAmhDV5bl8oB1SBfGaaj4PVumNbXkvKoelijl77oO6RspppVgE8/+Q=="],
- "@thi.ng/binary": ["@thi.ng/binary@3.4.44", "", { "dependencies": { "@thi.ng/api": "^8.11.21" } }, "sha512-kd6ZZ0xWR5JivkSIjqb6MvFpK2us7dU3ruwmiBAw4oZKsTnfibTbgvtJLL8Q5BJnxYmI3aILQ1sCamxRMwbzBw=="],
+ "@thi.ng/binary": ["@thi.ng/binary@3.4.45", "", { "dependencies": { "@thi.ng/api": "^8.11.22" } }, "sha512-VsLh3/zVWk+lquCDw90kIhZdS+VqBeSQyK29KVW66rQ7JAGn1glDhJuaqrucK/NEGnXmqxJfZqJdcEEJsn7M/Q=="],
- "@thi.ng/canvas": ["@thi.ng/canvas@1.0.8", "", {}, "sha512-r4bRWAsiaaNx+ihTtQDi1RQFMfOLwGgoSbZhdxLzEo6t1Ga5a/cqv/WKqUWLjD1LHjxhtflS8uPcihD2ETyAOg=="],
+ "@thi.ng/canvas": ["@thi.ng/canvas@1.0.9", "", {}, "sha512-BptFGkpJBi92IbHKyOmuoRr6M1yRoNReiM8a6lJOXtPwGQAowIAZX3HI/LBYp8jwwnyA/G3adXVoT6wfUqmwgw=="],
- "@thi.ng/checks": ["@thi.ng/checks@3.7.1", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-RfVBQgJN0kr00SKptAAzdDOaaRtWlctqegYogynTLkUhf8Ck516Efk/macgWTzSLEPgcumN7E9F1F3lgfRCWew=="],
+ "@thi.ng/checks": ["@thi.ng/checks@3.7.2", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-FGFHQrLOUKxLoNQfaoVKW8xBhFNaxtV0QqREbxYgnpCg8WgX5O8dU/T212bVTOMe0GSzqCkjEs3/BmPb4lxW5Q=="],
- "@thi.ng/color": ["@thi.ng/color@5.7.27", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/arrays": "^2.10.18", "@thi.ng/binary": "^3.4.44", "@thi.ng/checks": "^3.7.1", "@thi.ng/compare": "^2.4.13", "@thi.ng/compose": "^3.0.24", "@thi.ng/defmulti": "^3.0.61", "@thi.ng/errors": "^2.5.27", "@thi.ng/math": "^5.11.21", "@thi.ng/random": "^4.1.12", "@thi.ng/strings": "^3.9.6", "@thi.ng/transducers": "^9.2.21", "@thi.ng/vectors": "^7.12.23" } }, "sha512-zXI2X9lzGrp2I3o+qAg0ZFj+vWN9JMAFLfXvzLC/aAstn5N78eryhfQU55JXnlpuN4RkPbL+wlO/CZ/Sc37F1g=="],
+ "@thi.ng/color": ["@thi.ng/color@5.7.28", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/arrays": "^2.10.19", "@thi.ng/binary": "^3.4.45", "@thi.ng/checks": "^3.7.2", "@thi.ng/compare": "^2.4.14", "@thi.ng/compose": "^3.0.25", "@thi.ng/defmulti": "^3.0.62", "@thi.ng/errors": "^2.5.28", "@thi.ng/math": "^5.11.22", "@thi.ng/random": "^4.1.13", "@thi.ng/strings": "^3.9.7", "@thi.ng/transducers": "^9.2.22", "@thi.ng/vectors": "^7.12.24" } }, "sha512-NreMb8WrUXcFMg33m3AmtW8638H9VOVB2kzuy1FqrHL/xQJnNFMsgingEt2ts8uZxKb0dlBBZJfx+u10SmNPlA=="],
- "@thi.ng/color-palettes": ["@thi.ng/color-palettes@1.4.36", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/base-n": "^2.7.33", "@thi.ng/checks": "^3.7.1", "@thi.ng/color": "^5.7.27", "@thi.ng/errors": "^2.5.27", "@thi.ng/hex": "^2.3.65" } }, "sha512-D1dSSp1/RtTnqrBPAvvGzyZZcGFu+fempdUcSlLAd4SvE9zOZfWRS5kXn8IjwfzGCRtXL5vGcJ98Fv8N+BfYew=="],
+ "@thi.ng/color-palettes": ["@thi.ng/color-palettes@1.4.37", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/base-n": "^2.7.34", "@thi.ng/checks": "^3.7.2", "@thi.ng/color": "^5.7.28", "@thi.ng/errors": "^2.5.28", "@thi.ng/hex": "^2.3.66" } }, "sha512-NKoAnWJ3QEJYDUGzxCiBuIkiJplqtb6s+hRkLW8KD6Xw5klv9ajeBVxj93NY/4qB92Q0npfNWXCIrff++JM04A=="],
- "@thi.ng/compare": ["@thi.ng/compare@2.4.13", "", { "dependencies": { "@thi.ng/api": "^8.11.21" } }, "sha512-OOhxV5N7zrqKNS07ME8WZNBL81sFnE0BIjyVU0Z6zlwu2oH9mF7pZ6voF/wZH5mFfse8kcSxOA8oiQCCzCnPIQ=="],
+ "@thi.ng/compare": ["@thi.ng/compare@2.4.14", "", { "dependencies": { "@thi.ng/api": "^8.11.22" } }, "sha512-w/Bu2kNWVM94/BYYTB7QdNrd8UEKz/Wfv6w6ZZjdTGJ/69MrT+8m6jqnCeloD+WjkQ3vsfexLhRUmOft+fTrzQ=="],
- "@thi.ng/compose": ["@thi.ng/compose@3.0.24", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/errors": "^2.5.27" } }, "sha512-WN6EeKt6EqDMICqrfGWqNpneMWw9OGaMA3T4ZFGD/AFEFmREZ0xsOMDR91oYotlvVDJeVKPYVa5CCLGx6ppQLQ=="],
+ "@thi.ng/compose": ["@thi.ng/compose@3.0.25", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/errors": "^2.5.28" } }, "sha512-PAni1jk2b444GwGM+GLs++xeuOZ3OzJ6bVW+xr2D6iutLuqHsySFN2v44yTaWSVf1jxP0yqHla8en84b5ko92A=="],
- "@thi.ng/defmulti": ["@thi.ng/defmulti@3.0.61", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/errors": "^2.5.27", "@thi.ng/logger": "^3.1.2" } }, "sha512-BfS0RtXgvphbvpQ9n5mDUbjsXlOafxEC8t8qL41bnPK0UJhA7IrJ/yfraDB2WmeTPGid70L9NdX0bg4ih7GGyg=="],
+ "@thi.ng/defmulti": ["@thi.ng/defmulti@3.0.62", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/errors": "^2.5.28", "@thi.ng/logger": "^3.1.3" } }, "sha512-38sYjdjl7JFZPEPCRIay1mEHhbXK61iNExkOmJ6Qw2m9fzO0uT9NJ9CkH/iMnyNLfx1nTvZQwo48Q9qVkGMufQ=="],
- "@thi.ng/equiv": ["@thi.ng/equiv@2.1.77", "", {}, "sha512-qcpq7yMKNanK6NvoJaJOQeY2lHXuVWWERAHcM+wi3EpQwlVDO0smG3DOdZuAEcynnMli8JX3cyzAGFyZE86tZw=="],
+ "@thi.ng/equiv": ["@thi.ng/equiv@2.1.78", "", {}, "sha512-dEyGSzf/62Fv9X9swfqFfsB2/zWOQK+ItlwIarUXWxijYUIw0smCca4oix+eWs4/2QnESV1p0I1Jp66d8Psydg=="],
- "@thi.ng/errors": ["@thi.ng/errors@2.5.27", "", {}, "sha512-t1sgGuZqHv81lzNPSRySGHnDBvtt6h3MIMn3LdZnqMR0swwOIApw6YlheMYB63u94NdtuneQYcjdvbhOsHbwPQ=="],
+ "@thi.ng/errors": ["@thi.ng/errors@2.5.28", "", {}, "sha512-ZYFe7Hq9Oh2e2mOxlTc22/btCkozb0tm0xNAKK5kOoU8BxByh2fnN/qK1rHOfx11TW6EYtMpfEMQDdjmclOwjw=="],
- "@thi.ng/hex": ["@thi.ng/hex@2.3.65", "", {}, "sha512-rX3U8DCayQVLkm6J6uMf6w1TSRQ3pE4okwkahS/A6/sDmABUlMo7t+s1psQhV07Jigr8q103+JHQpDdrcURiWw=="],
+ "@thi.ng/hex": ["@thi.ng/hex@2.3.66", "", {}, "sha512-X4YVOByqPZYjIlqdDoZOpzNGW8FF5T2yWuAP3j80RPkVsgX2BM+gQYPyY7kkDe3gzWaRJIYE2TIwFHM+0RogJw=="],
- "@thi.ng/logger": ["@thi.ng/logger@3.1.2", "", {}, "sha512-WU/WCAOkxaLvGI2purG0iabueIG3Pq4CeoUogSHp/ctIh9vSVXplJYXlMy5PizttrHIJShJeHHu15x7IRyrAEQ=="],
+ "@thi.ng/logger": ["@thi.ng/logger@3.1.3", "", {}, "sha512-UOxnYsAeVyvPQuT1bcnZJGm5lRhHRYq1oJ08nISOy1lMoluN0Cht5jqGZVCG03hqwXj1sSsfhk6u08H4W5ACCQ=="],
- "@thi.ng/math": ["@thi.ng/math@5.11.21", "", { "dependencies": { "@thi.ng/api": "^8.11.21" } }, "sha512-JLjHdQbCpIP6F76Vq8yBtRtDOaUJ5HKsyEhU++8bjPX7VeDf8um4Ba/PUQZS+SpX0ghTZrqirqoI7H6wWz/TaQ=="],
+ "@thi.ng/math": ["@thi.ng/math@5.11.22", "", { "dependencies": { "@thi.ng/api": "^8.11.22" } }, "sha512-HpYCjOAZWfpiaGd4dVYrqYI8MbnOCpJWBDCUmGIienNCNnjov4iwmsHhi52kVCEIz3bDJtaEkPqB6L663wvCSw=="],
- "@thi.ng/memoize": ["@thi.ng/memoize@4.0.11", "", { "dependencies": { "@thi.ng/api": "^8.11.21" } }, "sha512-acxutHHnYAF8WfxCgDN+V/euhmC/FnjcPg1oLxgCQNET42X5jzhYyxhOEwOnkv+DRezkcgpkM9TFNF/raawlpQ=="],
+ "@thi.ng/memoize": ["@thi.ng/memoize@4.0.12", "", { "dependencies": { "@thi.ng/api": "^8.11.22" } }, "sha512-fVNaCojN+thuWstM+/Dz6iPOlH1MJD4yHx+pLErfkJjStAH1UGRCZt2C05qJrSDy8KJxNRfxcQivPKiJClzMCg=="],
- "@thi.ng/pixel": ["@thi.ng/pixel@7.3.19", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/canvas": "^1.0.8", "@thi.ng/checks": "^3.7.1", "@thi.ng/errors": "^2.5.27", "@thi.ng/math": "^5.11.21", "@thi.ng/porter-duff": "^2.1.99" } }, "sha512-k1oUFPktxOcJEh0RuzgwoZGBkNqB1zMNvk2QILph54Cj1JJH3CcAuqJTu+N6QkXoxsgoM4DtMiDcmIdyrRokJQ=="],
+ "@thi.ng/pixel": ["@thi.ng/pixel@7.3.20", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/canvas": "^1.0.9", "@thi.ng/checks": "^3.7.2", "@thi.ng/errors": "^2.5.28", "@thi.ng/math": "^5.11.22", "@thi.ng/porter-duff": "^2.1.100" } }, "sha512-n6CEMda8XYSrItk/llWlbV5GhFVBwWD9peTgXu/uL6BiZrTtrNmuO19hNiOALo/yWIFmWgb36eeSh0AFr6+d6w=="],
- "@thi.ng/pixel-dither": ["@thi.ng/pixel-dither@1.1.159", "", { "dependencies": { "@thi.ng/checks": "^3.7.1", "@thi.ng/math": "^5.11.21", "@thi.ng/pixel": "^7.3.19" } }, "sha512-1tQfZUPgTEe/sn0URTHbkDEtWpJyqC4WqR9BMdOcE+g+pHhp1AjGB+eru1Mhx0WKJ/jcYl669U73Qooh8L2V6A=="],
+ "@thi.ng/pixel-dither": ["@thi.ng/pixel-dither@1.1.160", "", { "dependencies": { "@thi.ng/checks": "^3.7.2", "@thi.ng/math": "^5.11.22", "@thi.ng/pixel": "^7.3.20" } }, "sha512-3VDO050krFIxiQ8LpwPqFlhyXzVe7upzApPWhIwGyJI2bAs9exqxLU1lrDLG9kqx0R17ktHr22NFOgOWra5iyQ=="],
- "@thi.ng/porter-duff": ["@thi.ng/porter-duff@2.1.99", "", { "dependencies": { "@thi.ng/api": "^8.11.21" } }, "sha512-HC4rqfHGfAMijUoNlZslRZypb7MJ8BO6XpRF2Ol/O6JqHzhHtWlwbB/WGfoSiAUfdEHprSZ3KsPCDEl7MnRdpg=="],
+ "@thi.ng/porter-duff": ["@thi.ng/porter-duff@2.1.100", "", { "dependencies": { "@thi.ng/api": "^8.11.22" } }, "sha512-qkaYL2KySvBBmjPzNvR/mMXO71NEg7grc/TKfTQijArtZdvg5odsjAI+FJXrb2ArrleXqXZKsO3dMdeD3t51ZQ=="],
- "@thi.ng/random": ["@thi.ng/random@4.1.12", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/errors": "^2.5.27" } }, "sha512-IpcAgCGDdaHAahY1LJbJ9oULsmhCrQpGeAjPfB0gpGM1D7JXeOaY83ctt3tdqovT2YIaCMIUd8ahJt7VBMD+Ig=="],
+ "@thi.ng/random": ["@thi.ng/random@4.1.13", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/errors": "^2.5.28" } }, "sha512-FQXVYRLt98hItEpzLrGaG7EUuP6+bJYvXeQ65NjtC0pGf+x/DxubI8Jmqphr5EyH8odyxuDzxdnRbak+p3Dnyg=="],
- "@thi.ng/strings": ["@thi.ng/strings@3.9.6", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/errors": "^2.5.27", "@thi.ng/hex": "^2.3.65", "@thi.ng/memoize": "^4.0.11" } }, "sha512-kdh66IfvlHz+iywyojTZCmUXg5d4NISrA3MwNm6fLUsuBA+yNVTOdf484Azbe1SQEUU5e9RLc6bfVeqkdthNxw=="],
+ "@thi.ng/strings": ["@thi.ng/strings@3.9.7", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/errors": "^2.5.28", "@thi.ng/hex": "^2.3.66", "@thi.ng/memoize": "^4.0.12" } }, "sha512-J1cNbtSaYCiuTYJJE4rFHsnJIMVKKyjGLcBoGMPUTK40uiR+rwM7Rg3nIbalW7QcBfcNejAelHFzHO5aTD3u3w=="],
- "@thi.ng/timestamp": ["@thi.ng/timestamp@1.1.6", "", {}, "sha512-LGVbm9AiGwBcNHh2jieEtccy2edYCdHG3aK0no6ChH1AyAzonDdr05D+nBv0pl1h9C2AFPe9UQj43RdFgQJuUQ=="],
+ "@thi.ng/timestamp": ["@thi.ng/timestamp@1.1.7", "", {}, "sha512-puu4cA5HqBoNeRpgPgKdwDHo6YI1PoS9S/X6HiMDfWVhv6Ln4SzydduvJF7fpCEs/01xtMu/8wSpGHH1icBdaA=="],
- "@thi.ng/transducers": ["@thi.ng/transducers@9.2.21", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/arrays": "^2.10.18", "@thi.ng/checks": "^3.7.1", "@thi.ng/compare": "^2.4.13", "@thi.ng/compose": "^3.0.24", "@thi.ng/errors": "^2.5.27", "@thi.ng/math": "^5.11.21", "@thi.ng/random": "^4.1.12", "@thi.ng/timestamp": "^1.1.6" } }, "sha512-5uOiddZICcOGW5L8Y301bwjshfLZOvBlJpLneQzzsYbpqfQdOR4NlkbrJVw+IKEuqZU5B64eRqzJzOwjDAsd0g=="],
+ "@thi.ng/transducers": ["@thi.ng/transducers@9.2.22", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/arrays": "^2.10.19", "@thi.ng/checks": "^3.7.2", "@thi.ng/compare": "^2.4.14", "@thi.ng/compose": "^3.0.25", "@thi.ng/errors": "^2.5.28", "@thi.ng/math": "^5.11.22", "@thi.ng/random": "^4.1.13", "@thi.ng/timestamp": "^1.1.7" } }, "sha512-CFYXYRlGsRKUZdroQkEn09HagzXhIhs73jVcJ8ld05O8L+H2Y3RPgsOEcSd/ysB1oH/2LfVuFbYkNXg/1jvlDQ=="],
- "@thi.ng/vectors": ["@thi.ng/vectors@7.12.23", "", { "dependencies": { "@thi.ng/api": "^8.11.21", "@thi.ng/binary": "^3.4.44", "@thi.ng/checks": "^3.7.1", "@thi.ng/equiv": "^2.1.77", "@thi.ng/errors": "^2.5.27", "@thi.ng/math": "^5.11.21", "@thi.ng/memoize": "^4.0.11", "@thi.ng/random": "^4.1.12", "@thi.ng/strings": "^3.9.6", "@thi.ng/transducers": "^9.2.21" } }, "sha512-+PbtaA2cYApQrzGpOKC3Yg4jxWiJw6qEchTH+YRyjVA6YdAlSdptwrvEYdUCiKhPLqa0WUrKzCUw4mqIE75yDA=="],
+ "@thi.ng/vectors": ["@thi.ng/vectors@7.12.24", "", { "dependencies": { "@thi.ng/api": "^8.11.22", "@thi.ng/binary": "^3.4.45", "@thi.ng/checks": "^3.7.2", "@thi.ng/equiv": "^2.1.78", "@thi.ng/errors": "^2.5.28", "@thi.ng/math": "^5.11.22", "@thi.ng/memoize": "^4.0.12", "@thi.ng/random": "^4.1.13", "@thi.ng/strings": "^3.9.7", "@thi.ng/transducers": "^9.2.22" } }, "sha512-ZxwlnDb/TZBR57BN0bcqFmZfQDu7NCNUBXO/LRhKAPVoqTXyShxELUYhFWKV8oNF8Tr7+dcJUg46tg9pP6VMtA=="],
- "@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
+ "@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
@@ -441,7 +441,7 @@
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.26.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.26.0", "@typescript-eslint/utils": "8.26.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.25.0", "", {}, "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.26.0", "", { "dependencies": { "@typescript-eslint/types": "8.26.0", "@typescript-eslint/visitor-keys": "8.26.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ=="],
@@ -537,7 +537,7 @@
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
- "bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
+ "bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
"cacheable": ["cacheable@1.8.8", "", { "dependencies": { "hookified": "^1.7.0", "keyv": "^5.2.3" } }, "sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw=="],
@@ -629,7 +629,7 @@
"easy-table": ["easy-table@1.2.0", "", { "dependencies": { "ansi-regex": "^5.0.1" }, "optionalDependencies": { "wcwidth": "^1.0.1" } }, "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww=="],
- "effect": ["effect@3.13.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-yjDuWX5M34kB38Pwilv8QU956nA2m1a6ws0i+04VQkQJEBPI4GfVhUhtNNFRaqKIOlkqdqzJHCDOwARmaf2XzA=="],
+ "effect": ["effect@3.13.9", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-fcynHENQW6pejcN5thnRyCFCZDleYSkxUTCbQL36AUXGBakbLXERrLHBlwJke3687S3Z4o9MHad7v+pUiDBC4Q=="],
"electron-to-chromium": ["electron-to-chromium@1.5.109", "", {}, "sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ=="],
@@ -653,7 +653,7 @@
"eslint": ["eslint@9.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.1.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.0", "@eslint/js": "9.22.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ=="],
- "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@4.9.0", "", { "dependencies": { "@typescript-eslint/types": "^8.24.0", "@typescript-eslint/utils": "^8.24.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-76lDfJnonOcXGW3bEXuqhEGId0LrOlvIE1yLHvK/eKMMPOc0b43KchAIR2Bdbqlg+LPXU5/Q+UzuzkO+cWHT6w=="],
+ "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@4.10.1", "", { "dependencies": { "@typescript-eslint/types": "^8.26.0", "@typescript-eslint/utils": "^8.26.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": ">=8.45.0" } }, "sha512-GXwFfL47RfBLZRGQdrvGZw9Ali2T2GPW8p4Gyj2fyWQ9396R/HgJMf0m9kn7D6WXRwrINfTDGLS+QYIeok9qEg=="],
"eslint-plugin-vue": ["eslint-plugin-vue@10.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" } }, "sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w=="],
@@ -1117,18 +1117,8 @@
"@snyk/github-codeowners/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
- "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
-
- "@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
-
- "@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
-
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
- "@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
-
- "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.26.0", "", {}, "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA=="],
-
"@vue/language-core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -1145,8 +1135,6 @@
"eslint/file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
- "eslint-plugin-perfectionist/@typescript-eslint/utils": ["@typescript-eslint/utils@8.25.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.25.0", "@typescript-eslint/types": "8.25.0", "@typescript-eslint/typescript-estree": "8.25.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA=="],
-
"fast-glob/@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -1227,10 +1215,6 @@
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0" } }, "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "@typescript-eslint/visitor-keys": "8.25.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q=="],
-
"eslint/file-entry-cache/flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"fast-glob/@nodelib/fs.walk/@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -1290,15 +1274,5 @@
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@vue/language-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.25.0", "", { "dependencies": { "@typescript-eslint/types": "8.25.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
-
- "eslint-plugin-perfectionist/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
}
}
diff --git a/cspell.json b/cspell.json
index 7e79d8d..779eb4a 100644
--- a/cspell.json
+++ b/cspell.json
@@ -16,6 +16,7 @@
"tabindex",
"currentcolor",
"labelledby",
- "imdb"
+ "imdb",
+ "xstate"
]
}
diff --git a/package.json b/package.json
index f69c01d..4d8f43f 100644
--- a/package.json
+++ b/package.json
@@ -4,15 +4,15 @@
"type": "module",
"private": true,
"dependencies": {
- "@effect/platform": "^0.78.0",
- "@effect/sql-drizzle": "^0.30.0",
- "@thi.ng/color-palettes": "^1.4.36",
- "@thi.ng/pixel": "^7.3.19",
- "@thi.ng/pixel-dither": "^1.1.159",
+ "@effect/platform": "^0.79.0",
+ "@effect/sql-drizzle": "^0.31.0",
+ "@thi.ng/color-palettes": "^1.4.37",
+ "@thi.ng/pixel": "^7.3.20",
+ "@thi.ng/pixel-dither": "^1.1.160",
"@xstate/vue": "^4.0.2",
"a11y-dialog": "^8.1.1",
"drizzle-orm": "^0.40.0",
- "effect": "^3.13.7",
+ "effect": "^3.13.9",
"pinia": "^3.0.1",
"sqlocal": "^0.14.0",
"vue": "^3.5.13",
@@ -22,7 +22,7 @@
"devDependencies": {
"@cspell/dict-fr-fr": "^2.2.5",
"@eslint/css": "^0.4.0",
- "@types/bun": "^1.2.4",
+ "@types/bun": "^1.2.5",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/typescript-plugin": "^2.2.8",
@@ -30,7 +30,7 @@
"cspell": "^8.17.5",
"drizzle-kit": "^0.30.5",
"eslint": "^9.22.0",
- "eslint-plugin-perfectionist": "^4.9.0",
+ "eslint-plugin-perfectionist": "^4.10.1",
"eslint-plugin-vue": "^10.0.0",
"globals": "^16.0.0",
"jiti": "^2.4.2",
diff --git a/src/components/dialogs/EditEntryDialog.vue b/src/components/dialogs/EditEntryDialog.vue
index 9df4387..078cc6e 100644
--- a/src/components/dialogs/EditEntryDialog.vue
+++ b/src/components/dialogs/EditEntryDialog.vue
@@ -1,21 +1,38 @@
-
+
+
+
+
+
diff --git a/src/libs/apis/clients.ts b/src/libs/apis/clients.ts
index 69bf478..6daa11b 100644
--- a/src/libs/apis/clients.ts
+++ b/src/libs/apis/clients.ts
@@ -1,4 +1,4 @@
-import type { Input } from "@effect/platform/UrlParams";
+import type { Coercible, Input } from "@effect/platform/UrlParams";
import { FetchHttpClient, HttpClient, HttpClientRequest, Url, UrlParams } from "@effect/platform";
import { Effect, pipe } from "effect";
@@ -19,6 +19,14 @@ export const createUrlWithParams = (stringUrl: string) => (params: Input) =>
return Url.setUrlParams(url, urlParams);
});
+export const createUrlWithParameterizedPathAndParams = (stringUrl: string, parameter: Coercible) => (params: Input) =>
+ Effect.gen(function*() {
+ const url: URL = yield* Url.fromString(`${stringUrl}/${String(parameter)}`);
+ const urlParams: UrlParams.UrlParams = UrlParams.fromInput(params);
+
+ return Url.setUrlParams(url, urlParams);
+ });
+
export const createGetHttpRequest = (url: URL): HttpClientRequest.HttpClientRequest =>
pipe(
HttpClientRequest.get(url),
diff --git a/src/libs/apis/routes.ts b/src/libs/apis/routes.ts
index aee5801..7c1fbb5 100644
--- a/src/libs/apis/routes.ts
+++ b/src/libs/apis/routes.ts
@@ -1 +1,2 @@
export const TMDB_ROUTE_SEARCH_MOVIE = "https://api.themoviedb.org/3/search/movie";
+export const TMDB_ROUTE_MOVIE_DETAILS = "https://api.themoviedb.org/3/movie";
diff --git a/src/libs/apis/tmdb/schemas.ts b/src/libs/apis/tmdb/schemas.ts
index 0602184..020a18a 100644
--- a/src/libs/apis/tmdb/schemas.ts
+++ b/src/libs/apis/tmdb/schemas.ts
@@ -32,6 +32,7 @@ export class TmdbMovieDetailsQueryParams
extends Schema.Class("TmdbMovieDetailsQueryParams")({
append_to_response: Schema.Literal("credits"),
language: Schema.NonEmptyString,
+ movie_id: Schema.NonNegativeInt,
})
{}
@@ -67,7 +68,7 @@ export class TmdbMovieDetailsWithCreditsResponse
extends Schema.Class("TmdbMovieDetailsWithCreditsResponse")({
adult: Schema.Boolean,
backdrop_path: Schema.Union(Schema.String, Schema.Null),
- belongs_to_collection: Schema.Union(Schema.String, Schema.Null),
+ belongs_to_collection: Schema.Unknown,
budget: Schema.NonNegativeInt,
credits: Schema.Struct({
cast: Schema.Array(TmdbCredit),
@@ -79,7 +80,7 @@ export class TmdbMovieDetailsWithCreditsResponse
})),
homepage: Schema.String,
id: Schema.NonNegativeInt,
- imdb_id: Schema.String,
+ imdb_id: Schema.Union(Schema.String, Schema.Null),
original_language: Schema.String,
original_title: Schema.String,
overview: Schema.String,
diff --git a/src/libs/apis/tmdb/schemas/companies.ts b/src/libs/apis/tmdb/schemas/companies.ts
index aa7d151..3f8c907 100644
--- a/src/libs/apis/tmdb/schemas/companies.ts
+++ b/src/libs/apis/tmdb/schemas/companies.ts
@@ -18,5 +18,5 @@ export class TmdbCompany extends Schema.Class("TmdbCompany")({
/** Nom de la société. */
name: Schema.String,
/** Pays d'origine de la société sous forme de code ISO 3166-1. */
- origin_country: Schema.NonEmptyString.pipe(Schema.fromBrand(CountryIsoCode)),
+ origin_country: Schema.Union(Schema.String.pipe(Schema.fromBrand(CountryIsoCode)), Schema.String),
}) {}
diff --git a/src/libs/apis/tmdb/schemas/credits.ts b/src/libs/apis/tmdb/schemas/credits.ts
index ec2179a..1cb0dc1 100644
--- a/src/libs/apis/tmdb/schemas/credits.ts
+++ b/src/libs/apis/tmdb/schemas/credits.ts
@@ -50,8 +50,8 @@ export const PersonName = Brand.refined(
export class TmdbCredit extends Schema.Class("TmdbCredit")({
adult: Schema.Boolean,
- cast_id: Schema.NonNegativeInt,
- character: Schema.NonEmptyString,
+ cast_id: Schema.NonNegativeInt.pipe(Schema.optional),
+ character: Schema.NonEmptyString.pipe(Schema.optional),
credit_id: Schema.String,
department: Schema.String.pipe(Schema.optional),
gender: GenderStringFromInteger,
@@ -61,6 +61,6 @@ export class TmdbCredit extends Schema.Class("TmdbCredit")({
name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
order: Schema.NonNegativeInt.pipe(Schema.optional),
original_name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
- popularity: Schema.NonNegativeInt,
- profile_path: Schema.String,
+ popularity: Schema.Number,
+ profile_path: Schema.Union(Schema.String, Schema.Null),
}) {}
diff --git a/src/libs/apis/tmdb/schemas/languages.ts b/src/libs/apis/tmdb/schemas/languages.ts
index 3b41318..a13645b 100644
--- a/src/libs/apis/tmdb/schemas/languages.ts
+++ b/src/libs/apis/tmdb/schemas/languages.ts
@@ -8,6 +8,6 @@ export const LanguageIsoCode = Brand.refined(
export class TmdbLanguage extends Schema.Class("TmdbLanguage")({
english_name: Schema.NonEmptyString,
- iso_631_1: Schema.NonEmptyString.pipe(Schema.fromBrand(LanguageIsoCode)),
+ iso_639_1: Schema.NonEmptyString.pipe(Schema.fromBrand(LanguageIsoCode)),
name: Schema.NonEmptyString,
}) {}
diff --git a/src/pages/AddEntryPage.vue b/src/pages/AddEntryPage.vue
new file mode 100644
index 0000000..a8a6302
--- /dev/null
+++ b/src/pages/AddEntryPage.vue
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/pages/SearchPage.vue b/src/pages/SearchPage.vue
index a3b45de..e09f148 100644
--- a/src/pages/SearchPage.vue
+++ b/src/pages/SearchPage.vue
@@ -23,6 +23,7 @@
import { Effect, pipe, Schema } from "effect";
import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
+ import { useEntryStore } from "@/stores/entry.ts";
// États
@@ -54,6 +55,9 @@
const editedEntry: Ref = ref();
+ // Magasins
+ const entryStore = useEntryStore();
+
// Fonctions
let updateUrlQuery = updateUrlQueryFromFormData(router, form.value);
@@ -148,33 +152,32 @@
effect => getOrUndefined(effect),
);
- // TODO: Uniformiser la casse des propriétés.
- searchData.value.set(
- result.id,
- yield* Schema.decodeUnknown(MergedTmdbLocalData)(
- {
- artWorkId: artWork?.id,
- artWorkMediumTypeId: artWork?.mediumTypeId,
- entryAppreciation: entry?.appreciation,
- entryCommentary: entry?.commentary,
- entryDateCreated: entry?.dateCreated,
- entryDateModified: entry?.dateModified,
- entryDateObtained: entry?.dateObtained,
- entryId: entry?.id,
- entryStateId: entry?.stateId,
- genreIds: result.genre_ids,
- originalLanguage: result.original_language,
- originalResultIndex: index,
- originalTitle: result.original_title,
- overview: result.overview,
- popularity: result.popularity,
- posterUrl: result.poster_path,
- releaseDate: result.release_date,
- title: result.title,
- tmdbId: result.id,
- } satisfies MergedTmdbLocalData,
- ),
+ const newEntry = yield* Schema.decodeUnknown(MergedTmdbLocalData)(
+ {
+ artWorkId: artWork?.id,
+ artWorkMediumTypeId: artWork?.mediumTypeId,
+ entryAppreciation: entry?.appreciation,
+ entryCommentary: entry?.commentary,
+ entryDateCreated: entry?.dateCreated,
+ entryDateModified: entry?.dateModified,
+ entryDateObtained: entry?.dateObtained,
+ entryId: entry?.id,
+ entryStateId: entry?.stateId,
+ genreIds: result.genre_ids,
+ originalLanguage: result.original_language,
+ originalResultIndex: index,
+ originalTitle: result.original_title,
+ overview: result.overview,
+ popularity: result.popularity,
+ posterUrl: result.poster_path,
+ releaseDate: result.release_date,
+ title: result.title,
+ tmdbId: result.id,
+ } satisfies MergedTmdbLocalData,
);
+
+ searchData.value.set(result.id, newEntry);
+ entryStore.setEntry(newEntry);
}).pipe(Effect.runPromise)
);
}));
diff --git a/src/router/index.ts b/src/router/index.ts
index 1372f30..7c36b72 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -29,6 +29,18 @@ const router: Router = createRouter({
name: "Search",
path: "/search",
},
+ {
+ component: () => import("@/pages/AddEntryPage.vue"),
+ meta: { title: "Ajouter une entrée au Journal" },
+ name: "AddEntry",
+ path: "/entry/add",
+ },
+ {
+ component: () => import("@/pages/HomePage.vue"),
+ meta: { title: "Voir une entrée du Journal" },
+ name: "ViewEntry",
+ path: "/entry/:entryId",
+ },
],
});
diff --git a/src/router/typed-routes.d.ts b/src/router/typed-routes.d.ts
index 480896c..37e82ef 100644
--- a/src/router/typed-routes.d.ts
+++ b/src/router/typed-routes.d.ts
@@ -3,6 +3,13 @@ import type { RouteRecordInfo } from "vue-router";
import "vue-router";
export interface RouteNamedMap {
+ AddEntry: RouteRecordInfo<
+ "AddEntry",
+ "/entry/add",
+ Record,
+ Record,
+ { title: string }
+ >;
Home: RouteRecordInfo<
"Home",
"/",
@@ -24,6 +31,13 @@ export interface RouteNamedMap {
Record,
{ title: string }
>;
+ ViewEntry: RouteRecordInfo<
+ "ViewEntry",
+ "/entry/:entryId",
+ { entryId: number },
+ { entryId: number },
+ { title: string }
+ >;
}
// Last, you will need to augment the Vue Router types with this map of routes
diff --git a/src/services/tmdb-api.ts b/src/services/tmdb-api.ts
index 806f52a..a9eeb95 100644
--- a/src/services/tmdb-api.ts
+++ b/src/services/tmdb-api.ts
@@ -1,6 +1,16 @@
-import { createGetHttpRequest, createUrlWithParams, DebugHttpClient as Dhc } from "@/libs/apis/clients";
-import { TMDB_ROUTE_SEARCH_MOVIE } from "@/libs/apis/routes";
-import { type TmdbMovieSearchQueryParams, TmdbMovieSearchResponse } from "@/libs/apis/tmdb/schemas";
+import {
+ createGetHttpRequest,
+ createUrlWithParameterizedPathAndParams,
+ createUrlWithParams,
+ DebugHttpClient as Dhc,
+} from "@/libs/apis/clients";
+import { TMDB_ROUTE_MOVIE_DETAILS, TMDB_ROUTE_SEARCH_MOVIE } from "@/libs/apis/routes";
+import {
+ TmdbMovieDetailsQueryParams,
+ TmdbMovieDetailsWithCreditsResponse,
+ type TmdbMovieSearchQueryParams,
+ TmdbMovieSearchResponse,
+} from "@/libs/apis/tmdb/schemas";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform";
import { Data, Effect, pipe } from "effect";
@@ -11,9 +21,23 @@ export class DebugHttpClient extends Effect.Service()("DebugHtt
export class TmdbApi extends Effect.Service()("TmdbApi", {
effect: Effect.gen(function*() {
yield* Effect.logDebug("--- TMDB-API ---");
- const client = yield* DebugHttpClient;
+ const client = yield* Dhc;
return {
+ getMovieDetails: (args: TmdbMovieDetailsQueryParams) =>
+ pipe(
+ createUrlWithParameterizedPathAndParams(TMDB_ROUTE_MOVIE_DETAILS, args.movie_id)({
+ append_to_response: args.append_to_response,
+ language: args.language,
+ }),
+ Effect.andThen((url: URL) => createGetHttpRequest(url)),
+ Effect.andThen((request: HttpClientRequest.HttpClientRequest) => client.execute(request)),
+ HttpClient.withTracerPropagation(false),
+ Effect.andThen((response: HttpClientResponse.HttpClientResponse) =>
+ HttpClientResponse.schemaBodyJson(TmdbMovieDetailsWithCreditsResponse)(response)
+ ),
+ Effect.scoped,
+ ),
searchMovie: (args: TmdbMovieSearchQueryParams) =>
pipe(
createUrlWithParams(TMDB_ROUTE_SEARCH_MOVIE)({ ...args }),
diff --git a/src/stores/entry.ts b/src/stores/entry.ts
new file mode 100644
index 0000000..d806d7d
--- /dev/null
+++ b/src/stores/entry.ts
@@ -0,0 +1,20 @@
+import type { TmdbMovieDetailsWithCreditsResponse } from "@/libs/apis/tmdb/schemas";
+import type { MergedTmdbLocalData } from "@/libs/search/schemas";
+
+import { defineStore } from "pinia";
+import { type Ref, ref } from "vue";
+
+export const useEntryStore = defineStore("entry", () => {
+ const entry: Ref = ref();
+ const tmdbData: Ref = ref();
+
+ const setEntry = (newEntry: MergedTmdbLocalData | undefined): void => {
+ entry.value = newEntry;
+ };
+
+ const setTmdbData = (newData: TmdbMovieDetailsWithCreditsResponse | undefined): void => {
+ tmdbData.value = newData;
+ };
+
+ return { entry, setEntry, setTmdbData, tmdbData };
+});
diff --git a/src/styles/base/base.css b/src/styles/base/base.css
index 71d26b2..adc72e5 100755
--- a/src/styles/base/base.css
+++ b/src/styles/base/base.css
@@ -67,7 +67,7 @@ body {
navigation: auto;
}
-@media (prefers-reduced-motion) {
+/* @media (prefers-reduced-motion) {
*, *::before, *::after {
scroll-behavior: auto !important;
transition: none !important;
@@ -77,4 +77,4 @@ body {
@view-transition {
navigation: none !important;
}
-}
+} */
diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css
index 70c7789..8a4ba58 100755
--- a/src/styles/base/variables.css
+++ b/src/styles/base/variables.css
@@ -11,6 +11,15 @@
--color-tertiary: dimgrey;
--color-quartary: #be2727;
+ /* *
+ * Depuis saumon :
+ * Complémentaire : #72ecfa
+ * Brun foncé : #502924
+ * Brun clair : #7a3f38
+ * Rouge brun : #a5544b
+ * Rouge marron : #cf6a5f
+ */
+
/* Hauteurs de ligne */
--line-height-comfortable: 1.4;
--line-height-compact: 1.1;
@@ -34,7 +43,11 @@
--s5: calc(var(--s4) * var(--ratio));
/* Arrière-plans à motifs */
- --bg25-secondary: repeating-conic-gradient(var(--color-tertiary) 0% 25%, transparent 0% 100%) 1px
+ --bg25-tertiary: repeating-conic-gradient(var(--color-tertiary) 0% 25%, transparent 0% 100%) 1px
+ 0.5px / 2px 2px;
+ --bg75-tertiary: repeating-conic-gradient(var(--color-tertiary) 0% 75%, transparent 0% 100%) 1px
+ 0.5px / 2px 2px;
+ --bg25-secondary: repeating-conic-gradient(var(--color-secondary) 0% 25%, transparent 0% 100%) 1px
0.5px / 2px 2px;
--bg75-secondary: repeating-conic-gradient(var(--color-secondary) 0% 75%, transparent 0% 100%) 1px
0.5px / 2px 2px;
diff --git a/src/styles/components/imposter-box.css b/src/styles/components/imposter-box.css
index 6b222c3..0954530 100644
--- a/src/styles/components/imposter-box.css
+++ b/src/styles/components/imposter-box.css
@@ -1,6 +1,6 @@
/* Élément conteneur de la fenêtre modale. */
.dialog {
- --dialog-overlay-background: var(--bg25-secondary);
+ --dialog-overlay-background: var(--bg25-tertiary);
--dialog-background-color: var(--root-background-color);
--dialog-border-color: var(--root-text-color);
--dialog-shadow-color: var(--root-text-color);
@@ -37,14 +37,16 @@
border-block-end: 1px solid var(--dialog-border-color);
h2 {
+ font-family: "BRKLY", monospace;
padding-inline: var(--s-1);
- font-size: var(--s1);
+ text-transform: uppercase;
+ font-size: var(--s0);
+ font-weight: var(--brkly-font-weight-semibold);
}
button {
padding: var(--s-1);
- font-family: Banquise, monospace;
- font-weight: 500;
+ font-weight: var(--brkly-font-weight-semibold);
}
}
diff --git a/src/styles/themes/default/animations.css b/src/styles/themes/default/animations.css
index be0e837..8dd9d7b 100644
--- a/src/styles/themes/default/animations.css
+++ b/src/styles/themes/default/animations.css
@@ -13,3 +13,25 @@
opacity: 1;
}
}
+
+@keyframes loading {
+ 0% {
+ content: "";
+ }
+
+ 25% {
+ content: ".";
+ }
+
+ 50% {
+ content: "..";
+ }
+
+ 75% {
+ content: "...";
+ }
+
+ 100% {
+ content: "";
+ }
+}
diff --git a/src/styles/themes/default/headings.css b/src/styles/themes/default/headings.css
index 891f9e5..9690ab1 100755
--- a/src/styles/themes/default/headings.css
+++ b/src/styles/themes/default/headings.css
@@ -6,16 +6,19 @@ h1 {
font-family: var(--headings-font-family);
font-size: var(--s3);
letter-spacing: var(--letter-spacing-medium);
+ line-height: var(--line-height-compact);
}
h2 {
font-family: var(--headings-font-family);
font-size: var(--s2);
letter-spacing: var(--letter-spacing-small);
+ line-height: var(--line-height-compact);
}
h3 {
font-family: var(--headings-font-family);
font-size: var(--s1);
letter-spacing: var(--letter-spacing-small);
+ line-height: var(--line-height-compact);
}