Detailed changes
@@ -40,23 +40,22 @@
"remark-gfm": "^4.0.0",
"rxjs": "^7.8.2",
"tailwind-merge": "^3.5.0",
- "tailwindcss-animate": "^1.0.7"
+ "tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@graphql-codegen/cli": "^6.2.1",
"@graphql-codegen/typescript": "^5.0.9",
"@graphql-codegen/typescript-operations": "^5.0.9",
"@graphql-codegen/typescript-react-apollo": "^4.3.2",
- "@tailwindcss/typography": "^0.5.15",
+ "@tailwindcss/typography": "^0.5.19",
+ "@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react": "^6.0.1",
- "autoprefixer": "^10.4.20",
"oxfmt": "^0.42.0",
"oxlint": "^1.57.0",
"oxlint-tsgolint": "^0.18.1",
- "postcss": "^8.4.49",
- "tailwindcss": "^3.4.17",
+ "tailwindcss": "^4.2.2",
"typescript": "^6.0.2",
"vite": "^8.0.3"
},
@@ -83,9 +83,9 @@ importers:
tailwind-merge:
specifier: ^3.5.0
version: 3.5.0
- tailwindcss-animate:
- specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.4.19(yaml@2.8.3))
+ tw-animate-css:
+ specifier: ^1.4.0
+ version: 1.4.0
devDependencies:
'@graphql-codegen/cli':
specifier: ^6.2.1
@@ -100,8 +100,11 @@ importers:
specifier: ^4.3.2
version: 4.4.1(graphql@16.13.2)
'@tailwindcss/typography':
- specifier: ^0.5.15
- version: 0.5.19(tailwindcss@3.4.19(yaml@2.8.3))
+ specifier: ^0.5.19
+ version: 0.5.19(tailwindcss@4.2.2)
+ '@tailwindcss/vite':
+ specifier: ^4.2.2
+ version: 4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3))
'@types/react':
specifier: ^19.1.0
version: 19.2.14
@@ -110,10 +113,7 @@ importers:
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^6.0.1
- version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@1.21.7)(yaml@2.8.3))
- autoprefixer:
- specifier: ^10.4.20
- version: 10.4.27(postcss@8.5.8)
+ version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3))
oxfmt:
specifier: ^0.42.0
version: 0.42.0
@@ -123,25 +123,18 @@ importers:
oxlint-tsgolint:
specifier: ^0.18.1
version: 0.18.1
- postcss:
- specifier: ^8.4.49
- version: 8.5.8
tailwindcss:
- specifier: ^3.4.17
- version: 3.4.19(yaml@2.8.3)
+ specifier: ^4.2.2
+ version: 4.2.2
typescript:
specifier: ^6.0.2
version: 6.0.2
vite:
specifier: ^8.0.3
- version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@1.21.7)(yaml@2.8.3)
+ version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3)
packages:
- '@alloc/quick-lru@5.2.0':
- resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
- engines: {node: '>=10'}
-
'@apollo/client@4.1.6':
resolution: {integrity: sha512-ak8uzqmKeX3u9BziGf83RRyODAJKFkPG72hTNvEj4WjMWFmuKW2gGN1i3OfajKT6yuGjvo+n23ES2zqWDKFCZg==}
peerDependencies:
@@ -1387,11 +1380,105 @@ packages:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'}
+ '@tailwindcss/node@4.2.2':
+ resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
+
+ '@tailwindcss/oxide-android-arm64@4.2.2':
+ resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.2.2':
+ resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.2.2':
+ resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.2.2':
+ resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
+ resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
+ resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
+ resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
+ resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.2.2':
+ resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.2.2':
+ resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
+ resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
+ resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.2.2':
+ resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
+ engines: {node: '>= 20'}
+
'@tailwindcss/typography@0.5.19':
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+ '@tailwindcss/vite@4.2.2':
+ resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7 || ^8
+
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -1501,16 +1588,6 @@ packages:
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
- any-promise@1.3.0:
- resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
-
- anymatch@3.1.3:
- resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
- engines: {node: '>= 8'}
-
- arg@5.0.2:
- resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
-
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -1526,13 +1603,6 @@ packages:
resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==}
engines: {node: '>=8'}
- autoprefixer@10.4.27:
- resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==}
- engines: {node: ^10 || ^12 || >=14}
- hasBin: true
- peerDependencies:
- postcss: ^8.1.0
-
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@@ -1545,10 +1615,6 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- binary-extensions@2.3.0:
- resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
- engines: {node: '>=8'}
-
brace-expansion@5.0.5:
resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
engines: {node: 18 || 20 || >=22}
@@ -1569,10 +1635,6 @@ packages:
camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
- camelcase-css@2.0.1:
- resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
- engines: {node: '>= 6'}
-
caniuse-lite@1.0.30001781:
resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==}
@@ -1611,10 +1673,6 @@ packages:
chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
- chokidar@3.6.0:
- resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
- engines: {node: '>= 8.10.0'}
-
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -1651,10 +1709,6 @@ packages:
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
- commander@4.1.1:
- resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
- engines: {node: '>= 6'}
-
common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
engines: {node: '>=4.0.0'}
@@ -1747,16 +1801,10 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
- didyoumean@1.2.2:
- resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
-
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
- dlv@1.1.3:
- resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
-
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
@@ -1775,6 +1823,10 @@ packages:
emoticon@4.1.0:
resolution: {integrity: sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==}
+ enhanced-resolve@5.20.1:
+ resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
+ engines: {node: '>=10.13.0'}
+
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
@@ -1835,17 +1887,11 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
- fraction.js@5.3.4:
- resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
-
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
- function-bind@1.1.2:
- resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -1869,14 +1915,13 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
- glob-parent@6.0.2:
- resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
- engines: {node: '>=10.13.0'}
-
globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'}
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
graphql-config@5.1.6:
resolution: {integrity: sha512-fCkYnm4Kdq3un0YIM4BCZHVR5xl0UeLP6syxxO7KAstdY7QVyVvTHP0kRPDYEP1v08uwtJVgis5sj3IOTLOniQ==}
engines: {node: '>= 16.0.0'}
@@ -1917,10 +1962,6 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
- engines: {node: '>= 0.4'}
-
hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
@@ -2009,14 +2050,6 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
- is-binary-path@2.1.0:
- resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
- engines: {node: '>=8'}
-
- is-core-module@2.16.1:
- resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
- engines: {node: '>= 0.4'}
-
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
@@ -2079,10 +2112,6 @@ packages:
peerDependencies:
ws: '*'
- jiti@1.21.7:
- resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
- hasBin: true
-
jiti@2.6.1:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
@@ -2185,10 +2214,6 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
- lilconfig@3.1.3:
- resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
- engines: {node: '>=14'}
-
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -2231,6 +2256,9 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
map-cache@0.2.2:
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
engines: {node: '>=0.10.0'}
@@ -2399,9 +2427,6 @@ packages:
resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
engines: {node: ^18.17.0 || >=20.5.0}
- mz@2.7.0:
- resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
-
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -2430,18 +2455,6 @@ packages:
resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==}
engines: {node: '>=0.10.0'}
- normalize-path@3.0.0:
- resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
- engines: {node: '>=0.10.0'}
-
- object-assign@4.1.1:
- resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
- engines: {node: '>=0.10.0'}
-
- object-hash@3.0.0:
- resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
- engines: {node: '>= 6'}
-
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
@@ -2499,9 +2512,6 @@ packages:
path-case@3.0.4:
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
- path-parse@1.0.7:
- resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
path-root-regex@0.1.2:
resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==}
engines: {node: '>=0.10.0'}
@@ -2525,61 +2535,10 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
- pify@2.3.0:
- resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
- engines: {node: '>=0.10.0'}
-
- pirates@4.0.7:
- resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
- engines: {node: '>= 6'}
-
- postcss-import@15.1.0:
- resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- postcss: ^8.0.0
-
- postcss-js@4.1.0:
- resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
- engines: {node: ^12 || ^14 || >= 16}
- peerDependencies:
- postcss: ^8.4.21
-
- postcss-load-config@6.0.1:
- resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
- engines: {node: '>= 18'}
- peerDependencies:
- jiti: '>=1.21.0'
- postcss: '>=8.0.9'
- tsx: ^4.8.1
- yaml: ^2.4.2
- peerDependenciesMeta:
- jiti:
- optional: true
- postcss:
- optional: true
- tsx:
- optional: true
- yaml:
- optional: true
-
- postcss-nested@6.2.0:
- resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
- engines: {node: '>=12.0'}
- peerDependencies:
- postcss: ^8.2.14
-
postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
- postcss-selector-parser@6.1.2:
- resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
- engines: {node: '>=4'}
-
- postcss-value-parser@4.2.0:
- resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
-
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -2645,13 +2604,6 @@ packages:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
- read-cache@1.0.0:
- resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
-
- readdirp@3.6.0:
- resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
- engines: {node: '>=8.10.0'}
-
rehype-autolink-headings@7.1.0:
resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==}
@@ -2704,11 +2656,6 @@ packages:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
- resolve@1.22.11:
- resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
- engines: {node: '>= 0.4'}
- hasBin: true
-
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -2816,19 +2763,10 @@ packages:
style-to-object@1.0.14:
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
- sucrase@3.35.1:
- resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
- engines: {node: '>=16 || 14 >=14.17'}
- hasBin: true
-
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
- supports-preserve-symlinks-flag@1.0.0:
- resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
- engines: {node: '>= 0.4'}
-
swap-case@2.0.2:
resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==}
@@ -2839,22 +2777,12 @@ packages:
tailwind-merge@3.5.0:
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
- tailwindcss-animate@1.0.7:
- resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
- peerDependencies:
- tailwindcss: '>=3.0.0 || insiders'
-
- tailwindcss@3.4.19:
- resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
- engines: {node: '>=14.0.0'}
- hasBin: true
-
- thenify-all@1.6.0:
- resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
- engines: {node: '>=0.8'}
+ tailwindcss@4.2.2:
+ resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
- thenify@3.3.1:
- resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ tapable@2.3.2:
+ resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
+ engines: {node: '>=6'}
timeout-signal@2.0.0:
resolution: {integrity: sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==}
@@ -2881,9 +2809,6 @@ packages:
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
- ts-interface-checker@0.1.13:
- resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
-
ts-log@2.2.7:
resolution: {integrity: sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg==}
@@ -2893,6 +2818,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tw-animate-css@1.4.0:
+ resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
+
typescript@6.0.2:
resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
engines: {node: '>=14.17'}
@@ -3094,8 +3022,6 @@ packages:
snapshots:
- '@alloc/quick-lru@5.2.0': {}
-
'@apollo/client@4.1.6(graphql-ws@6.0.8(graphql@16.13.2)(ws@8.20.0))(graphql@16.13.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2)
@@ -4303,10 +4229,78 @@ snapshots:
'@sindresorhus/is@4.6.0': {}
- '@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(yaml@2.8.3))':
+ '@tailwindcss/node@4.2.2':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.20.1
+ jiti: 2.6.1
+ lightningcss: 1.32.0
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.2.2
+
+ '@tailwindcss/oxide-android-arm64@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-wasm32-wasi@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
+ optional: true
+
+ '@tailwindcss/oxide@4.2.2':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.2.2
+ '@tailwindcss/oxide-darwin-arm64': 4.2.2
+ '@tailwindcss/oxide-darwin-x64': 4.2.2
+ '@tailwindcss/oxide-freebsd-x64': 4.2.2
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2
+ '@tailwindcss/oxide-linux-arm64-musl': 4.2.2
+ '@tailwindcss/oxide-linux-x64-gnu': 4.2.2
+ '@tailwindcss/oxide-linux-x64-musl': 4.2.2
+ '@tailwindcss/oxide-wasm32-wasi': 4.2.2
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
+ '@tailwindcss/oxide-win32-x64-msvc': 4.2.2
+
+ '@tailwindcss/typography@0.5.19(tailwindcss@4.2.2)':
dependencies:
postcss-selector-parser: 6.0.10
- tailwindcss: 3.4.19(yaml@2.8.3)
+ tailwindcss: 4.2.2
+
+ '@tailwindcss/vite@4.2.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3))':
+ dependencies:
+ '@tailwindcss/node': 4.2.2
+ '@tailwindcss/oxide': 4.2.2
+ tailwindcss: 4.2.2
+ vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3)
'@tybys/wasm-util@0.10.1':
dependencies:
@@ -4355,10 +4349,10 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
- '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@1.21.7)(yaml@2.8.3))':
+ '@vitejs/plugin-react@6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.7
- vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@1.21.7)(yaml@2.8.3)
+ vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3)
'@whatwg-node/disposablestack@0.0.6':
dependencies:
@@ -4411,15 +4405,6 @@ snapshots:
ansi-styles@6.2.3: {}
- any-promise@1.3.0: {}
-
- anymatch@3.1.3:
- dependencies:
- normalize-path: 3.0.0
- picomatch: 2.3.2
-
- arg@5.0.2: {}
-
argparse@2.0.1: {}
aria-hidden@1.2.6:
@@ -4430,23 +4415,12 @@ snapshots:
auto-bind@4.0.0: {}
- autoprefixer@10.4.27(postcss@8.5.8):
- dependencies:
- browserslist: 4.28.1
- caniuse-lite: 1.0.30001781
- fraction.js: 5.3.4
- picocolors: 1.1.1
- postcss: 8.5.8
- postcss-value-parser: 4.2.0
-
bail@2.0.2: {}
balanced-match@4.0.4: {}
baseline-browser-mapping@2.10.12: {}
- binary-extensions@2.3.0: {}
-
brace-expansion@5.0.5:
dependencies:
balanced-match: 4.0.4
@@ -4470,8 +4444,6 @@ snapshots:
pascal-case: 3.1.2
tslib: 2.8.1
- camelcase-css@2.0.1: {}
-
caniuse-lite@1.0.30001781: {}
capital-case@1.0.4:
@@ -4527,18 +4499,6 @@ snapshots:
chardet@2.1.1: {}
- chokidar@3.6.0:
- dependencies:
- anymatch: 3.1.3
- braces: 3.0.3
- glob-parent: 5.1.2
- is-binary-path: 2.1.0
- is-glob: 4.0.3
- normalize-path: 3.0.0
- readdirp: 3.6.0
- optionalDependencies:
- fsevents: 2.3.3
-
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -4572,8 +4532,6 @@ snapshots:
comma-separated-tokens@2.0.3: {}
- commander@4.1.1: {}
-
common-tags@1.8.2: {}
constant-case@3.0.4:
@@ -4642,14 +4600,10 @@ snapshots:
dependencies:
dequal: 2.0.3
- didyoumean@1.2.2: {}
-
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
- dlv@1.1.3: {}
-
dot-case@3.0.4:
dependencies:
no-case: 3.0.4
@@ -4665,6 +4619,11 @@ snapshots:
emoticon@4.1.0: {}
+ enhanced-resolve@5.20.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.2
+
entities@6.0.1: {}
env-paths@2.2.1: {}
@@ -4714,13 +4673,9 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
- fraction.js@5.3.4: {}
-
fsevents@2.3.3:
optional: true
- function-bind@1.1.2: {}
-
gensync@1.0.0-beta.2: {}
get-caller-file@2.0.5: {}
@@ -4735,10 +4690,6 @@ snapshots:
dependencies:
is-glob: 4.0.3
- glob-parent@6.0.2:
- dependencies:
- is-glob: 4.0.3
-
globby@11.1.0:
dependencies:
array-union: 2.1.0
@@ -4748,6 +4699,8 @@ snapshots:
merge2: 1.4.1
slash: 3.0.0
+ graceful-fs@4.2.11: {}
+
graphql-config@5.1.6(@types/node@25.5.0)(graphql@16.13.2)(typescript@6.0.2):
dependencies:
'@graphql-tools/graphql-file-loader': 8.1.12(graphql@16.13.2)
@@ -4785,10 +4738,6 @@ snapshots:
has-flag@4.0.0: {}
- hasown@2.0.2:
- dependencies:
- function-bind: 1.1.2
-
hast-util-from-parse5@8.0.3:
dependencies:
'@types/hast': 3.0.4
@@ -4928,14 +4877,6 @@ snapshots:
is-arrayish@0.2.1: {}
- is-binary-path@2.1.0:
- dependencies:
- binary-extensions: 2.3.0
-
- is-core-module@2.16.1:
- dependencies:
- hasown: 2.0.2
-
is-decimal@2.0.1: {}
is-extglob@2.1.1: {}
@@ -4984,8 +4925,6 @@ snapshots:
dependencies:
ws: 8.20.0
- jiti@1.21.7: {}
-
jiti@2.6.1: {}
js-tokens@4.0.0: {}
@@ -5054,8 +4993,6 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
- lilconfig@3.1.3: {}
-
lines-and-columns@1.2.4: {}
listr2@9.0.5:
@@ -5106,6 +5043,10 @@ snapshots:
dependencies:
react: 19.2.4
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
map-cache@0.2.2: {}
markdown-table@3.0.4: {}
@@ -5475,12 +5416,6 @@ snapshots:
mute-stream@2.0.0: {}
- mz@2.7.0:
- dependencies:
- any-promise: 1.3.0
- object-assign: 4.1.1
- thenify-all: 1.6.0
-
nanoid@3.3.11: {}
no-case@3.0.4:
@@ -5509,12 +5444,6 @@ snapshots:
dependencies:
remove-trailing-separator: 1.1.0
- normalize-path@3.0.0: {}
-
- object-assign@4.1.1: {}
-
- object-hash@3.0.0: {}
-
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
@@ -5632,8 +5561,6 @@ snapshots:
dot-case: 3.0.4
tslib: 2.8.1
- path-parse@1.0.7: {}
-
path-root-regex@0.1.2: {}
path-root@0.1.1:
@@ -5648,47 +5575,11 @@ snapshots:
picomatch@4.0.4: {}
- pify@2.3.0: {}
-
- pirates@4.0.7: {}
-
- postcss-import@15.1.0(postcss@8.5.8):
- dependencies:
- postcss: 8.5.8
- postcss-value-parser: 4.2.0
- read-cache: 1.0.0
- resolve: 1.22.11
-
- postcss-js@4.1.0(postcss@8.5.8):
- dependencies:
- camelcase-css: 2.0.1
- postcss: 8.5.8
-
- postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.3):
- dependencies:
- lilconfig: 3.1.3
- optionalDependencies:
- jiti: 1.21.7
- postcss: 8.5.8
- yaml: 2.8.3
-
- postcss-nested@6.2.0(postcss@8.5.8):
- dependencies:
- postcss: 8.5.8
- postcss-selector-parser: 6.1.2
-
postcss-selector-parser@6.0.10:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss-selector-parser@6.1.2:
- dependencies:
- cssesc: 3.0.0
- util-deprecate: 1.0.2
-
- postcss-value-parser@4.2.0: {}
-
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -5759,14 +5650,6 @@ snapshots:
react@19.2.4: {}
- read-cache@1.0.0:
- dependencies:
- pify: 2.3.0
-
- readdirp@3.6.0:
- dependencies:
- picomatch: 2.3.2
-
rehype-autolink-headings@7.1.0:
dependencies:
'@types/hast': 3.0.4
@@ -5858,12 +5741,6 @@ snapshots:
resolve-from@5.0.0: {}
- resolve@1.22.11:
- dependencies:
- is-core-module: 2.16.1
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
-
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -5992,22 +5869,10 @@ snapshots:
dependencies:
inline-style-parser: 0.2.7
- sucrase@3.35.1:
- dependencies:
- '@jridgewell/gen-mapping': 0.3.13
- commander: 4.1.1
- lines-and-columns: 1.2.4
- mz: 2.7.0
- pirates: 4.0.7
- tinyglobby: 0.2.15
- ts-interface-checker: 0.1.13
-
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
- supports-preserve-symlinks-flag@1.0.0: {}
-
swap-case@2.0.2:
dependencies:
tslib: 2.8.1
@@ -6020,45 +5885,9 @@ snapshots:
tailwind-merge@3.5.0: {}
- tailwindcss-animate@1.0.7(tailwindcss@3.4.19(yaml@2.8.3)):
- dependencies:
- tailwindcss: 3.4.19(yaml@2.8.3)
-
- tailwindcss@3.4.19(yaml@2.8.3):
- dependencies:
- '@alloc/quick-lru': 5.2.0
- arg: 5.0.2
- chokidar: 3.6.0
- didyoumean: 1.2.2
- dlv: 1.1.3
- fast-glob: 3.3.3
- glob-parent: 6.0.2
- is-glob: 4.0.3
- jiti: 1.21.7
- lilconfig: 3.1.3
- micromatch: 4.0.8
- normalize-path: 3.0.0
- object-hash: 3.0.0
- picocolors: 1.1.1
- postcss: 8.5.8
- postcss-import: 15.1.0(postcss@8.5.8)
- postcss-js: 4.1.0(postcss@8.5.8)
- postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8)(yaml@2.8.3)
- postcss-nested: 6.2.0(postcss@8.5.8)
- postcss-selector-parser: 6.1.2
- resolve: 1.22.11
- sucrase: 3.35.1
- transitivePeerDependencies:
- - tsx
- - yaml
-
- thenify-all@1.6.0:
- dependencies:
- thenify: 3.3.1
+ tailwindcss@4.2.2: {}
- thenify@3.3.1:
- dependencies:
- any-promise: 1.3.0
+ tapable@2.3.2: {}
timeout-signal@2.0.0: {}
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
@@ -40,7 +40,7 @@ export function BugRow({
const authorHref = repo ? `/${repo}/user/${author.humanId}` : `/user/${author.humanId}`;
return (
- <div className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0 hover:bg-muted/30">
+ <div className="border-border hover:bg-muted/30 flex items-start gap-3 border-b px-4 py-3 last:border-0">
<StatusIcon
className={
isOpen
@@ -53,7 +53,7 @@ export function BugRow({
<div className="flex flex-wrap items-baseline gap-2">
<Link
to={issueHref}
- className="font-medium text-foreground hover:text-primary hover:underline"
+ className="text-foreground hover:text-primary font-medium hover:underline"
>
{title}
</Link>
@@ -66,7 +66,7 @@ export function BugRow({
/>
))}
</div>
- <p className="mt-0.5 text-xs text-muted-foreground">
+ <p className="text-muted-foreground mt-0.5 text-xs">
#{humanId} opened {formatDistanceToNow(new Date(createdAt), { addSuffix: true })} by{" "}
<Link to={authorHref} className="hover:underline">
{author.displayName}
@@ -75,7 +75,7 @@ export function BugRow({
</div>
{commentCount > 0 && (
- <div className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
+ <div className="text-muted-foreground flex shrink-0 items-center gap-1 text-xs">
<MessageSquare className="size-3.5" />
{commentCount}
</div>
@@ -80,14 +80,14 @@ export function CommentBox({ bugPrefix, bugStatus, ref_ }: CommentBoxProps) {
</AvatarFallback>
</Avatar>
- <div className="min-w-0 flex-1 rounded-md border border-border">
+ <div className="border-border min-w-0 flex-1 rounded-md border">
{/* Write / Preview tabs */}
- <div className="flex border-b border-border">
+ <div className="border-border flex border-b">
<button
onClick={() => setPreview(false)}
className={`px-4 py-2 text-sm font-medium transition-colors ${
!preview
- ? "border-b-2 border-primary text-foreground"
+ ? "border-primary text-foreground border-b-2"
: "text-muted-foreground hover:text-foreground"
}`}
>
@@ -98,7 +98,7 @@ export function CommentBox({ bugPrefix, bugStatus, ref_ }: CommentBoxProps) {
disabled={!hasMessage}
className={`px-4 py-2 text-sm font-medium transition-colors disabled:opacity-40 ${
preview
- ? "border-b-2 border-primary text-foreground"
+ ? "border-primary text-foreground border-b-2"
: "text-muted-foreground hover:text-foreground"
}`}
>
@@ -120,7 +120,7 @@ export function CommentBox({ bugPrefix, bugStatus, ref_ }: CommentBoxProps) {
/>
)}
- <div className="flex items-center justify-end gap-2 border-t border-border px-3 py-2">
+ <div className="border-border flex items-center justify-end gap-2 border-t px-3 py-2">
<Button
variant="outline"
size="sm"
@@ -190,28 +190,28 @@ export function IssueFilters({
<Tag className="size-3.5" />
Labels
{selectedLabels.length > 0 && (
- <span className="rounded-full bg-muted px-1.5 py-0.5 text-xs leading-none">
+ <span className="bg-muted rounded-full px-1.5 py-0.5 text-xs leading-none">
{selectedLabels.length}
</span>
)}
<ChevronDown className="size-3" />
</button>
</PopoverTrigger>
- <PopoverContent align="end" className="w-56 bg-popover p-0 shadow-lg">
+ <PopoverContent align="end" className="bg-popover w-56 p-0 shadow-lg">
{/* Search */}
- <div className="flex items-center gap-2 border-b border-border px-3 py-2">
- <Search className="size-3.5 shrink-0 text-muted-foreground" />
+ <div className="border-border flex items-center gap-2 border-b px-3 py-2">
+ <Search className="text-muted-foreground size-3.5 shrink-0" />
<input
autoFocus
placeholder="Search labelsβ¦"
value={labelSearch}
onChange={(e) => setLabelSearch(e.target.value)}
- className="w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
+ className="placeholder:text-muted-foreground w-full bg-transparent text-sm outline-hidden"
/>
</div>
<div className="max-h-64 overflow-y-auto p-1">
{sortedLabels.length === 0 && (
- <p className="px-2 py-3 text-center text-xs text-muted-foreground">No labels found</p>
+ <p className="text-muted-foreground px-2 py-3 text-center text-xs">No labels found</p>
)}
{sortedLabels.map((label) => {
const active = selectedLabels.includes(label.name);
@@ -219,7 +219,7 @@ export function IssueFilters({
<button
key={label.name}
onClick={() => toggleLabel(label.name)}
- className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted"
+ className="hover:bg-muted flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm"
>
<span
className="size-2 shrink-0 rounded-full"
@@ -229,16 +229,16 @@ export function IssueFilters({
}}
/>
<LabelBadge name={label.name} color={label.color} />
- {active && <Check className="ml-auto size-3.5 shrink-0 text-foreground" />}
+ {active && <Check className="text-foreground ml-auto size-3.5 shrink-0" />}
</button>
);
})}
</div>
{selectedLabels.length > 0 && (
- <div className="border-t border-border p-1">
+ <div className="border-border border-t p-1">
<button
onClick={() => onLabelsChange([])}
- className="flex w-full items-center gap-1.5 rounded px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted"
+ className="text-muted-foreground hover:bg-muted flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 text-xs"
>
<X className="size-3" />
Clear labels
@@ -285,21 +285,21 @@ export function IssueFilters({
<ChevronDown className="size-3" />
</button>
</PopoverTrigger>
- <PopoverContent align="end" className="w-56 bg-popover p-0 shadow-lg">
+ <PopoverContent align="end" className="bg-popover w-56 p-0 shadow-lg">
{/* Search */}
- <div className="flex items-center gap-2 border-b border-border px-3 py-2">
- <Search className="size-3.5 shrink-0 text-muted-foreground" />
+ <div className="border-border flex items-center gap-2 border-b px-3 py-2">
+ <Search className="text-muted-foreground size-3.5 shrink-0" />
<input
autoFocus
placeholder="Search authorsβ¦"
value={authorSearch}
onChange={(e) => setAuthorSearch(e.target.value)}
- className="w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
+ className="placeholder:text-muted-foreground w-full bg-transparent text-sm outline-hidden"
/>
</div>
<div className="max-h-64 overflow-y-auto p-1">
{visibleIdentities.length === 0 && (
- <p className="px-2 py-3 text-center text-xs text-muted-foreground">
+ <p className="text-muted-foreground px-2 py-3 text-center text-xs">
No authors found
</p>
)}
@@ -314,7 +314,7 @@ export function IssueFilters({
active ? null : authorQueryValue(identity),
)
}
- className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted"
+ className="hover:bg-muted flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm"
>
<Avatar className="size-5 shrink-0">
<AvatarImage src={identity.avatarUrl ?? undefined} alt={identity.displayName} />
@@ -325,26 +325,26 @@ export function IssueFilters({
<div className="min-w-0 flex-1 text-left">
<div className="truncate">{identity.displayName}</div>
{identity.login && identity.login !== identity.displayName && (
- <div className="truncate text-xs text-muted-foreground">
+ <div className="text-muted-foreground truncate text-xs">
@{identity.login}
</div>
)}
</div>
- {active && <Check className="size-3.5 shrink-0 text-foreground" />}
+ {active && <Check className="text-foreground size-3.5 shrink-0" />}
</button>
);
})}
{!isSearching && allIdentities.length > INITIAL_AUTHOR_LIMIT && (
- <p className="px-2 py-1.5 text-center text-xs text-muted-foreground">
+ <p className="text-muted-foreground px-2 py-1.5 text-center text-xs">
{allIdentities.length - visibleIdentities.length} more β type to search
</p>
)}
</div>
{selectedAuthorId && (
- <div className="border-t border-border p-1">
+ <div className="border-border border-t p-1">
<button
onClick={() => onAuthorChange(null, null)}
- className="flex w-full items-center gap-1.5 rounded px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted"
+ className="text-muted-foreground hover:bg-muted flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 text-xs"
>
<X className="size-3" />
Clear author
@@ -370,16 +370,16 @@ export function IssueFilters({
<ChevronDown className="size-3" />
</button>
</PopoverTrigger>
- <PopoverContent align="end" className="w-56 bg-popover p-1 shadow-lg">
+ <PopoverContent align="end" className="bg-popover w-56 p-1 shadow-lg">
{SORT_OPTIONS.map((opt) => (
<button
key={opt.value}
onClick={() => onSortChange(opt.value)}
- className="flex w-full items-center gap-2 whitespace-nowrap rounded px-2 py-1.5 text-sm hover:bg-muted"
+ className="hover:bg-muted flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm whitespace-nowrap"
>
{opt.label}
{sort === opt.value && (
- <Check className="ml-auto size-3.5 shrink-0 text-foreground" />
+ <Check className="text-foreground ml-auto size-3.5 shrink-0" />
)}
</button>
))}
@@ -46,7 +46,7 @@ export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps
return (
<div>
<div className="mb-2 flex items-center justify-between">
- <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
+ <h3 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
Labels
</h3>
{user && validLabels.length > 0 && (
@@ -57,7 +57,7 @@ export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps
</button>
</PopoverTrigger>
<PopoverContent align="end" className="w-56 p-2">
- <p className="mb-2 px-2 text-xs font-medium text-muted-foreground">Apply labels</p>
+ <p className="text-muted-foreground mb-2 px-2 text-xs font-medium">Apply labels</p>
<div className="space-y-1">
{validLabels.map((label) => {
const active = currentNames.has(label.name);
@@ -67,7 +67,7 @@ export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps
onClick={() => {
void toggleLabel(label.name);
}}
- className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted"
+ className="hover:bg-muted flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm"
>
<span
className={`size-2 rounded-full border-2 transition-colors ${
@@ -94,7 +94,7 @@ export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps
</div>
{currentLabels.length === 0 ? (
- <p className="text-sm text-muted-foreground">None yet</p>
+ <p className="text-muted-foreground text-sm">None yet</p>
) : (
<div className="flex flex-wrap gap-1">
{currentLabels.map((label) => (
@@ -299,13 +299,13 @@ export function QueryInput({ value, onChange, onSubmit, placeholder, className }
)}
onClick={() => inputRef.current?.focus()}
>
- <Search className="pointer-events-none absolute left-3 size-4 shrink-0 text-muted-foreground" />
+ <Search className="text-muted-foreground pointer-events-none absolute left-3 size-4 shrink-0" />
{/* Colored backdrop β same font/size/padding as the input. aria-hidden so
screen readers only see the real input, not the duplicate text. */}
<div
aria-hidden
- className="pointer-events-none absolute inset-0 flex items-center overflow-hidden whitespace-pre pl-9 pr-3 font-mono text-sm text-foreground"
+ className="text-foreground pointer-events-none absolute inset-0 flex items-center overflow-hidden pr-3 pl-9 font-mono text-sm whitespace-pre"
>
{value === "" ? null : segments.map((seg, i) => renderSegment(seg, i))}
</div>
@@ -320,7 +320,7 @@ export function QueryInput({ value, onChange, onSubmit, placeholder, className }
onChange={handleChange}
onKeyDown={handleKeyDown}
onSelect={handleSelect}
- className="relative w-full bg-transparent py-2 pl-9 pr-3 font-mono text-sm text-transparent caret-foreground outline-none placeholder:font-sans placeholder:text-muted-foreground"
+ className="caret-foreground placeholder:text-muted-foreground relative w-full bg-transparent py-2 pr-3 pl-9 font-mono text-sm text-transparent outline-hidden placeholder:font-sans"
spellCheck={false}
autoComplete="off"
/>
@@ -329,7 +329,7 @@ export function QueryInput({ value, onChange, onSubmit, placeholder, className }
Uses onMouseDown+preventDefault so clicking a suggestion doesn't blur
the input before the click registers (classic focus-race problem). */}
{showDropdown && (
- <div className="absolute left-0 right-0 top-full z-50 mt-1 overflow-hidden rounded-md border border-border bg-popover shadow-md">
+ <div className="border-border bg-popover absolute top-full right-0 left-0 z-50 mt-1 overflow-hidden rounded-md border shadow-md">
{suggestions.map((s, i) => (
<button
key={s.completedToken}
@@ -350,7 +350,7 @@ export function QueryInput({ value, onChange, onSubmit, placeholder, className }
)}
<span className="font-mono">{s.completedToken}</span>
{s.display !== s.completedToken.split(":")[1]?.replace(/"/g, "") && (
- <span className="ml-auto text-xs text-muted-foreground">{s.display}</span>
+ <span className="text-muted-foreground ml-auto text-xs">{s.display}</span>
)}
</button>
))}
@@ -95,22 +95,22 @@ function CommentItem({ item, bugPrefix }: { item: CommentItem; bugPrefix: string
</AvatarFallback>
</Avatar>
- <div className="min-w-0 flex-1 rounded-md border border-border">
- <div className="flex items-center gap-2 border-b border-border bg-muted/40 px-4 py-2 text-sm">
+ <div className="border-border min-w-0 flex-1 rounded-md border">
+ <div className="border-border bg-muted/40 flex items-center gap-2 border-b px-4 py-2 text-sm">
<Link
to={repo ? `/${repo}/user/${item.author.humanId}` : `/user/${item.author.humanId}`}
- className="font-medium text-foreground hover:underline"
+ className="text-foreground font-medium hover:underline"
>
{item.author.displayName}
</Link>
<span className="text-muted-foreground">
{formatDistanceToNow(new Date(item.createdAt), { addSuffix: true })}
</span>
- {item.edited && !editing && <span className="text-xs text-muted-foreground">edited</span>}
+ {item.edited && !editing && <span className="text-muted-foreground text-xs">edited</span>}
{canEdit && !editing && (
<button
onClick={() => setEditing(true)}
- className="ml-auto rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground"
+ className="text-muted-foreground hover:bg-muted hover:text-foreground ml-auto rounded-sm px-1.5 py-0.5 text-xs"
>
Edit
</button>
@@ -147,7 +147,7 @@ function CommentItem({ item, bugPrefix }: { item: CommentItem; bugPrefix: string
{item.message ? (
<Markdown content={item.message} />
) : (
- <p className="text-sm italic text-muted-foreground">No description provided.</p>
+ <p className="text-muted-foreground text-sm italic">No description provided.</p>
)}
</div>
)}
@@ -164,7 +164,7 @@ type TitleChangeItem = Extract<TimelineNode, { __typename: "BugSetTitleTimelineI
function EventRow({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) {
return (
- <div className="flex items-center gap-3 pl-2 text-sm text-muted-foreground">
+ <div className="text-muted-foreground flex items-center gap-3 pl-2 text-sm">
<span className="flex size-8 shrink-0 items-center justify-center">{icon}</span>
{children}
</div>
@@ -178,7 +178,7 @@ function LabelChangeItem({ item }: { item: LabelChangeItem }) {
<span>
<Link
to={repo ? `/${repo}/user/${item.author.humanId}` : `/user/${item.author.humanId}`}
- className="font-medium text-foreground hover:underline"
+ className="text-foreground font-medium hover:underline"
>
{item.author.displayName}
</Link>{" "}
@@ -220,7 +220,7 @@ function StatusChangeItem({ item }: { item: StatusChangeItem }) {
<span>
<Link
to={repo ? `/${repo}/user/${item.author.humanId}` : `/user/${item.author.humanId}`}
- className="font-medium text-foreground hover:underline"
+ className="text-foreground font-medium hover:underline"
>
{item.author.displayName}
</Link>{" "}
@@ -238,12 +238,12 @@ function TitleChangeItem({ item }: { item: TitleChangeItem }) {
<span>
<Link
to={repo ? `/${repo}/user/${item.author.humanId}` : `/user/${item.author.humanId}`}
- className="font-medium text-foreground hover:underline"
+ className="text-foreground font-medium hover:underline"
>
{item.author.displayName}
</Link>{" "}
changed the title from <span className="line-through">{item.was}</span> to{" "}
- <span className="font-medium text-foreground">{item.title}</span>{" "}
+ <span className="text-foreground font-medium">{item.title}</span>{" "}
{formatDistanceToNow(new Date(item.date), { addSuffix: true })}
</span>
</EventRow>
@@ -87,14 +87,14 @@ export function TitleEditor({ bugPrefix, title, humanId, ref_ }: TitleEditorProp
return (
<div className="group flex items-start gap-2">
- <h1 className="flex-1 text-2xl font-semibold leading-tight text-foreground">
+ <h1 className="text-foreground flex-1 text-2xl leading-tight font-semibold">
{title}
- <span className="ml-2 text-xl font-normal text-muted-foreground">#{humanId}</span>
+ <span className="text-muted-foreground ml-2 text-xl font-normal">#{humanId}</span>
</h1>
{user && (
<button
onClick={() => setEditing(true)}
- className="mt-1 shrink-0 text-muted-foreground opacity-0 transition-opacity hover:text-foreground group-hover:opacity-100"
+ className="text-muted-foreground hover:text-foreground mt-1 shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
title="Edit title"
>
<Pencil className="size-4" />
@@ -17,7 +17,7 @@ export function CodeBreadcrumb({ repoName, ref, path, onNavigate }: CodeBreadcru
<div className="flex flex-wrap items-center gap-1 font-mono text-sm">
<button
onClick={() => onNavigate("")}
- className="font-medium text-foreground hover:underline"
+ className="text-foreground font-medium hover:underline"
>
{repoName}
</button>
@@ -27,9 +27,9 @@ export function CodeBreadcrumb({ repoName, ref, path, onNavigate }: CodeBreadcru
const isLast = i === parts.length - 1;
return (
<span key={partPath} className="flex items-center gap-1">
- <ChevronRight className="size-3.5 text-muted-foreground" />
+ <ChevronRight className="text-muted-foreground size-3.5" />
{isLast ? (
- <span className="font-medium text-foreground">{part}</span>
+ <span className="text-foreground font-medium">{part}</span>
) : (
<button
onClick={() => onNavigate(partPath)}
@@ -42,7 +42,7 @@ export function CodeBreadcrumb({ repoName, ref, path, onNavigate }: CodeBreadcru
);
})}
- <span className="ml-2 text-xs text-muted-foreground">@ {ref}</span>
+ <span className="text-muted-foreground ml-2 text-xs">@ {ref}</span>
</div>
);
}
@@ -5,7 +5,7 @@ import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
import { formatDistanceToNow } from "date-fns";
import { GitCommit } from "lucide-react";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { Link } from "react-router";
import { Button } from "@/components/ui/button";
@@ -34,6 +34,15 @@ const COMMITS_QUERY = gql`
const PAGE_SIZE = 30;
+interface CommitListQueryData {
+ repository: {
+ commits: {
+ nodes: CommitNode[];
+ pageInfo: { hasNextPage: boolean; endCursor: string | null };
+ } | null;
+ } | null;
+}
+
interface CommitListProps {
ref_: string;
path?: string;
@@ -52,16 +61,17 @@ export function CommitList({ ref_, path }: CommitListProps) {
const [cursor, setCursor] = useState<string | null>(null);
const [allCommits, setAllCommits] = useState<CommitNode[]>([]);
- const { loading, error, fetchMore } = useQuery(COMMITS_QUERY, {
+ const { data, loading, error, fetchMore } = useQuery<CommitListQueryData>(COMMITS_QUERY, {
variables: { repo, ref: ref_, path: path ?? null, after: null, first: PAGE_SIZE },
skip: !ref_,
- onCompleted(data) {
- const nodes = data?.repository?.commits?.nodes ?? [];
- setAllCommits(nodes);
- setCursor(data?.repository?.commits?.pageInfo?.endCursor ?? null);
- },
});
+ useEffect(() => {
+ const nodes = data?.repository?.commits?.nodes ?? [];
+ setAllCommits(nodes);
+ setCursor(data?.repository?.commits?.pageInfo?.endCursor ?? null);
+ }, [data]);
+
const hasMore = !!cursor && allCommits.length > 0 && allCommits.length % PAGE_SIZE === 0;
const [loadingMore, setLoadingMore] = useState(false);
@@ -83,7 +93,7 @@ export function CommitList({ ref_, path }: CommitListProps) {
if (error) {
return (
- <div className="rounded-md border border-border px-4 py-8 text-center text-sm text-destructive">
+ <div className="border-border text-destructive rounded-md border px-4 py-8 text-center text-sm">
{error.message}
</div>
);
@@ -95,10 +105,10 @@ export function CommitList({ ref_, path }: CommitListProps) {
<div className="space-y-6">
{groups.map(([date, group]) => (
<div key={date}>
- <h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
+ <h3 className="text-muted-foreground mb-2 text-xs font-semibold tracking-wider uppercase">
Commits on {date}
</h3>
- <div className="divide-y divide-border overflow-hidden rounded-md border border-border">
+ <div className="divide-border border-border divide-y overflow-hidden rounded-md border">
{group.map((commit) => (
<CommitRow key={commit.hash} commit={commit} repo={repo} />
))}
@@ -120,23 +130,23 @@ export function CommitList({ ref_, path }: CommitListProps) {
function CommitRow({ commit, repo }: { commit: CommitNode; repo: string | null }) {
const commitPath = repo ? `/${repo}/commit/${commit.hash}` : `/commit/${commit.hash}`;
return (
- <div className="flex items-center gap-3 bg-background px-4 py-3 hover:bg-muted/30">
- <GitCommit className="size-4 shrink-0 text-muted-foreground" />
+ <div className="bg-background hover:bg-muted/30 flex items-center gap-3 px-4 py-3">
+ <GitCommit className="text-muted-foreground size-4 shrink-0" />
<div className="min-w-0 flex-1">
<Link
to={commitPath}
- className="block truncate font-medium text-foreground hover:text-primary hover:underline"
+ className="text-foreground hover:text-primary block truncate font-medium hover:underline"
>
{commit.message}
</Link>
- <p className="mt-0.5 text-xs text-muted-foreground">
+ <p className="text-muted-foreground mt-0.5 text-xs">
{commit.authorName} ·{" "}
{formatDistanceToNow(new Date(commit.date), { addSuffix: true })}
</p>
</div>
<Link
to={commitPath}
- className="shrink-0 font-mono text-xs text-muted-foreground hover:text-foreground hover:underline"
+ className="text-muted-foreground hover:text-foreground shrink-0 font-mono text-xs hover:underline"
title={commit.hash}
>
{commit.shortHash}
@@ -166,10 +176,10 @@ function CommitListSkeleton() {
{Array.from({ length: 2 }).map((_, g) => (
<div key={g}>
<Skeleton className="mb-2 h-3 w-32" />
- <div className="divide-y divide-border overflow-hidden rounded-md border border-border">
+ <div className="divide-border border-border divide-y overflow-hidden rounded-md border">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex items-center gap-3 px-4 py-3">
- <Skeleton className="size-4 rounded" />
+ <Skeleton className="size-4 rounded-sm" />
<div className="flex-1 space-y-1.5">
<Skeleton className="h-4 w-2/3" />
<Skeleton className="h-3 w-1/4" />
@@ -37,6 +37,21 @@ const DIFF_QUERY = gql`
}
`;
+interface DiffQueryData {
+ repository: {
+ commit: {
+ diff: {
+ path: string;
+ oldPath: string | null;
+ isBinary: boolean;
+ isNew: boolean;
+ isDelete: boolean;
+ hunks: HunkType[];
+ } | null;
+ } | null;
+ } | null;
+}
+
interface FileDiffViewProps {
hash: string;
path: string;
@@ -60,7 +75,7 @@ const statusBadge: Record<string, string> = {
export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps) {
const repo = useRepo();
const [open, setOpen] = useState(false);
- const [fetchDiff, { data, loading, error }] = useLazyQuery(DIFF_QUERY);
+ const [fetchDiff, { data, loading, error }] = useLazyQuery<DiffQueryData>(DIFF_QUERY);
function toggle() {
if (!open && !data && !loading) {
@@ -72,10 +87,10 @@ export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps)
const diff = data?.repository?.commit?.diff;
return (
- <div className="divide-y divide-border">
+ <div className="divide-border divide-y">
<button
onClick={toggle}
- className="flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-muted/50"
+ className="hover:bg-muted/50 flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors"
>
<ChevronRight
className={cn(
@@ -83,7 +98,7 @@ export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps)
open && "rotate-90",
)}
/>
- {statusIcon[status] ?? <FileEdit className="size-3.5 text-muted-foreground" />}
+ {statusIcon[status] ?? <FileEdit className="text-muted-foreground size-3.5" />}
<span className="min-w-0 flex-1 font-mono text-sm">
{status === "RENAMED" ? (
<>
@@ -95,24 +110,24 @@ export function FileDiffView({ hash, path, oldPath, status }: FileDiffViewProps)
path
)}
</span>
- <span className="shrink-0 rounded border border-border px-1.5 py-0.5 font-mono text-xs text-muted-foreground">
+ <span className="border-border text-muted-foreground shrink-0 rounded-sm border px-1.5 py-0.5 font-mono text-xs">
{statusBadge[status] ?? "?"}
</span>
</button>
{open && (
<div className="overflow-x-auto">
- {loading && <div className="px-4 py-3 text-xs text-muted-foreground">Loading diffβ¦</div>}
+ {loading && <div className="text-muted-foreground px-4 py-3 text-xs">Loading diffβ¦</div>}
{error && (
- <div className="px-4 py-3 text-xs text-destructive">
+ <div className="text-destructive px-4 py-3 text-xs">
Failed to load diff: {error.message}
</div>
)}
{diff &&
(diff.isBinary ? (
- <div className="px-4 py-3 text-xs text-muted-foreground">Binary file</div>
+ <div className="text-muted-foreground px-4 py-3 text-xs">Binary file</div>
) : diff.hunks.length === 0 ? (
- <div className="px-4 py-3 text-xs text-muted-foreground">No changes</div>
+ <div className="text-muted-foreground px-4 py-3 text-xs">No changes</div>
) : (
diff.hunks.map((hunk: HunkType, i: number) => <Hunk key={i} hunk={hunk} />)
))}
@@ -134,7 +149,7 @@ type HunkType = {
function Hunk({ hunk }: { hunk: HunkType }) {
return (
<div className="font-mono text-xs leading-5">
- <div className="select-none bg-blue-50 px-4 py-0.5 text-blue-600 dark:bg-blue-950/40 dark:text-blue-400">
+ <div className="bg-blue-50 px-4 py-0.5 text-blue-600 select-none dark:bg-blue-950/40 dark:text-blue-400">
@@ -{hunk.oldStart},{hunk.oldLines} +{hunk.newStart},{hunk.newLines} @@
</div>
{hunk.lines.map((line, i) => (
@@ -146,10 +161,10 @@ function Hunk({ hunk }: { hunk: HunkType }) {
line.type === "DELETED" && "bg-red-50 dark:bg-red-950/30",
)}
>
- <span className="w-10 shrink-0 select-none border-r border-border/50 px-2 text-right text-muted-foreground/50">
+ <span className="border-border/50 text-muted-foreground/50 w-10 shrink-0 border-r px-2 text-right select-none">
{line.oldLine || ""}
</span>
- <span className="w-10 shrink-0 select-none border-r border-border/50 px-2 text-right text-muted-foreground/50">
+ <span className="border-border/50 text-muted-foreground/50 w-10 shrink-0 border-r px-2 text-right select-none">
{line.newLine || ""}
</span>
<span
@@ -35,17 +35,17 @@ export function FileTree({ entries, path, loading, onNavigate, onNavigateUp }: F
if (loading) return <FileTreeSkeleton />;
return (
- <div className="overflow-hidden rounded-md border border-border">
+ <div className="border-border overflow-hidden rounded-md border">
<table className="w-full text-sm">
- <tbody className="divide-y divide-border">
+ <tbody className="divide-border divide-y">
{path && (
- <tr className="cursor-pointer hover:bg-muted/40" onClick={onNavigateUp}>
+ <tr className="hover:bg-muted/40 cursor-pointer" onClick={onNavigateUp}>
<td className="w-6 py-2 pl-4">
<Folder className="size-4 text-blue-500 dark:text-blue-400" />
</td>
- <td className="px-3 py-2 font-mono text-muted-foreground">..</td>
- <td className="hidden px-3 py-2 text-muted-foreground md:table-cell" />
- <td className="hidden px-4 py-2 text-right text-muted-foreground md:table-cell" />
+ <td className="text-muted-foreground px-3 py-2 font-mono">..</td>
+ <td className="text-muted-foreground hidden px-3 py-2 md:table-cell" />
+ <td className="text-muted-foreground hidden px-4 py-2 text-right md:table-cell" />
</tr>
)}
{sorted.map((entry) => (
@@ -68,20 +68,20 @@ function FileTreeRow({
const repo = useRepo();
return (
- <tr className="cursor-pointer hover:bg-muted/40" onClick={() => onNavigate(entry)}>
+ <tr className="hover:bg-muted/40 cursor-pointer" onClick={() => onNavigate(entry)}>
<td className="w-6 py-2 pl-4">
{isDir ? (
<Folder className="size-4 text-blue-500 dark:text-blue-400" />
) : (
- <File className="size-4 text-muted-foreground" />
+ <File className="text-muted-foreground size-4" />
)}
</td>
<td className="px-3 py-2">
- <span className={`font-mono ${isDir ? "font-medium text-foreground" : "text-foreground"}`}>
+ <span className={`font-mono ${isDir ? "text-foreground font-medium" : "text-foreground"}`}>
{entry.name}
</span>
</td>
- <td className="hidden max-w-xs truncate px-3 py-2 text-muted-foreground md:table-cell">
+ <td className="text-muted-foreground hidden max-w-xs truncate px-3 py-2 md:table-cell">
{entry.lastCommit && (
<Link
to={
@@ -94,7 +94,7 @@ function FileTreeRow({
</Link>
)}
</td>
- <td className="hidden whitespace-nowrap px-4 py-2 text-right text-xs text-muted-foreground md:table-cell">
+ <td className="text-muted-foreground hidden px-4 py-2 text-right text-xs whitespace-nowrap md:table-cell">
{entry.lastCommit &&
formatDistanceToNow(new Date(entry.lastCommit.date), { addSuffix: true })}
</td>
@@ -104,11 +104,11 @@ function FileTreeRow({
function FileTreeSkeleton() {
return (
- <div className="overflow-hidden rounded-md border border-border">
- <div className="divide-y divide-border">
+ <div className="border-border overflow-hidden rounded-md border">
+ <div className="divide-border divide-y">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="flex items-center gap-3 px-4 py-2">
- <Skeleton className="size-4 rounded" />
+ <Skeleton className="size-4 rounded-sm" />
<Skeleton className="h-4 w-32" />
<Skeleton className="ml-6 hidden h-4 w-64 md:block" />
<Skeleton className="ml-auto hidden h-4 w-20 md:block" />
@@ -47,8 +47,8 @@ export function FileViewer({ blob, loading }: FileViewerProps) {
}
return (
- <div className="overflow-hidden rounded-md border border-border">
- <div className="flex items-center justify-between border-b border-border bg-muted/40 px-4 py-2 text-xs text-muted-foreground">
+ <div className="border-border overflow-hidden rounded-md border">
+ <div className="border-border bg-muted/40 text-muted-foreground flex items-center justify-between border-b px-4 py-2 text-xs">
<span>
{lineCount.toLocaleString()} lines Β· {formatBytes(blob.size)}
{blob.isTruncated && " Β· truncated"}
@@ -65,13 +65,13 @@ export function FileViewer({ blob, loading }: FileViewerProps) {
</div>
{blob.isBinary ? (
- <div className="px-4 py-8 text-center text-sm text-muted-foreground">
+ <div className="text-muted-foreground px-4 py-8 text-center text-sm">
Binary file β {formatBytes(blob.size)}
</div>
) : (
<div className="flex overflow-x-auto font-mono text-xs leading-5">
<div
- className="select-none border-r border-border bg-muted/20 px-4 py-4 text-right text-muted-foreground/50"
+ className="border-border bg-muted/20 text-muted-foreground/50 border-r px-4 py-4 text-right select-none"
aria-hidden
>
{Array.from({ length: lineCount }, (_, i) => (
@@ -95,8 +95,8 @@ function formatBytes(bytes: number): string {
function FileViewerSkeleton() {
return (
- <div className="overflow-hidden rounded-md border border-border">
- <div className="border-b border-border bg-muted/40 px-4 py-2">
+ <div className="border-border overflow-hidden rounded-md border">
+ <div className="border-border bg-muted/40 border-b px-4 py-2">
<Skeleton className="h-4 w-32" />
</div>
<div className="flex gap-4 p-4">
@@ -29,11 +29,11 @@ export function RefSelector({ refs, currentRef, onSelect }: RefSelectorProps) {
<Button variant="outline" size="sm" className="gap-2 font-mono text-xs">
<GitBranch className="size-3.5" />
{currentRef}
- <ChevronsUpDown className="size-3 text-muted-foreground" />
+ <ChevronsUpDown className="text-muted-foreground size-3" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-64 p-2">
- <p className="mb-2 px-1 text-xs font-semibold text-muted-foreground">Switch branch / tag</p>
+ <p className="text-muted-foreground mb-2 px-1 text-xs font-semibold">Switch branch / tag</p>
<Input
placeholder="Filterβ¦"
className="mb-2 h-7 text-xs"
@@ -44,7 +44,7 @@ export function RefSelector({ refs, currentRef, onSelect }: RefSelectorProps) {
<div className="max-h-64 overflow-y-auto">
{branches.length > 0 && (
<div className="mb-1">
- <p className="px-2 py-1 text-xs text-muted-foreground">Branches</p>
+ <p className="text-muted-foreground px-2 py-1 text-xs">Branches</p>
{branches.map((ref) => (
<RefItem
key={ref.name}
@@ -61,7 +61,7 @@ export function RefSelector({ refs, currentRef, onSelect }: RefSelectorProps) {
)}
{tags.length > 0 && (
<div>
- <p className="px-2 py-1 text-xs text-muted-foreground">Tags</p>
+ <p className="text-muted-foreground px-2 py-1 text-xs">Tags</p>
{tags.map((ref) => (
<RefItem
key={ref.name}
@@ -77,7 +77,7 @@ export function RefSelector({ refs, currentRef, onSelect }: RefSelectorProps) {
</div>
)}
{filtered.length === 0 && (
- <p className="px-2 py-2 text-xs text-muted-foreground">No results</p>
+ <p className="text-muted-foreground px-2 py-2 text-xs">No results</p>
)}
</div>
</PopoverContent>
@@ -98,17 +98,17 @@ function RefItem({
<button
onClick={onSelect}
className={cn(
- "flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs hover:bg-muted",
+ "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-xs hover:bg-muted",
active && "font-medium",
)}
>
{ref_.type === "BRANCH" ? (
- <GitBranch className="size-3 shrink-0 text-muted-foreground" />
+ <GitBranch className="text-muted-foreground size-3 shrink-0" />
) : (
- <Tag className="size-3 shrink-0 text-muted-foreground" />
+ <Tag className="text-muted-foreground size-3 shrink-0" />
)}
<span className="flex-1 truncate font-mono">{ref_.shortName}</span>
- {active && <Check className="size-3 text-muted-foreground" />}
+ {active && <Check className="text-muted-foreground size-3" />}
</button>
);
}
@@ -34,24 +34,27 @@ interface MarkdownProps {
// lists, strikethrough). Used in Timeline comments and NewBugPage preview.
export function Markdown({ content, className }: MarkdownProps) {
return (
- <ReactMarkdown
- remarkPlugins={[remarkGfm, remarkEmoji]}
- rehypePlugins={[
- rehypeRaw,
- [rehypeSanitize, sanitizeSchema],
- rehypeSlug,
- [rehypeAutolinkHeadings, { behavior: "append" }],
- [rehypeExternalLinks, { target: "_blank", rel: ["noopener", "noreferrer"] }],
- ]}
+ <div
className={cn(
"prose prose-sm dark:prose-invert max-w-none",
"prose-pre:bg-muted prose-pre:text-foreground",
- "prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:before:content-none prose-code:after:content-none",
+ "prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-sm prose-code:before:content-none prose-code:after:content-none",
"prose-img:inline prose-img:my-0",
className,
)}
>
- {content}
- </ReactMarkdown>
+ <ReactMarkdown
+ remarkPlugins={[remarkGfm, remarkEmoji]}
+ rehypePlugins={[
+ rehypeRaw,
+ [rehypeSanitize, sanitizeSchema],
+ rehypeSlug,
+ [rehypeAutolinkHeadings, { behavior: "append" }],
+ [rehypeExternalLinks, { target: "_blank", rel: ["noopener", "noreferrer"] }],
+ ]}
+ >
+ {content}
+ </ReactMarkdown>
+ </div>
);
}
@@ -44,10 +44,10 @@ export function Header() {
const effectiveRepo = repo === "auth" ? null : repo;
return (
- <header className="sticky top-0 z-50 border-b border-border bg-background/95 backdrop-blur">
+ <header className="border-border bg-background/95 sticky top-0 z-50 border-b backdrop-blur">
<div className="mx-auto flex h-14 max-w-screen-xl items-center gap-6 px-4">
{/* Logo always goes to the repo picker root */}
- <Link to="/" className="flex items-center gap-2 font-semibold text-foreground">
+ <Link to="/" className="text-foreground flex items-center gap-2 font-semibold">
<Bug className="size-4" />
<span>git-bug</span>
</Link>
@@ -86,7 +86,7 @@ export function Header() {
)}
<div className="ml-auto flex items-center gap-2">
- {mode === "readonly" && <span className="text-xs text-muted-foreground">Read only</span>}
+ {mode === "readonly" && <span className="text-muted-foreground text-xs">Read only</span>}
<Button variant="ghost" size="icon" onClick={toggle} title="Toggle theme">
{theme === "light" ? <Moon className="size-4" /> : <Sun className="size-4" />}
@@ -6,7 +6,7 @@ import { Header } from "./Header";
// Header above the current route's page component via <Outlet>.
export function Shell() {
return (
- <div className="min-h-screen bg-background font-sans antialiased">
+ <div className="bg-background min-h-screen font-sans antialiased">
<Header />
<main className="mx-auto max-w-screen-xl px-4 py-6">
<Outlet />
@@ -4,15 +4,16 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
- "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
- default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
+ default:
+ "border-transparent bg-primary text-primary-foreground shadow-sm hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
- "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
+ "border-transparent bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80",
outline: "text-foreground",
},
},
@@ -5,15 +5,15 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ default: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
outline:
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
<input
type={type}
className={cn(
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
@@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
- "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+ "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className,
)}
{...props}
@@ -7,7 +7,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"tex
return (
<textarea
className={cn(
- "flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+ "flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
ref={ref}
@@ -1,69 +1,119 @@
-/* highlight.js theme must be imported before any @layer rules (PostCSS requirement) */
+/* highlight.js theme must be imported before tailwind */
@import "highlight.js/styles/github.css";
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import "tailwindcss";
+@import "tw-animate-css";
+@plugin "@tailwindcss/typography";
-@layer base {
- :root {
- /* Blue-accented light palette. Primary is GitHub-style indigo-blue so
- action buttons are clearly coloured, not a flat dark grey. */
- --background: 0 0% 100%;
- --foreground: 222 20% 18%;
- --card: 0 0% 100%;
- --card-foreground: 222 20% 18%;
- --popover: 0 0% 100%;
- --popover-foreground: 222 20% 18%;
- --primary: 212 88% 44%;
- --primary-foreground: 0 0% 100%;
- --secondary: 214 32% 95%;
- --secondary-foreground: 222 20% 18%;
- --muted: 214 32% 96%;
- --muted-foreground: 220 9% 46%;
- --accent: 214 88% 95%;
- --accent-foreground: 212 88% 35%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 214 32% 88%;
- --input: 214 32% 88%;
- --ring: 212 88% 44%;
- --radius: 0.5rem;
- }
+@custom-variant dark (&:where(.dark, .dark *));
+
+:root {
+ /* Blue-accented light palette. Primary is GitHub-style indigo-blue so
+ action buttons are clearly coloured, not a flat dark grey. */
+ --background: hsl(0 0% 100%);
+ --foreground: hsl(222 20% 18%);
+ --card: hsl(0 0% 100%);
+ --card-foreground: hsl(222 20% 18%);
+ --popover: hsl(0 0% 100%);
+ --popover-foreground: hsl(222 20% 18%);
+ --primary: hsl(212 88% 44%);
+ --primary-foreground: hsl(0 0% 100%);
+ --secondary: hsl(214 32% 95%);
+ --secondary-foreground: hsl(222 20% 18%);
+ --muted: hsl(214 32% 96%);
+ --muted-foreground: hsl(220 9% 46%);
+ --accent: hsl(214 88% 95%);
+ --accent-foreground: hsl(212 88% 35%);
+ --destructive: hsl(0 84.2% 60.2%);
+ --destructive-foreground: hsl(0 0% 98%);
+ --border: hsl(214 32% 88%);
+ --input: hsl(214 32% 88%);
+ --ring: hsl(212 88% 44%);
+ --radius: 0.5rem;
+}
+
+.dark {
+ /* Softer dark β background lifted slightly, text dimmed to reduce glare. */
+ --background: hsl(220 13% 15%);
+ --foreground: hsl(220 10% 72%);
+ --card: hsl(220 13% 18%);
+ --card-foreground: hsl(220 10% 72%);
+ --popover: hsl(220 13% 18%);
+ --popover-foreground: hsl(220 10% 72%);
+ --primary: hsl(213 88% 62%);
+ --primary-foreground: hsl(220 20% 10%);
+ --secondary: hsl(220 12% 24%);
+ --secondary-foreground: hsl(220 10% 72%);
+ --muted: hsl(220 12% 24%);
+ --muted-foreground: hsl(220 8% 52%);
+ --accent: hsl(220 20% 28%);
+ --accent-foreground: hsl(213 88% 72%);
+ --destructive: hsl(0 65% 50%);
+ --destructive-foreground: hsl(0 0% 98%);
+ --border: hsl(220 12% 26%);
+ --input: hsl(220 12% 26%);
+ --ring: hsl(213 88% 62%);
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+
+ --radius-lg: var(--radius);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-sm: calc(var(--radius) - 4px);
+
+ --font-sans: ui-sans-serif, system-ui, sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
- .dark {
- /* Softer dark β background lifted slightly, text dimmed to reduce glare. */
- --background: 220 13% 15%;
- --foreground: 220 10% 72%;
- --card: 220 13% 18%;
- --card-foreground: 220 10% 72%;
- --popover: 220 13% 18%;
- --popover-foreground: 220 10% 72%;
- --primary: 213 88% 62%;
- --primary-foreground: 220 20% 10%;
- --secondary: 220 12% 24%;
- --secondary-foreground: 220 10% 72%;
- --muted: 220 12% 24%;
- --muted-foreground: 220 8% 52%;
- --accent: 220 20% 28%;
- --accent-foreground: 213 88% 72%;
- --destructive: 0 65% 50%;
- --destructive-foreground: 0 0% 98%;
- --border: 220 12% 26%;
- --input: 220 12% 26%;
- --ring: 213 88% 62%;
+ --animate-accordion-down: accordion-down 0.2s ease-out;
+ --animate-accordion-up: accordion-up 0.2s ease-out;
+}
+
+@keyframes accordion-down {
+ from {
+ height: 0;
+ }
+ to {
+ height: var(--radix-accordion-content-height);
}
}
-@layer base {
- * {
- @apply border-border;
+@keyframes accordion-up {
+ from {
+ height: var(--radix-accordion-content-height);
}
- body {
- @apply bg-background text-foreground;
+ to {
+ height: 0;
}
}
+* {
+ border-color: var(--border);
+}
+
+body {
+ background-color: var(--background);
+ color: var(--foreground);
+}
+
/* ββ Dark-mode overrides for highlight.js (imported above) ββββββββββββββββ */
.dark .hljs {
background: hsl(220, 13%, 16%);
@@ -76,7 +76,9 @@ function LocalAuthProvider({
children: ReactNode;
loginProviders: string[];
}) {
- const { data, loading } = useQuery(USER_IDENTITY_QUERY);
+ const { data, loading } = useQuery<{ repository: { userIdentity: AuthUser | null } }>(
+ USER_IDENTITY_QUERY,
+ );
const user: AuthUser | null = data?.repository?.userIdentity ?? null;
const mode: AuthMode = loading ? "local" : user ? "local" : "readonly";
return (
@@ -24,7 +24,7 @@ export function BugDetailPage() {
if (error) {
return (
- <div className="py-16 text-center text-sm text-destructive">
+ <div className="text-destructive py-16 text-center text-sm">
Failed to load issue: {error.message}
</div>
);
@@ -36,7 +36,7 @@ export function BugDetailPage() {
const bug = data?.repository?.bug;
if (!bug) {
- return <div className="py-16 text-center text-sm text-muted-foreground">Issue not found.</div>;
+ return <div className="text-muted-foreground py-16 text-center text-sm">Issue not found.</div>;
}
const issuesHref = repo ? `/${repo}/issues` : "/issues";
@@ -46,7 +46,7 @@ export function BugDetailPage() {
<div>
<Link
to={issuesHref}
- className="mb-4 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
+ className="text-muted-foreground hover:text-foreground mb-4 flex items-center gap-1.5 text-sm"
>
<ArrowLeft className="size-3.5" />
Back to issues
@@ -57,10 +57,10 @@ export function BugDetailPage() {
<TitleEditor bugPrefix={bug.humanId} title={bug.title} humanId={bug.humanId} ref_={repo} />
</div>
- <div className="mb-6 flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
+ <div className="text-muted-foreground mb-6 flex flex-wrap items-center gap-3 text-sm">
<StatusBadge status={bug.status} />
<span>
- <Link to={authorHref} className="font-medium text-foreground hover:underline">
+ <Link to={authorHref} className="text-foreground font-medium hover:underline">
{bug.author.displayName}
</Link>{" "}
opened this issue {formatDistanceToNow(new Date(bug.createdAt), { addSuffix: true })}
@@ -83,7 +83,7 @@ export function BugDetailPage() {
<Separator />
<div>
- <h3 className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
+ <h3 className="text-muted-foreground mb-2 text-xs font-semibold tracking-wider uppercase">
Participants
</h3>
<div className="flex flex-wrap gap-1.5">
@@ -117,7 +117,7 @@ function BugDetailSkeleton() {
<div className="flex gap-8">
<div className="flex-1 space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
- <div key={i} className="rounded-md border border-border p-4">
+ <div key={i} className="border-border rounded-md border p-4">
<Skeleton className="mb-3 h-4 w-1/4" />
<Skeleton className="h-16 w-full" />
</div>
@@ -123,9 +123,9 @@ export function BugListPage() {
</form>
{/* List container */}
- <div className="rounded-md border border-border">
+ <div className="border-border rounded-md border">
{/* Open / Closed toggle + filter dropdowns */}
- <div className="flex items-center gap-2 overflow-x-auto border-b border-border px-4 py-2">
+ <div className="border-border flex items-center gap-2 overflow-x-auto border-b px-4 py-2">
<div className="flex shrink-0 items-center gap-1">
<button
onClick={() =>
@@ -151,7 +151,7 @@ export function BugListPage() {
)}
/>
Open
- <span className="ml-0.5 rounded-full bg-muted px-1.5 py-0.5 text-xs tabular-nums leading-none">
+ <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums">
{openCount}
</span>
</button>
@@ -180,7 +180,7 @@ export function BugListPage() {
)}
/>
Closed
- <span className="ml-0.5 rounded-full bg-muted px-1.5 py-0.5 text-xs tabular-nums leading-none">
+ <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none tabular-nums">
{closedCount}
</span>
</button>
@@ -214,7 +214,7 @@ export function BugListPage() {
{/* Bug rows */}
{error && (
- <p className="px-4 py-8 text-center text-sm text-destructive">
+ <p className="text-destructive px-4 py-8 text-center text-sm">
Failed to load issues: {error.message}
</p>
)}
@@ -222,7 +222,7 @@ export function BugListPage() {
{loading && !data && <BugListSkeleton />}
{bugs?.nodes.length === 0 && (
- <p className="px-4 py-8 text-center text-sm text-muted-foreground">
+ <p className="text-muted-foreground px-4 py-8 text-center text-sm">
No {statusFilter} issues found.
</p>
)}
@@ -254,18 +254,18 @@ export function BugListPage() {
))}
{totalPages > 1 && (
- <div className="flex items-center justify-center gap-2 border-t border-border px-4 py-2">
+ <div className="border-border flex items-center justify-center gap-2 border-t px-4 py-2">
<Button
variant="ghost"
size="sm"
onClick={goPrev}
disabled={!hasPrev || loading}
- className="gap-1 text-muted-foreground"
+ className="text-muted-foreground gap-1"
>
<ChevronLeft className="size-4" />
Previous
</Button>
- <span className="text-sm text-muted-foreground">
+ <span className="text-muted-foreground text-sm">
Page {page + 1} of {totalPages}
</span>
<Button
@@ -273,7 +273,7 @@ export function BugListPage() {
size="sm"
onClick={goNext}
disabled={!hasNext || loading}
- className="gap-1 text-muted-foreground"
+ className="text-muted-foreground gap-1"
>
Next
<ChevronRight className="size-4" />
@@ -373,7 +373,7 @@ function parseQueryString(input: string): {
function BugListSkeleton() {
return (
- <div className="divide-y divide-border">
+ <div className="divide-border divide-y">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="flex items-start gap-3 px-4 py-3">
<Skeleton className="mt-0.5 size-4 rounded-full" />
@@ -79,6 +79,31 @@ const BLOB_QUERY = gql`
}
`;
+interface RefsQueryData {
+ repository: {
+ name: string;
+ refs: { nodes: GitRef[] } | null;
+ } | null;
+}
+
+interface TreeQueryData {
+ repository: {
+ tree: GitTreeEntry[] | null;
+ } | null;
+}
+
+interface LastCommitsQueryData {
+ repository: {
+ lastCommits: GitLastCommit[] | null;
+ } | null;
+}
+
+interface BlobQueryData {
+ repository: {
+ blob: GitBlob | null;
+ } | null;
+}
+
type ViewMode = "tree" | "blob" | "commits";
export function CodePage() {
@@ -93,7 +118,7 @@ export function CodePage() {
data: refsData,
loading: refsLoading,
error: refsError,
- } = useQuery(REFS_QUERY, {
+ } = useQuery<RefsQueryData>(REFS_QUERY, {
variables: { repo },
});
const refs: GitRef[] = refsData?.repository?.refs?.nodes ?? [];
@@ -116,14 +141,14 @@ export function CodePage() {
const inTreeMode = viewMode === "tree" && !!currentRef;
const inBlobMode = viewMode === "blob" && !!currentRef && !!currentPath;
- const { data: treeData, loading: treeLoading } = useQuery(TREE_QUERY, {
+ const { data: treeData, loading: treeLoading } = useQuery<TreeQueryData>(TREE_QUERY, {
variables: { repo, ref: currentRef, path: currentPath || null },
skip: !inTreeMode,
});
const entries: GitTreeEntry[] = treeData?.repository?.tree ?? [];
const entryNames = entries.map((e: GitTreeEntry) => e.name);
- const { data: lastCommitsData } = useQuery(LAST_COMMITS_QUERY, {
+ const { data: lastCommitsData } = useQuery<LastCommitsQueryData>(LAST_COMMITS_QUERY, {
variables: { repo, ref: currentRef, path: currentPath || null, names: entryNames },
skip: !inTreeMode || entryNames.length === 0,
});
@@ -135,7 +160,7 @@ export function CodePage() {
lastCommit: lastCommitsByName.get(e.name)?.commit ?? undefined,
}));
- const { data: blobData, loading: blobLoading } = useQuery(BLOB_QUERY, {
+ const { data: blobData, loading: blobLoading } = useQuery<BlobQueryData>(BLOB_QUERY, {
variables: { repo, ref: currentRef, path: currentPath },
skip: !inBlobMode,
});
@@ -149,7 +174,7 @@ export function CodePage() {
? `${currentPath}/${readmeEntry.name}`
: readmeEntry.name
: null;
- const { data: readmeBlobData } = useQuery(BLOB_QUERY, {
+ const { data: readmeBlobData } = useQuery<BlobQueryData>(BLOB_QUERY, {
variables: { repo, ref: currentRef, path: readmePath },
skip: !inTreeMode || !readmePath,
});
@@ -188,9 +213,9 @@ export function CodePage() {
if (refsError) {
return (
<div className="flex flex-col items-center gap-3 py-16 text-center">
- <AlertCircle className="size-8 text-muted-foreground" />
+ <AlertCircle className="text-muted-foreground size-8" />
<p className="text-sm font-medium">Code browser unavailable</p>
- <p className="max-w-sm text-xs text-muted-foreground">{refsError.message}</p>
+ <p className="text-muted-foreground max-w-sm text-xs">{refsError.message}</p>
</div>
);
}
@@ -240,7 +265,7 @@ export function CodePage() {
/>
{readme && (
<div className="rounded-md border">
- <div className="border-b px-4 py-2 text-xs font-medium text-muted-foreground">
+ <div className="text-muted-foreground border-b px-4 py-2 text-xs font-medium">
README
</div>
<div className="px-6 py-4">
@@ -35,12 +35,30 @@ const COMMIT_QUERY = gql`
}
`;
+interface CommitQueryData {
+ repository: {
+ commit: {
+ hash: string;
+ shortHash: string;
+ message: string;
+ fullMessage: string;
+ authorName: string;
+ authorEmail: string | null;
+ date: string;
+ parents: string[];
+ files: {
+ nodes: { path: string; oldPath: string | null; status: string }[];
+ } | null;
+ } | null;
+ } | null;
+}
+
export function CommitPage() {
const { hash } = useParams<{ hash: string }>();
const navigate = useNavigate();
const repo = useRepo();
- const { data, loading, error } = useQuery(COMMIT_QUERY, {
+ const { data, loading, error } = useQuery<CommitQueryData>(COMMIT_QUERY, {
variables: { repo, hash },
skip: !hash,
});
@@ -49,7 +67,7 @@ export function CommitPage() {
if (error) {
return (
- <div className="py-16 text-center text-sm text-destructive">
+ <div className="text-destructive py-16 text-center text-sm">
Failed to load commit: {error.message}
</div>
);
@@ -64,43 +82,45 @@ export function CommitPage() {
return (
<div>
<button
- onClick={() => navigate(-1)}
- className="mb-6 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
+ onClick={() => {
+ void navigate(-1);
+ }}
+ className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
>
<ArrowLeft className="size-3.5" />
Back
</button>
- <div className="mb-6 rounded-md border border-border p-5">
+ <div className="border-border mb-6 rounded-md border p-5">
<div className="mb-1 flex items-start gap-3">
- <GitCommit className="mt-1 size-5 shrink-0 text-muted-foreground" />
- <h1 className="text-lg font-semibold leading-snug">{commit.message}</h1>
+ <GitCommit className="text-muted-foreground mt-1 size-5 shrink-0" />
+ <h1 className="text-lg leading-snug font-semibold">{commit.message}</h1>
</div>
{commit.fullMessage.includes("\n") && (
- <pre className="mb-4 ml-8 mt-3 whitespace-pre-wrap font-sans text-sm text-muted-foreground">
+ <pre className="text-muted-foreground mt-3 mb-4 ml-8 font-sans text-sm whitespace-pre-wrap">
{commit.fullMessage.split("\n").slice(1).join("\n").trim()}
</pre>
)}
- <div className="ml-8 mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground">
+ <div className="text-muted-foreground mt-3 ml-8 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
<span>
- <span className="font-medium text-foreground">{commit.authorName}</span>
+ <span className="text-foreground font-medium">{commit.authorName}</span>
{commit.authorEmail && <span> <{commit.authorEmail}></span>}
</span>
<span title={date.toISOString()}>{format(date, "PPP")}</span>
</div>
- <div className="ml-8 mt-3 flex flex-wrap gap-3 text-xs">
+ <div className="mt-3 ml-8 flex flex-wrap gap-3 text-xs">
<span className="text-muted-foreground">
- commit <code className="font-mono text-foreground">{commit.hash}</code>
+ commit <code className="text-foreground font-mono">{commit.hash}</code>
</span>
{commit.parents.map((p: string) => (
<span key={p} className="text-muted-foreground">
parent{" "}
<Link
to={repo ? `/${repo}/commit/${p}` : `/commit/${p}`}
- className="font-mono text-foreground hover:underline"
+ className="text-foreground font-mono hover:underline"
>
{p.slice(0, 7)}
</Link>
@@ -110,12 +130,12 @@ export function CommitPage() {
</div>
<div>
- <h2 className="mb-3 text-sm font-semibold text-muted-foreground">
+ <h2 className="text-muted-foreground mb-3 text-sm font-semibold">
{files.length} file{files.length !== 1 ? "s" : ""} changed
</h2>
- <div className="divide-y divide-border overflow-hidden rounded-md border border-border">
+ <div className="divide-border border-border divide-y overflow-hidden rounded-md border">
{files.length === 0 && (
- <p className="px-4 py-4 text-sm text-muted-foreground">No file changes.</p>
+ <p className="text-muted-foreground px-4 py-4 text-sm">No file changes.</p>
)}
{files.map((file: { path: string; oldPath?: string | null; status: string }) => (
<FileDiffView
@@ -136,12 +156,12 @@ function CommitPageSkeleton() {
return (
<div className="space-y-6">
<Skeleton className="h-4 w-24" />
- <div className="space-y-3 rounded-md border border-border p-5">
+ <div className="border-border space-y-3 rounded-md border p-5">
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-4 w-1/3" />
<Skeleton className="h-3 w-1/2" />
</div>
- <div className="divide-y divide-border rounded-md border border-border">
+ <div className="divide-border border-border divide-y rounded-md border">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center gap-3 px-4 py-2.5">
<Skeleton className="size-4" />
@@ -24,9 +24,9 @@ export function ErrorPage() {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 text-center">
- <AlertTriangle className="size-10 text-muted-foreground" />
+ <AlertTriangle className="text-muted-foreground size-10" />
{status && <p className="text-5xl font-bold tracking-tight">{status}</p>}
- <p className="text-sm text-muted-foreground">{message}</p>
+ <p className="text-muted-foreground text-sm">{message}</p>
<Button variant="outline" size="sm" asChild>
<Link to="/">Go home</Link>
</Button>
@@ -27,13 +27,17 @@ export function IdentitySelectPage() {
const [working, setWorking] = useState(false);
useEffect(() => {
- void fetch("/auth/identities", { credentials: "include" })
- .then((res) => {
+ async function loadIdentities() {
+ try {
+ const res = await fetch("/auth/identities", { credentials: "include" });
if (!res.ok) throw new Error(`unexpected status ${res.status}`);
- return res.json() as Promise<IdentityItem[]>;
- })
- .then(setIdentities)
- .catch((e) => setError(String(e)));
+ const data: IdentityItem[] = await res.json();
+ setIdentities(data);
+ } catch (e) {
+ setError(String(e));
+ }
+ }
+ void loadIdentities();
}, []);
async function adopt(identityId: string | null) {
@@ -57,16 +61,16 @@ export function IdentitySelectPage() {
return (
<div className="mx-auto max-w-lg py-12">
<div className="mb-2 flex items-center gap-3">
- <UserCircle className="size-6 text-muted-foreground" />
+ <UserCircle className="text-muted-foreground size-6" />
<h1 className="text-xl font-semibold">Choose your identity</h1>
</div>
- <p className="mb-8 text-sm text-muted-foreground">
+ <p className="text-muted-foreground mb-8 text-sm">
No git-bug identity was found linked to your account. Select an existing identity to link
it, or create a new one from your profile.
</p>
{error && (
- <div className="mb-4 flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
+ <div className="border-destructive/30 bg-destructive/10 text-destructive mb-4 flex items-center gap-2 rounded-md border px-4 py-3 text-sm">
<AlertCircle className="size-4 shrink-0" />
{error}
</div>
@@ -80,12 +84,12 @@ export function IdentitySelectPage() {
</div>
)}
- <div className="divide-y divide-border rounded-md border border-border">
+ <div className="divide-border border-border divide-y rounded-md border">
{identities?.map((id) => (
<div key={id.id} className="flex items-center gap-3 px-4 py-3">
<div className="min-w-0 flex-1">
<p className="font-medium">{id.displayName}</p>
- <p className="text-xs text-muted-foreground">
+ <p className="text-muted-foreground text-xs">
{id.login ? `@${id.login} Β· ` : ""}
{id.repoSlug} Β· {id.humanId}
</p>
@@ -106,7 +110,7 @@ export function IdentitySelectPage() {
<div className="flex items-center gap-3 px-4 py-3">
<div className="min-w-0 flex-1">
<p className="font-medium">Create new identity</p>
- <p className="text-xs text-muted-foreground">
+ <p className="text-muted-foreground text-xs">
A fresh git-bug identity will be created from your OAuth profile.
</p>
</div>
@@ -27,7 +27,7 @@ export function NewBugPage() {
});
const humanId = result.data?.bugCreate.bug.humanId;
if (humanId) {
- navigate(repo ? `/${repo}/issues/${humanId}` : `/issues/${humanId}`);
+ void navigate(repo ? `/${repo}/issues/${humanId}` : `/issues/${humanId}`);
}
}
@@ -37,7 +37,7 @@ export function NewBugPage() {
<div className="mx-auto max-w-3xl">
<Link
to={issuesHref}
- className="mb-6 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
+ className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
>
<ArrowLeft className="size-3.5" />
Back to issues
@@ -72,7 +72,7 @@ export function NewBugPage() {
<button
type="button"
onClick={() => setPreview(false)}
- className={`rounded px-2 py-0.5 transition-colors ${
+ className={`rounded-sm px-2 py-0.5 transition-colors ${
!preview ? "bg-muted font-medium" : "text-muted-foreground hover:text-foreground"
}`}
>
@@ -82,7 +82,7 @@ export function NewBugPage() {
type="button"
onClick={() => setPreview(true)}
disabled={!message.trim()}
- className={`rounded px-2 py-0.5 transition-colors disabled:opacity-40 ${
+ className={`rounded-sm px-2 py-0.5 transition-colors disabled:opacity-40 ${
preview ? "bg-muted font-medium" : "text-muted-foreground hover:text-foreground"
}`}
>
@@ -92,7 +92,7 @@ export function NewBugPage() {
</div>
{preview ? (
- <div className="min-h-[200px] rounded-md border border-input px-3 py-2">
+ <div className="border-input min-h-[200px] rounded-md border px-3 py-2">
<Markdown content={message} />
</div>
) : (
@@ -107,14 +107,16 @@ export function NewBugPage() {
</div>
{error && (
- <p className="text-sm text-destructive">Failed to create issue: {error.message}</p>
+ <p className="text-destructive text-sm">Failed to create issue: {error.message}</p>
)}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="ghost"
- onClick={() => navigate(issuesHref)}
+ onClick={() => {
+ void navigate(issuesHref);
+ }}
disabled={loading}
>
Cancel
@@ -23,19 +23,19 @@ export function RepoPickerPage() {
// Auto-redirect when there is exactly one repo β no need to pick.
useEffect(() => {
if (data?.repositories.nodes.length === 1) {
- navigate("/" + repoSlug(data.repositories.nodes[0].name), { replace: true });
+ void navigate("/" + repoSlug(data.repositories.nodes[0].name), { replace: true });
}
}, [data, navigate]);
return (
<div className="mx-auto max-w-lg py-12">
<div className="mb-8 flex items-center gap-3">
- <GitFork className="size-6 text-muted-foreground" />
+ <GitFork className="text-muted-foreground size-6" />
<h1 className="text-xl font-semibold">Repositories</h1>
</div>
{error && (
- <div className="flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
+ <div className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-2 rounded-md border px-4 py-3 text-sm">
<AlertCircle className="size-4 shrink-0" />
Failed to load repositories: {error.message}
</div>
@@ -49,20 +49,20 @@ export function RepoPickerPage() {
</div>
)}
- <div className="divide-y divide-border rounded-md border border-border">
+ <div className="divide-border border-border divide-y rounded-md border">
{data?.repositories.nodes.map((repo) => (
<Link
key={repoSlug(repo.name)}
to={`/${repoSlug(repo.name)}`}
- className="flex items-center gap-3 px-4 py-4 transition-colors hover:bg-muted/50"
+ className="hover:bg-muted/50 flex items-center gap-3 px-4 py-4 transition-colors"
>
- <FolderOpen className="size-5 shrink-0 text-muted-foreground" />
- <p className="font-medium text-foreground">{repoLabel(repo.name)}</p>
+ <FolderOpen className="text-muted-foreground size-5 shrink-0" />
+ <p className="text-foreground font-medium">{repoLabel(repo.name)}</p>
</Link>
))}
{data?.repositories.totalCount === 0 && (
- <p className="px-4 py-8 text-center text-sm text-muted-foreground">
+ <p className="text-muted-foreground px-4 py-8 text-center text-sm">
No repositories found.
</p>
)}
@@ -61,7 +61,7 @@ export function UserProfilePage() {
if (error) {
return (
- <div className="py-16 text-center text-sm text-destructive">
+ <div className="text-destructive py-16 text-center text-sm">
Failed to load profile: {error.message}
</div>
);
@@ -71,7 +71,7 @@ export function UserProfilePage() {
const identity = data?.repository?.identity;
if (!identity) {
- return <div className="py-16 text-center text-sm text-muted-foreground">User not found.</div>;
+ return <div className="text-muted-foreground py-16 text-center text-sm">User not found.</div>;
}
const openCount = data?.repository?.openCount.totalCount ?? 0;
@@ -97,7 +97,7 @@ export function UserProfilePage() {
<div>
<Link
to={issuesHref}
- className="mb-6 flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
+ className="text-muted-foreground hover:text-foreground mb-6 flex items-center gap-1.5 text-sm"
>
<ArrowLeft className="size-3.5" />
Back to issues
@@ -118,11 +118,11 @@ export function UserProfilePage() {
{/* isProtected means this identity has been cryptographically signed */}
{identity.isProtected && (
<span title="Protected identity">
- <ShieldCheck className="size-4 text-muted-foreground" />
+ <ShieldCheck className="text-muted-foreground size-4" />
</span>
)}
</div>
- <div className="mt-1 space-y-0.5 text-sm text-muted-foreground">
+ <div className="text-muted-foreground mt-1 space-y-0.5 text-sm">
{identity.login && <p>@{identity.login}</p>}
{identity.email && <p>{identity.email}</p>}
<p className="font-mono text-xs">#{identity.humanId}</p>
@@ -130,22 +130,22 @@ export function UserProfilePage() {
{/* Aggregate stats β always visible, independent of selected tab */}
<div className="mt-3 flex items-center gap-4 text-sm">
- <span className="flex items-center gap-1 text-muted-foreground">
+ <span className="text-muted-foreground flex items-center gap-1">
<CircleDot className="size-3.5 text-green-600 dark:text-green-400" />
- <span className="font-medium text-foreground">{openCount}</span> open
+ <span className="text-foreground font-medium">{openCount}</span> open
</span>
- <span className="flex items-center gap-1 text-muted-foreground">
+ <span className="text-muted-foreground flex items-center gap-1">
<CircleCheck className="size-3.5 text-purple-600 dark:text-purple-400" />
- <span className="font-medium text-foreground">{closedCount}</span> closed
+ <span className="text-foreground font-medium">{closedCount}</span> closed
</span>
</div>
</div>
</div>
{/* ββ Issue list βββββββββββββββββββββββββββββββββββββββββββββββββββ */}
- <div className="rounded-md border border-border">
+ <div className="border-border rounded-md border">
{/* Open / Closed toggle β mirrors BugListPage style */}
- <div className="flex items-center gap-1 border-b border-border px-4 py-2">
+ <div className="border-border flex items-center gap-1 border-b px-4 py-2">
<button
onClick={() => switchStatus("open")}
className={cn(
@@ -162,7 +162,7 @@ export function UserProfilePage() {
)}
/>
Open
- <span className="ml-0.5 rounded-full bg-muted px-1.5 py-0.5 text-xs leading-none">
+ <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none">
{openCount}
</span>
</button>
@@ -183,14 +183,14 @@ export function UserProfilePage() {
)}
/>
Closed
- <span className="ml-0.5 rounded-full bg-muted px-1.5 py-0.5 text-xs leading-none">
+ <span className="bg-muted ml-0.5 rounded-full px-1.5 py-0.5 text-xs leading-none">
{closedCount}
</span>
</button>
</div>
{bugs?.nodes.length === 0 && (
- <p className="px-4 py-8 text-center text-sm text-muted-foreground">
+ <p className="text-muted-foreground px-4 py-8 text-center text-sm">
No {statusFilter} issues.
</p>
)}
@@ -201,7 +201,7 @@ export function UserProfilePage() {
return (
<div
key={bug.id}
- className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0"
+ className="border-border flex items-start gap-3 border-b px-4 py-3 last:border-0"
>
<StatusIcon
className={cn(
@@ -215,7 +215,7 @@ export function UserProfilePage() {
<div className="flex flex-wrap items-baseline gap-2">
<Link
to={repo ? `/${repo}/issues/${bug.humanId}` : `/issues/${bug.humanId}`}
- className="font-medium text-foreground hover:text-primary hover:underline"
+ className="text-foreground hover:text-primary font-medium hover:underline"
>
{bug.title}
</Link>
@@ -223,13 +223,13 @@ export function UserProfilePage() {
<LabelBadge key={label.name} name={label.name} color={label.color} />
))}
</div>
- <p className="mt-0.5 text-xs text-muted-foreground">
+ <p className="text-muted-foreground mt-0.5 text-xs">
#{bug.humanId} opened{" "}
{formatDistanceToNow(new Date(bug.createdAt), { addSuffix: true })}
</p>
</div>
{bug.comments.totalCount > 0 && (
- <div className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
+ <div className="text-muted-foreground flex shrink-0 items-center gap-1 text-xs">
<MessageSquare className="size-3.5" />
{bug.comments.totalCount}
</div>
@@ -240,18 +240,18 @@ export function UserProfilePage() {
{/* Pagination footer β only shown when there is more than one page */}
{totalPages > 1 && (
- <div className="flex items-center justify-center gap-2 border-t border-border px-4 py-2">
+ <div className="border-border flex items-center justify-center gap-2 border-t px-4 py-2">
<Button
variant="ghost"
size="sm"
onClick={goPrev}
disabled={!hasPrev || loading}
- className="gap-1 text-muted-foreground"
+ className="text-muted-foreground gap-1"
>
<ChevronLeft className="size-4" />
Previous
</Button>
- <span className="text-sm text-muted-foreground">
+ <span className="text-muted-foreground text-sm">
Page {page + 1} of {totalPages}
</span>
<Button
@@ -259,7 +259,7 @@ export function UserProfilePage() {
size="sm"
onClick={goNext}
disabled={!hasNext || loading}
- className="gap-1 text-muted-foreground"
+ className="text-muted-foreground gap-1"
>
Next
<ChevronRight className="size-4" />
@@ -1,71 +0,0 @@
-import type { Config } from "tailwindcss";
-
-const config: Config = {
- darkMode: ["class"],
- content: ["./index.html", "./src/**/*.{ts,tsx}"],
- theme: {
- extend: {
- fontFamily: {
- sans: ["ui-sans-serif", "system-ui", "sans-serif"],
- mono: ["ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "monospace"],
- },
- colors: {
- border: "hsl(var(--border))",
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
- background: "hsl(var(--background))",
- foreground: "hsl(var(--foreground))",
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- destructive: {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- popover: {
- DEFAULT: "hsl(var(--popover))",
- foreground: "hsl(var(--popover-foreground))",
- },
- card: {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
- },
- },
- borderRadius: {
- lg: "var(--radius)",
- md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)",
- },
- keyframes: {
- "accordion-down": {
- from: { height: "0" },
- to: { height: "var(--radix-accordion-content-height)" },
- },
- "accordion-up": {
- from: { height: "var(--radix-accordion-content-height)" },
- to: { height: "0" },
- },
- },
- animation: {
- "accordion-down": "accordion-down 0.2s ease-out",
- "accordion-up": "accordion-up 0.2s ease-out",
- },
- },
- },
- plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
-};
-
-export default config;
@@ -13,5 +13,5 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
- "include": ["vite.config.ts", "tailwind.config.ts", "codegen.ts"]
+ "include": ["vite.config.ts", "codegen.ts"]
}
@@ -1,5 +1,6 @@
import path from "path";
+import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
@@ -7,7 +8,7 @@ import { defineConfig } from "vite";
const API_URL = process.env.VITE_API_URL || "http://localhost:3000";
export default defineConfig({
- plugins: [react()],
+ plugins: [tailwindcss(), react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),