From 19e1dc60d94e14bb9d7898f164cb3cde2851794f Mon Sep 17 00:00:00 2001 From: Mackie Underdown Date: Fri, 12 Dec 2025 13:42:21 -0500 Subject: [PATCH 1/2] Upgrade dependencies --- bun.lock | 8 ++++---- package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bun.lock b/bun.lock index 627e094..ea1cdbc 100644 --- a/bun.lock +++ b/bun.lock @@ -6,14 +6,14 @@ "name": "ghui", "dependencies": { "@effect/cli": "^0.72.1", - "@effect/platform": "^0.93.6", + "@effect/platform": "^0.93.7", "@effect/platform-bun": "^0.86.0", "@opentui/core": "^0.1.60", "@opentui/react": "^0.1.60", "@tanstack/react-query": "^5.90.12", "cli-spinners": "^3.3.0", "effect": "^3.19.11", - "react": "^19.2.1", + "react": "^19.2.3", "tiny-invariant": "^1.3.3", "zustand": "^5.0.9", }, @@ -99,7 +99,7 @@ "@effect/language-service": ["@effect/language-service@0.62.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-K78mPtNpHqArLTXpYNASoqiqYUmPASrdnntSBVInMYmI39hyLkmTqk78bclzkfsIvJZBBS4vvsuWx7Etmz8rkA=="], - "@effect/platform": ["@effect/platform@0.93.6", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.8" } }, "sha512-I5lBGQWzWXP4zlIdPs7z7WHmEFVBQhn+74emr/h16GZX96EEJ6I1rjGaKyZF7mtukbMuo9wEckDPssM8vskZ/w=="], + "@effect/platform": ["@effect/platform@0.93.7", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.11" } }, "sha512-kdF5qAGOz1hotfh9lws/cdBO6q0JT7d+V/u9OvBJsocnV3EbMKlVDXEHNg8Wv6ZLgFs0T1AA/NBF+Hks9eZVkA=="], "@effect/platform-bun": ["@effect/platform-bun@0.86.0", "", { "dependencies": { "@effect/platform-node-shared": "^0.56.0", "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/cluster": "^0.55.0", "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "@effect/sql": "^0.48.6", "effect": "^3.19.8" } }, "sha512-wg6eyzIxHuOd87rHjR18T7DjOtQ0dz8/cQD/pvZo8xFBSZ0AoZXqkHOKuU34DIRA4tp+BdBP6wjDDQYNQK/agQ=="], @@ -535,7 +535,7 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], + "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], "react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="], diff --git a/package.json b/package.json index b1df880..91e0fd7 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,14 @@ }, "dependencies": { "@effect/cli": "^0.72.1", - "@effect/platform": "^0.93.6", + "@effect/platform": "^0.93.7", "@effect/platform-bun": "^0.86.0", "@opentui/core": "^0.1.60", "@opentui/react": "^0.1.60", "@tanstack/react-query": "^5.90.12", "cli-spinners": "^3.3.0", "effect": "^3.19.11", - "react": "^19.2.1", + "react": "^19.2.3", "tiny-invariant": "^1.3.3", "zustand": "^5.0.9" }, From fb6fc17785228c46b3204890d496e29e152f3492 Mon Sep 17 00:00:00 2001 From: Mackie Underdown Date: Mon, 15 Dec 2025 15:44:11 -0500 Subject: [PATCH 2/2] Migrate to effect-atom/atom-react --- .changeset/sour-garlics-buy.md | 5 ++ bun.lock | 42 +++++----- package.json | 10 +-- src/GitHub.ts | 76 ++++++++++++----- src/Issues.tsx | 65 ++++++++------- src/PullRequests.tsx | 148 +++++++++++++++------------------ src/Queries.ts | 48 ----------- src/ReactQueryEffect.ts | 120 -------------------------- src/Repo.ts | 13 ++- src/index.tsx | 39 ++++----- 10 files changed, 220 insertions(+), 346 deletions(-) create mode 100644 .changeset/sour-garlics-buy.md delete mode 100644 src/Queries.ts delete mode 100644 src/ReactQueryEffect.ts diff --git a/.changeset/sour-garlics-buy.md b/.changeset/sour-garlics-buy.md new file mode 100644 index 0000000..2d7d9c9 --- /dev/null +++ b/.changeset/sour-garlics-buy.md @@ -0,0 +1,5 @@ +--- +'ghui': patch +--- + +Migrate to effect-atom/atom-react diff --git a/bun.lock b/bun.lock index ea1cdbc..4b8215d 100644 --- a/bun.lock +++ b/bun.lock @@ -5,28 +5,28 @@ "": { "name": "ghui", "dependencies": { + "@effect-atom/atom-react": "^0.4.4", "@effect/cli": "^0.72.1", - "@effect/platform": "^0.93.7", + "@effect/platform": "^0.93.8", "@effect/platform-bun": "^0.86.0", "@opentui/core": "^0.1.60", "@opentui/react": "^0.1.60", - "@tanstack/react-query": "^5.90.12", "cli-spinners": "^3.3.0", - "effect": "^3.19.11", + "effect": "^3.19.12", "react": "^19.2.3", "tiny-invariant": "^1.3.3", "zustand": "^5.0.9", }, "devDependencies": { "@changesets/cli": "^2.29.8", - "@effect/language-service": "^0.62.1", + "@effect/language-service": "^0.62.3", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@macklinu/prettier-config": "^0.1.0", "@types/bun": "1.3.4", "@types/react": "^19.2.7", "husky": "^9.1.7", "lint-staged": "^16.2.7", - "oxlint": "^1.32.0", + "oxlint": "^1.33.0", "prettier": "^3.7.4", }, "peerDependencies": { @@ -91,15 +91,19 @@ "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="], + "@effect-atom/atom": ["@effect-atom/atom@0.4.10", "", { "peerDependencies": { "@effect/experimental": "^0.57.0", "@effect/platform": "^0.93.0", "@effect/rpc": "^0.72.1", "effect": "^3.19.0" } }, "sha512-SEPDN4rbNsz0LTWfu4+a//OGh+zIpe64pJ4SJDFN1k07c9l171FTKCHIQHVwjK84vE+TxIj/Tg/JZzgxEkdMsQ=="], + + "@effect-atom/atom-react": ["@effect-atom/atom-react@0.4.4", "", { "dependencies": { "@effect-atom/atom": "^0.4.10" }, "peerDependencies": { "effect": "^3.19", "react": ">=18 <20", "scheduler": "*" } }, "sha512-EWT9WpB67GNCecxQXJdAZNVMhuk3VxpdDfojxg1wOgWmR5TPaGiXA74Jqa2Y9AEjSB0hMMAk7RNve2CoIikg2w=="], + "@effect/cli": ["@effect/cli@0.72.1", "", { "dependencies": { "ini": "^4.1.3", "toml": "^3.0.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.93.0", "@effect/printer": "^0.47.0", "@effect/printer-ansi": "^0.47.0", "effect": "^3.19.3" } }, "sha512-HGDMGD23TxFW9tCSX6g+M2u0robikMA0mP0SqeJMj7FWXTdcQ+cQsJE99bxi9iu+5YID7MIrVJMs8TUwXUV2sg=="], "@effect/cluster": ["@effect/cluster@0.50.6", "", { "peerDependencies": { "@effect/platform": "^0.92.1", "@effect/rpc": "^0.71.1", "@effect/sql": "^0.46.0", "@effect/workflow": "^0.11.5", "effect": "^3.18.4" } }, "sha512-JZTctj1SjMWZuG8RnkJCd1myAXmpDYEbLPURookJ6jXhpg8ZU29us49YtQa+MCUNmIr80qjeok/I1KVaQmJfrQ=="], "@effect/experimental": ["@effect/experimental@0.56.0", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.92.0", "effect": "^3.18.0", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-ZT9wTUVyDptzdkW4Tfvz5fNzygW9vt5jWcFmKI9SlhZMu9unVJgsBhxWCNYCyfPnxw3n/Z6SEKsqgt8iKQc4MA=="], - "@effect/language-service": ["@effect/language-service@0.62.1", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-K78mPtNpHqArLTXpYNASoqiqYUmPASrdnntSBVInMYmI39hyLkmTqk78bclzkfsIvJZBBS4vvsuWx7Etmz8rkA=="], + "@effect/language-service": ["@effect/language-service@0.62.3", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-YL+YI2BbMcnSUtgESHrXWChByQH7SMhQ5DoQVhXQo2ahY9/NQGSjJ59S9Hizlfe4VneIcmpk2FkCrUZ9VwyA2Q=="], - "@effect/platform": ["@effect/platform@0.93.7", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.11" } }, "sha512-kdF5qAGOz1hotfh9lws/cdBO6q0JT7d+V/u9OvBJsocnV3EbMKlVDXEHNg8Wv6ZLgFs0T1AA/NBF+Hks9eZVkA=="], + "@effect/platform": ["@effect/platform@0.93.8", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.12" } }, "sha512-xTEy6fyTy4ijmFC3afKgtvYtn/JyPoIov4ZSUWJZUv3VeOcUPNGrrqG6IJlWkXs3NhvSywKv7wc1kw3epCQVZw=="], "@effect/platform-bun": ["@effect/platform-bun@0.86.0", "", { "dependencies": { "@effect/platform-node-shared": "^0.56.0", "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/cluster": "^0.55.0", "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "@effect/sql": "^0.48.6", "effect": "^3.19.8" } }, "sha512-wg6eyzIxHuOd87rHjR18T7DjOtQ0dz8/cQD/pvZo8xFBSZ0AoZXqkHOKuU34DIRA4tp+BdBP6wjDDQYNQK/agQ=="], @@ -225,21 +229,21 @@ "@opentui/react": ["@opentui/react@0.1.60", "", { "dependencies": { "@opentui/core": "0.1.60", "react-reconciler": "^0.32.0" }, "peerDependencies": { "react": ">=19.0.0" } }, "sha512-2zZh/IeFFZJPzYJFp14fV9dmC4m8rwkQuNiApYfkYtinv2cwlxs73XD69QBxrgGIox+P4y58agr+o3iiLyQVYQ=="], - "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yrqPmZYu5Qb+49h0P5EXVIq8VxYkDDM6ZQrWzlh16+UGFcD8HOXs4oF3g9RyfaoAbShLCXooSQsM/Ifwx8E/eQ=="], + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw=="], - "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-pQRZrJG/2nAKc3IuocFbaFFbTDlQsjz2WfivRsMn0hw65EEsSuM84WMFMiAfLpTGyTICeUtHZLHlrM5lzVr36A=="], + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-2R9aH3kR0X2M30z5agGikv3tfNTi8/uLhU5/tYktu33VGUXpbf0OLZSlD25UEuwOKAlf3RVtzV5oDyjoq93JuQ=="], - "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-tyomSmU2DzwcTmbaWFmStHgVfRmJDDvqcIvcw4fRB1YlL2Qg/XaM4NJ0m2bdTap38gxD5FSxSgCo0DkQ8GTolg=="], + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yb/k8GaMDgnX2LyO6km33kKItZ/n573SlbiHBBFU2HmeU7tzEHL5jHkHQXXcysUkapmqHd7UsDhOZDqPmXaQRg=="], - "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0W46dRMaf71OGE4+Rd+GHfS1uF/UODl5Mef6871pMhN7opPGfTI2fKJxh9VzRhXeSYXW/Z1EuCq9yCfmIJq+5Q=="], + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-03pt9IO1C4ZfVOW6SQiOK26mzklAhLM3Kc79OXpX1kgZRlxk+rvFoMhlgCOzn7tEdrEgbePkBoxNnwDnJDFqJQ=="], - "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5+6myVCBOMvM62rDB9T3CARXUvIwhGqte6E+HoKRwYaqsxGUZ4bh3pItSgSFwHjLGPrvADS11qJUkk39eQQBzQ=="], + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Z7ImLWM50FoVXzYvyxUQ+QwBkBfRyK4YdLEGonyAGMp7iT3DksonDaTK9ODnJ1qHyAyAZCvuqXD7AEDsDvzDbA=="], - "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-qwQlwYYgVIC6ScjpUwiKKNyVdUlJckrfwPVpIjC9mvglIQeIjKuuyaDxUZWIOc/rEzeCV/tW6tcbehLkfEzqsw=="], + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-idb55Uzu5kkqqpMiVUfI9nP7zOqPZinQKsIRQAIU40wILcf/ijvhNZKIu3ucDMmye0n6IWOaSnxIRL5W2fNoUQ=="], - "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-7qYZF9CiXGtdv8Z/fBkgB5idD2Zokht67I5DKWH0fZS/2R232sDqW2JpWVkXltk0+9yFvmvJ0ouJgQRl9M3S2g=="], + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-wKKFt7cubfrLelNzdmDsNSmtBrlSUe1fWus587+uSxDZdpFbQ7liU0gsUlCbcHvym0H1Tc2O3K3cnLrgQORLPQ=="], - "@oxlint/win32-x64": ["@oxlint/win32-x64@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-XW1xqCj34MEGJlHteqasTZ/LmBrwYIgluhNW0aP+XWkn90+stKAq3W/40dvJKbMK9F7o09LPCuMVtUW7FIUuiA=="], + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ReyR8rNHjKNnO7dxGny9RCPELRAdhm3y780FNBcA07E1wvxSCkB+Mn5db0Pa5bRmxrsU/MTZ/aaBFa+ERXDdXw=="], "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], @@ -271,10 +275,6 @@ "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], - - "@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="], - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="], @@ -355,7 +355,7 @@ "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - "effect": ["effect@3.19.11", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-UTEj3c1s41Ha3uzSPKKvFBZaDjZ8ez00Q2NYWVm2mKh2LXeX8j6LTg1HcQHnmdUhOjr79KHmhVWYB/zbegLO1A=="], + "effect": ["effect@3.19.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7F9RGTrCTC3D7nh9Zw+3VlJWwZgo5k33KA+476BAaD0rKIXKZsY/jQ+ipyhR/Avo239Fi6GqAVFs1mqM1IJ7yg=="], "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], @@ -481,7 +481,7 @@ "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], - "oxlint": ["oxlint@1.32.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.32.0", "@oxlint/darwin-x64": "1.32.0", "@oxlint/linux-arm64-gnu": "1.32.0", "@oxlint/linux-arm64-musl": "1.32.0", "@oxlint/linux-x64-gnu": "1.32.0", "@oxlint/linux-x64-musl": "1.32.0", "@oxlint/win32-arm64": "1.32.0", "@oxlint/win32-x64": "1.32.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.8.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-HYDQCga7flsdyLMUIxTgSnEx5KBxpP9VINB8NgO+UjV80xBiTQXyVsvjtneMT3ZBLMbL0SlG/Dm03XQAsEshMA=="], + "oxlint": ["oxlint@1.33.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.33.0", "@oxlint/darwin-x64": "1.33.0", "@oxlint/linux-arm64-gnu": "1.33.0", "@oxlint/linux-arm64-musl": "1.33.0", "@oxlint/linux-x64-gnu": "1.33.0", "@oxlint/linux-x64-musl": "1.33.0", "@oxlint/win32-arm64": "1.33.0", "@oxlint/win32-x64": "1.33.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.9.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-4WCL0K8jiOshwJ8WrVk35VAuVaZHC0iX6asjKsrENOrynkAAGcTLLx0Urf0eXZ1Tq7r+qAe3Z9EyHMFPzVyUkg=="], "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="], diff --git a/package.json b/package.json index 91e0fd7..8353678 100644 --- a/package.json +++ b/package.json @@ -5,28 +5,28 @@ "private": true, "devDependencies": { "@changesets/cli": "^2.29.8", - "@effect/language-service": "^0.62.1", + "@effect/language-service": "^0.62.3", "@ianvs/prettier-plugin-sort-imports": "^4.7.0", "@macklinu/prettier-config": "^0.1.0", "@types/bun": "1.3.4", "@types/react": "^19.2.7", "husky": "^9.1.7", "lint-staged": "^16.2.7", - "oxlint": "^1.32.0", + "oxlint": "^1.33.0", "prettier": "^3.7.4" }, "peerDependencies": { "typescript": "^5.9.3" }, "dependencies": { + "@effect-atom/atom-react": "^0.4.4", "@effect/cli": "^0.72.1", - "@effect/platform": "^0.93.7", + "@effect/platform": "^0.93.8", "@effect/platform-bun": "^0.86.0", "@opentui/core": "^0.1.60", "@opentui/react": "^0.1.60", - "@tanstack/react-query": "^5.90.12", "cli-spinners": "^3.3.0", - "effect": "^3.19.11", + "effect": "^3.19.12", "react": "^19.2.3", "tiny-invariant": "^1.3.3", "zustand": "^5.0.9" diff --git a/src/GitHub.ts b/src/GitHub.ts index 7de2d53..dc340b8 100644 --- a/src/GitHub.ts +++ b/src/GitHub.ts @@ -1,7 +1,9 @@ +import { Atom } from '@effect-atom/atom-react' import * as BunContext from '@effect/platform-bun/BunContext' import * as Command from '@effect/platform/Command' import { pipe } from 'effect' import * as Data from 'effect/Data' +import * as Duration from 'effect/Duration' import * as Effect from 'effect/Effect' import * as Option from 'effect/Option' import * as Schema from 'effect/Schema' @@ -50,13 +52,7 @@ const runCommand = Effect.fn(function* (command: Command.Command) { export class PullRequests extends Effect.Service()('ghui/GitHub/PullRequests', { accessors: true, sync: () => ({ - list: Effect.fn('PullRequests.list')(function* ({ - author, - repo, - }: { - author: Option.Option - repo: Option.Option - }) { + list: Effect.fn('PullRequests.list')(function* ({ repo }: { repo: Option.Option }) { const args = yield* CommandArgs.builder() .append( 'gh', @@ -71,18 +67,27 @@ export class PullRequests extends Effect.Service()('ghui/GitHub/Pu const result = yield* CommandArgs.toCommand(args).pipe(Command.string) - const response = yield* Schema.decodeUnknown( + return yield* Schema.decodeUnknown( Schema.compose(Schema.parseJson(), Schema.Array(GitHubApiSchema.PullRequest)) )(result) - - return Option.match(author, { - onSome: (author) => response.filter((pr) => pr.user.login === author), - onNone: () => response, - }) }), }), dependencies: [BunContext.layer], -}) {} +}) { + static readonly runtime = Atom.runtime(PullRequests.Default) + + static readonly listAtom = Atom.family((repo: Option.Option) => + PullRequests.runtime + .atom(PullRequests.list({ repo }).pipe(Effect.provide(BunContext.layer))) + .pipe(Atom.setIdleTTL(Duration.decode('10 minutes'))) + ) +} + +type UpdateBranchOptions = { + number: number + repo: string + type?: 'rebase' | 'merge' +} export class PullRequest extends Effect.Service()('ghui/GitHub/PullRequest', { accessors: true, @@ -108,11 +113,7 @@ export class PullRequest extends Effect.Service()('ghui/GitHub/Pull number, repo, type = 'merge', - }: { - number: number - repo: string - type?: 'rebase' | 'merge' - }) { + }: UpdateBranchOptions) { const args = yield* CommandArgs.builder() .append('gh', 'pr', 'update-branch', number, '--repo', repo) .appendIf(type === 'rebase', () => '--rebase') @@ -132,7 +133,32 @@ export class PullRequest extends Effect.Service()('ghui/GitHub/Pull }), }), dependencies: [BunContext.layer], -}) {} +}) { + static readonly runtime = Atom.runtime(PullRequest.Default) + + static readonly markdownDescriptionAtom = Atom.family((number: Option.Option) => + PullRequest.runtime + .atom( + Effect.gen(function* () { + if (Option.isNone(number)) { + return Option.none() + } + const description = yield* PullRequest.markdownDescription({ number: number.value }) + if (!description) { + return Option.none() + } + return Option.some(description) + }).pipe(Effect.provide(BunContext.layer)) + ) + .pipe(Atom.setIdleTTL(Duration.decode('30 minutes'))) + ) + + static readonly updateBranchAtom = PullRequest.runtime.fn( + Effect.fnUntraced(function* (args: UpdateBranchOptions) { + return yield* PullRequest.updateBranch(args) + }, Effect.provide(BunContext.layer)) + ) +} export class Issues extends Effect.Service()('ghui/GitHub/Issues', { accessors: true, @@ -160,4 +186,12 @@ export class Issues extends Effect.Service()('ghui/GitHub/Issues', { }), dependencies: [BunContext.layer], }), -}) {} +}) { + static readonly runtime = Atom.runtime(Issues.Default) + + static readonly listAtom = Atom.family((repo: Option.Option) => + Issues.runtime + .atom(Issues.list({ repo }).pipe(Effect.provide(BunContext.layer))) + .pipe(Atom.setIdleTTL(Duration.decode('10 minutes'))) + ) +} diff --git a/src/Issues.tsx b/src/Issues.tsx index 47bcde7..87562c5 100644 --- a/src/Issues.tsx +++ b/src/Issues.tsx @@ -1,3 +1,4 @@ +import { Result, useAtomValue } from '@effect-atom/atom-react' import { TextAttributes } from '@opentui/core' import { useKeyboard } from '@opentui/react' import * as DateTime from 'effect/DateTime' @@ -6,29 +7,30 @@ import * as Option from 'effect/Option' import { useCallback, useEffect, useState } from 'react' import invariant from 'tiny-invariant' +import * as GitHub from './GitHub' import { Loading } from './Loading' -import * as Queries from './Queries' -import * as RQE from './ReactQueryEffect' +import { Repo } from './Repo' import { useCurrentRepo } from './RepoProvider' export const Issues = () => { const orgRepo = useCurrentRepo() - const repo = RQE.useQuery(Queries.getRepo(orgRepo)) - const issues = RQE.useQuery(Queries.issues({ repo: orgRepo })) + + const repo = useAtomValue(Repo.getRepoAtom(orgRepo)) + const issues = useAtomValue(GitHub.Issues.listAtom(orgRepo)) const [selectedIssueNumber, setSelectedIssueNumber] = useState>( Option.none() ) useEffect(() => { - if (issues.isSuccess) { - setSelectedIssueNumber(Option.fromNullable(issues.data[0]?.number)) + if (Result.isSuccess(issues)) { + setSelectedIssueNumber(Option.fromNullable(issues.value[0]?.number)) } - }, [issues.isSuccess, issues.data]) + }, [issues]) const selectedIssue = Match.value([issues, selectedIssueNumber]).pipe( - Match.when([{ isSuccess: true }, Option.isSome], ([{ data }, issueNumber]) => - Option.fromNullable(data.find((issue) => issue.number === issueNumber.value)) + Match.when([Result.isSuccess, Option.isSome], ([{ value: issues }, issueNumber]) => + Option.fromNullable(issues.find((issue) => issue.number === issueNumber.value)) ), Match.orElse(() => Option.none()) ) @@ -45,29 +47,33 @@ export const Issues = () => { - {Match.value(repo).pipe( - Match.when({ isLoading: true }, () => ), - Match.when({ isSuccess: true }, ({ data: repo }) => ( - <> - {Option.getOrThrow(repo)} - {'→'} - issues - {Option.isSome(selectedIssueNumber) && ( + {Result.builder(repo) + .onWaiting(() => ) + .onSuccess( + Option.match({ + onSome: (data) => ( <> + {data} {'→'} - #{selectedIssueNumber.value} + issues + {Option.isSome(selectedIssueNumber) && ( + <> + {'→'} + #{selectedIssueNumber.value} + + )} - )} - - )), - Match.orElse(() => null) - )} + ), + onNone: () => null, + }) + ) + .orNull()} {Match.value(issues).pipe( - Match.when({ isLoading: true }, () => ), - Match.when({ isSuccess: true }, ({ data: issues }) => ( + Match.when(Result.isWaiting, () => ), + Match.when(Result.isSuccess, ({ value: issues }) => ( - {Match.value(description).pipe( - Match.when({ isLoading: true }, () => ), - Match.when({ status: 'success' }, ({ data }) => - data.length > 0 ? ( - {data} - ) : ( - No PR description - ) - ), - Match.orElse(() => null) - )} + {Result.builder(description) + .onWaiting(() => ) + .onSuccess( + Option.match({ + onNone: () => No PR description, + onSome: (value) => {value}, + }) + ) + .onFailure(() => ( + Error fetching PR description + )) + .orNull()} diff --git a/src/Queries.ts b/src/Queries.ts deleted file mode 100644 index c9de9b7..0000000 --- a/src/Queries.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { skipToken } from '@tanstack/react-query' -import * as Option from 'effect/Option' - -import * as GitHub from './GitHub' -import * as RQE from './ReactQueryEffect' -import { Repo } from './Repo' -import { AppRuntime } from './Runtime' - -export const currentDirectoryRepo = () => - RQE.queryOptions({ - queryKey: ['currentDirectoryRepo'] as const, - queryFn: () => AppRuntime.runPromiseExit(Repo.currentRepo), - }) - -export const getRepo = (orgRepo: Option.Option) => - RQE.queryOptions({ - queryKey: ['repo', orgRepo] as const, - queryFn: () => AppRuntime.runPromiseExit(Repo.get(orgRepo)), - }) - -export const pullRequests = (options: { - author: Option.Option - repo: Option.Option -}) => - RQE.queryOptions({ - queryKey: ['pulls', options] as const, - queryFn: () => AppRuntime.runPromiseExit(GitHub.PullRequests.list(options)), - }) - -export const pullRequestMarkdownDescription = (options: { - number: Option.Option - repo: Option.Option -}) => { - return RQE.queryOptions({ - queryKey: ['pull', options, 'markdownDescription'], - queryFn: Option.match(options.number, { - onSome: (number) => () => - AppRuntime.runPromiseExit(GitHub.PullRequest.markdownDescription({ ...options, number })), - onNone: () => skipToken, - }), - }) -} - -export const issues = (options: { repo: Option.Option }) => - RQE.queryOptions({ - queryKey: ['issues', options] as const, - queryFn: () => AppRuntime.runPromiseExit(GitHub.Issues.list(options)), - }) diff --git a/src/ReactQueryEffect.ts b/src/ReactQueryEffect.ts deleted file mode 100644 index 1753730..0000000 --- a/src/ReactQueryEffect.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as RQ from '@tanstack/react-query' -import * as Exit from 'effect/Exit' - -export const queryOptions = < - A, - E, - TQueryFnData extends Exit.Exit, - TData = TQueryFnData, - TQueryKey extends RQ.QueryKey = RQ.QueryKey, ->( - options: RQ.UndefinedInitialDataOptions -) => RQ.queryOptions(options) - -interface UseEffectSuspenseQueryOptions< - A, - E, - TQueryFnData extends Exit.Exit, - TData = TQueryFnData, - TQueryKey extends RQ.QueryKey = readonly unknown[], -> extends RQ.UseSuspenseQueryOptions {} - -interface UseQueryOptions< - TQueryFnData extends Exit.Exit = Exit.Exit, - TData = TQueryFnData, - TQueryKey extends RQ.QueryKey = readonly unknown[], -> extends RQ.UseQueryOptions {} - -export const useQuery = ( - options: UseQueryOptions, Exit.Exit, TQueryKey> -) => { - const result = RQ.useQuery, never, Exit.Exit, TQueryKey>(options) - - if (result.isSuccess) { - if (Exit.isSuccess(result.data)) { - return { - ...result, - data: result.data.value, - } as const - } else { - return { - ...result, - isSuccess: false, - isError: true, - status: 'error', - data: undefined, - error: result.data.cause, - } as const - } - } - - return result -} - -export const useSuspenseQuery = ( - options: UseEffectSuspenseQueryOptions, Exit.Exit, TQueryKey> -) => { - const result = RQ.useSuspenseQuery, never, Exit.Exit, TQueryKey>(options) - - return result -} - -export interface UseEffectMutationOptions< - A, - E, - TVariables = void, - TOnMutateResult = unknown, -> extends Omit< - RQ.UseMutationOptions, Exit.Failure, TVariables, TOnMutateResult>, - 'mutationFn' | 'onSuccess' | 'onError' -> { - mutationFn: (variables: TVariables) => Promise> - onSuccess?: ( - data: A, - variables: TVariables, - onMutateResult: TOnMutateResult, - context: RQ.MutationFunctionContext - ) => Promise | unknown - onError?: ( - error: Exit.Failure, - variables: TVariables, - onMutateResult: TOnMutateResult | undefined, - context: RQ.MutationFunctionContext - ) => Promise | unknown -} - -export const useMutation = ( - options: UseEffectMutationOptions -): RQ.UseMutationResult, TVariables, TOnMutateResult> => { - const { mutationFn, onSuccess, ...restOptions } = options - - const wrappedMutationFn = async (variables: TVariables): Promise> => { - const exit = await mutationFn(variables) - if (Exit.isFailure(exit)) { - // Throw the failure so react-query treats it as an error - throw exit - } - return exit - } - - const result = RQ.useMutation, Exit.Failure, TVariables, TOnMutateResult>({ - ...restOptions, - mutationFn: wrappedMutationFn, - onSuccess(data, variables, onMutateResult, context) { - if (Exit.isSuccess(data)) { - onSuccess?.(data.value, variables, onMutateResult, context) - } - }, - }) - - if (result.isSuccess && result.data) { - if (Exit.isSuccess(result.data)) { - return { - ...result, - data: result.data.value, - } as RQ.UseMutationResult, TVariables, TOnMutateResult> - } - } - - return result as RQ.UseMutationResult, TVariables, TOnMutateResult> -} diff --git a/src/Repo.ts b/src/Repo.ts index f1a657c..6a63bb8 100644 --- a/src/Repo.ts +++ b/src/Repo.ts @@ -1,3 +1,4 @@ +import { Atom } from '@effect-atom/atom-react' import * as BunContext from '@effect/platform-bun/BunContext' import * as Command from '@effect/platform/Command' import * as Array from 'effect/Array' @@ -57,4 +58,14 @@ export class Repo extends Effect.Service()('ghui/Repo', { }), }), dependencies: [BunContext.layer], -}) {} +}) { + static readonly runtime = Atom.runtime(Repo.Default) + + static readonly currentRepoAtom = Repo.runtime.atom( + Repo.currentRepo.pipe(Effect.provide(BunContext.layer)) + ) + + static readonly getRepoAtom = Atom.family((orgRepo: Option.Option) => + Repo.runtime.atom(Repo.get(orgRepo).pipe(Effect.provide(BunContext.layer))) + ) +} diff --git a/src/index.tsx b/src/index.tsx index bcf9a34..e77fb6c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,10 @@ +import { useAtomValue } from '@effect-atom/atom-react' import * as Command from '@effect/cli/Command' import * as Options from '@effect/cli/Options' import * as BunContext from '@effect/platform-bun/BunContext' import * as BunRuntime from '@effect/platform-bun/BunRuntime' import { createCliRenderer } from '@opentui/core' import { createRoot, useAppContext, useKeyboard, useTerminalDimensions } from '@opentui/react' -import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query' import * as Effect from 'effect/Effect' import * as Equal from 'effect/Equal' import * as Match from 'effect/Match' @@ -19,8 +19,8 @@ import { DialogKind } from './DialogKind' import { Issues } from './Issues' import { Keybindings } from './Keybindings' import { PullRequests } from './PullRequests' -import * as Queries from './Queries' -import { RepoProvider, useCurrentRepo } from './RepoProvider' +import { Repo } from './Repo' +import { RepoProvider } from './RepoProvider' import { SplashScreen } from './SplashScreen' import { useToast } from './Toast' import * as View from './View' @@ -55,7 +55,7 @@ const App = ({ view: initialView }: { view: Option.Option }) => { Option.getOrElse(initialView, () => View.SplashScreen()) ) - useQuery(Queries.getRepo(useCurrentRepo())) + useAtomValue(Repo.currentRepoAtom) const { dialog, showDialog, closeDialog } = useCurrentDialog() @@ -141,15 +141,6 @@ const App = ({ view: initialView }: { view: Option.Option }) => { ) } -const createQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { - staleTime: Infinity, - }, - }, - }) - const renderApp = ( { repo, ...props }: ComponentProps & { repo: Option.Option } = { view: Option.none(), @@ -166,11 +157,9 @@ const renderApp = ( }) return createRoot(renderer).render( - - - - - + + + ) }) @@ -189,7 +178,19 @@ const ghuiPrs = Command.make( ({ author, repo }) => renderApp({ view: Option.some(View.PullRequests({ author })), repo }) ) -const cli = Command.run(ghui.pipe(Command.withSubcommands([ghuiPrs])), { +const ghuiIssues = Command.make( + 'issues', + { + repo: Options.text('repo').pipe( + Options.withSchema(Schema.String.pipe(Schema.pattern(/^\w[\w.-]*\/\w[\w.-]*$/))), + Options.withDescription('The repo in org/repo format to set as the current repo context'), + Options.optional + ), + }, + ({ repo }) => renderApp({ view: Option.some(View.Issues()), repo }) +) + +const cli = Command.run(ghui.pipe(Command.withSubcommands([ghuiPrs, ghuiIssues])), { name: 'ghui', version, })