gitxp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.output/nitro.json +17 -0
  2. package/.output/public/assets/index-BGHvBNtP.js +11 -0
  3. package/.output/public/assets/routes-DuE8S6pX.js +60 -0
  4. package/.output/public/assets/styles-D5xbQNYu.css +1 -0
  5. package/.output/server/_chunks/ssr-renderer.mjs +26 -0
  6. package/.output/server/_libs/@floating-ui/core+[...].mjs +671 -0
  7. package/.output/server/_libs/@floating-ui/dom+[...].mjs +649 -0
  8. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +856 -0
  9. package/.output/server/_libs/@radix-ui/react-arrow+[...].mjs +258 -0
  10. package/.output/server/_libs/@radix-ui/react-dialog+[...].mjs +1862 -0
  11. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +320 -0
  12. package/.output/server/_libs/@radix-ui/react-tooltip+[...].mjs +534 -0
  13. package/.output/server/_libs/@tanstack/db+[...].mjs +14680 -0
  14. package/.output/server/_libs/@tanstack/react-router+[...].mjs +14563 -0
  15. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +126 -0
  16. package/.output/server/_libs/@tanstack/router-core+[...].mjs +3636 -0
  17. package/.output/server/_libs/class-variance-authority+clsx.mjs +69 -0
  18. package/.output/server/_libs/cmdk.mjs +504 -0
  19. package/.output/server/_libs/h3+rou3+srvx.mjs +1361 -0
  20. package/.output/server/_libs/h3-v2.mjs +285 -0
  21. package/.output/server/_libs/lucide-react.mjs +158 -0
  22. package/.output/server/_libs/radix-ui__primitive.mjs +44 -0
  23. package/.output/server/_libs/radix-ui__react-context.mjs +108 -0
  24. package/.output/server/_libs/tailwind-merge.mjs +3380 -0
  25. package/.output/server/_libs/tanstack__history.mjs +384 -0
  26. package/.output/server/_libs/tanstack__query-core.mjs +2225 -0
  27. package/.output/server/_libs/tanstack__react-db.mjs +242 -0
  28. package/.output/server/_libs/tanstack__react-query.mjs +140 -0
  29. package/.output/server/_libs/ufo.mjs +64 -0
  30. package/.output/server/_runtime.mjs +35 -0
  31. package/.output/server/_ssr/empty-plugin-adapters-D9UWiqvJ.mjs +5 -0
  32. package/.output/server/_ssr/events-BNrmOvgU.mjs +13 -0
  33. package/.output/server/_ssr/notifications-DUoENv6E.mjs +514 -0
  34. package/.output/server/_ssr/router-DsUHD4bT.mjs +179 -0
  35. package/.output/server/_ssr/routes-DIFfr242.mjs +982 -0
  36. package/.output/server/_ssr/ssr.mjs +1854 -0
  37. package/.output/server/_ssr/start-5Z2QO8AU.mjs +4 -0
  38. package/.output/server/_tanstack-start-manifest_v-CrD0YpZr.mjs +20 -0
  39. package/.output/server/index.mjs +310 -0
  40. package/.output/server/node_modules/tslib/modules/index.js +70 -0
  41. package/.output/server/node_modules/tslib/modules/package.json +3 -0
  42. package/.output/server/node_modules/tslib/package.json +47 -0
  43. package/.output/server/node_modules/tslib/tslib.js +484 -0
  44. package/.output/server/package.json +9 -0
  45. package/LICENSE +21 -0
  46. package/README.md +325 -0
  47. package/bin/gitxp.js +85 -0
  48. package/package.json +92 -0
package/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # gitxp
2
+
3
+ An alternative GitHub UI that runs on your own machine. `npx gitxp` starts a
4
+ local server, authenticates through the GitHub CLI you already have set up, and
5
+ opens the app in your browser. There is no account to create and no OAuth app to
6
+ register.
7
+
8
+ The goal is an interface that feels like Linear rather than a website: the UI
9
+ reads from a local store and never blocks on a network round trip, and syncing
10
+ with GitHub happens in the background.
11
+
12
+ The long-term scope is the daily GitHub workflow: browsing repos, issues and
13
+ pull requests, reviewing code, discussions and comments, watching CI, and
14
+ merging. v0 is narrower, see Roadmap.
15
+
16
+ ## Requirements
17
+
18
+ - Node 22.13 or newer, because the local store uses the built-in `node:sqlite`
19
+ module
20
+ - The [GitHub CLI](https://cli.github.com), authenticated with `gh auth login`
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ npx gitxp
26
+ ```
27
+
28
+ Options:
29
+
30
+ - `--port <number>` use a specific port instead of a random free one
31
+ - `--no-open` start the server without opening a browser
32
+
33
+ ## How it works
34
+
35
+ ```
36
+ npx gitxp
37
+ |
38
+ +-- bin/gitxp.js checks `gh auth status`, picks a free port,
39
+ | sets HOST=127.0.0.1, boots the built server
40
+ |
41
+ +-- .output/server TanStack Start server functions talk to the
42
+ | GitHub API using the token from `gh auth token`
43
+ |
44
+ +-- browser React app that reads and writes a local
45
+ collection, synced against GitHub in the background
46
+ ```
47
+
48
+ The build output is a self-contained Node server produced by Nitro. It imports
49
+ nothing but `node:` builtins, so the published package has no runtime
50
+ dependencies.
51
+
52
+ ## Roadmap
53
+
54
+ v0 is a complete review loop: a notifications inbox, the pull requests waiting
55
+ on you, the pull request itself with its diff and CI status, and submitting a
56
+ review with inline comments, approval or changes requested, and merging.
57
+
58
+ Build order, risky parts first, each step runnable on its own:
59
+
60
+ 1. SQLite store, migration runner, the two zones
61
+ 2. API client: token from `gh`, ETags, rate limit accounting
62
+ 3. Notifications sync loop, one resource proven end to end
63
+ 4. SSE and the client mirror, so a background write reaches the UI
64
+ 5. Inbox UI: list, preview pane, command palette, row actions
65
+ 6. Mutation queue, proven on mark-as-done
66
+ 7. Review inbox as the second resource
67
+ 8. Pull request detail: `@pierre/diffs`, existing review comments, CI status
68
+ 9. Review submission: inline comments, approve, request changes, merge
69
+
70
+ After v0, in rough order: issues, repo and file browsing, discussions.
71
+
72
+ Out of v0 on purpose: search, projects, Actions logs, wikis, gists, org and team
73
+ views.
74
+
75
+ None of it is built yet. What is in `src/` today is the project scaffold.
76
+
77
+ ## Interface
78
+
79
+ Three columns: a sidebar of views, the list, and a preview pane. Triage happens
80
+ without leaving the list. A pull request diff breaks out of that layout and
81
+ takes the full width.
82
+
83
+ Keyboard first. Cmd+K opens a command palette covering everything, and single
84
+ letters act on the focused row. The palette is also where an action lives before
85
+ it earns a shortcut, so nothing ends up reachable only by mouse.
86
+
87
+ The visual layer stays on stock shadcn for now. It is the one choice here that
88
+ is cheap to revisit, because retuning spacing and type tokens later does not
89
+ touch component structure or focus handling.
90
+
91
+ Cached rows render immediately, with a thin indicator while a background refresh
92
+ runs. Skeletons appear only where data is genuinely absent. Applied everywhere,
93
+ that rule is most of what separates this from a website.
94
+
95
+ ## Local sync layer
96
+
97
+ The UI reads from a SQLite database at `~/.gitxp/`, written by the server
98
+ process. It uses the built-in `node:sqlite` module, so it adds no dependency and
99
+ the published package stays free of them. One server process means one writer,
100
+ sync keeps running while no tab is open, and the cache survives clearing browser
101
+ data.
102
+
103
+ ### What gets synced
104
+
105
+ GitHub is too large to mirror, so the store has a boundary:
106
+
107
+ - Synced in the background: your notifications, pull requests waiting on your
108
+ review, and the repos you explicitly follow in gitxp.
109
+ - Fetched on demand and cached afterwards: file trees, blobs, older comment
110
+ threads, CI logs, anything you reach by navigating rather than by triage.
111
+
112
+ Anything outside the synced set is a cache miss on first view. The UI should
113
+ show it arriving rather than pretending it was already local.
114
+
115
+ ### Schema
116
+
117
+ Each entity keeps GitHub's JSON payload as-is in a `raw` column, alongside
118
+ extracted columns for the fields queries filter and sort on. Scope grows toward
119
+ all of GitHub, and keeping the payload whole means adding a view does not need a
120
+ migration for every field that view happens to read.
121
+
122
+ Rows are keyed by GitHub's GraphQL `node_id`. REST and GraphQL return different
123
+ identifiers for the same object and REST payloads carry `node_id` too, so keying
124
+ on it means a row written by a bulk GraphQL pull and a row written by a REST
125
+ poll are the same row.
126
+
127
+ Notification threads are the exception. The REST notifications endpoint returns
128
+ no `node_id`, only a string thread `id`, so `notifications` is keyed on that.
129
+
130
+ The store has two zones. Everything cached from GitHub is disposable, can be
131
+ dropped and resynced, and therefore makes most schema changes free. The mutation
132
+ queue and your settings cannot be rederived from GitHub, so they live in tables
133
+ migrations have to preserve. That split is what makes a reset-cache command safe
134
+ to offer. Migrations run off SQLite's `user_version`.
135
+
136
+ ### Scheduling
137
+
138
+ Each resource is its own job with its own interval, ETag and backoff, rather
139
+ than one loop polling everything. Checks on a pull request you are looking at
140
+ want seconds, followed repos want minutes, and whatever is on screen gets a
141
+ priority lane.
142
+
143
+ `node:sqlite` is synchronous, so a large write batch stalls HTTP responses in
144
+ the same process. Keep batches small and measure. If the UI stutters, the sync
145
+ loop moves to a worker thread.
146
+
147
+ ### The client mirror
148
+
149
+ React runs in the browser and cannot read that SQLite file, so every row the UI
150
+ renders is copied into browser memory first. That copy is a TanStack DB
151
+ collection holding the slice the current view needs.
152
+
153
+ Holding a collection, rather than calling a server function per view, is what
154
+ allows filtering, sorting and joining to happen in the browser with no round
155
+ trip. It is why an inbox filter can feel instant. Only the bounded triage set is
156
+ mirrored this way; everything fetched on demand stays in the plain TanStack
157
+ Query cache.
158
+
159
+ The server pushes over Server-Sent Events at `/api/events`, as a plain
160
+ `ReadableStream` response. A sync that writes rows emits a change signal naming
161
+ the resource, and the client refetches that collection through its server
162
+ function, applying the result inside TanStack DB's `begin`, `truncate`, `write`
163
+ and `commit` calls. Mutations travel the other way as ordinary server function
164
+ calls, so the stream stays one-directional.
165
+
166
+ The stream carries a signal rather than the changed rows. On localhost the
167
+ refetch costs a sub-millisecond round trip and reuses the same query path as the
168
+ initial load, which is worth more than the saved bytes. Streaming rows, and
169
+ per-subset subscriptions through `loadSubset` and `unloadSubset`, are the
170
+ refinement if a collection ever grows past what a full refetch can carry.
171
+
172
+ ```
173
+ GitHub --poll--> sync loop --write--> SQLite (~/.gitxp)
174
+ |
175
+ change signal
176
+ |
177
+ SSE stream at /api/events
178
+ |
179
+ client refetches that collection
180
+ |
181
+ SyncConfig: begin / truncate / write / commit
182
+ |
183
+ client collection --> React
184
+ ```
185
+
186
+ ### Writes
187
+
188
+ Mutations are queued in the same database and applied in order, so a write
189
+ survives a page reload. They fall into two classes:
190
+
191
+ - Optimistic: comments, labels, review submissions, marking a notification
192
+ done. The local store updates immediately and rolls back if the request fails.
193
+ - Confirmed: merging, closing, anything awkward to undo or that branch
194
+ protection and required checks can reject. The UI waits for GitHub and shows
195
+ the request in flight.
196
+
197
+ A queued write can fail long after you navigated away, so failed actions need a
198
+ persistent surface with retry rather than a toast that disappears.
199
+
200
+ A review in progress stays local until you submit it. Inline comments accumulate
201
+ in the queue and reach GitHub as one request that creates the review with its
202
+ comments, which keeps drafting instant and makes submission atomic.
203
+
204
+ ### Staying inside the rate limit
205
+
206
+ The limit is 5000 requests an hour on REST and on GraphQL, and 30 a minute on
207
+ search, so search is not used for syncing.
208
+
209
+ - Background polling uses conditional requests. A 304 does not count against the
210
+ REST limit.
211
+ - The notifications endpoint returns an `X-Poll-Interval` header saying how
212
+ often GitHub wants to be polled. Respect it instead of picking an interval.
213
+ - GraphQL is for composite reads where one query replaces several REST calls,
214
+ such as a pull request with its reviews and checks.
215
+ - CI status is the hungriest part, because check runs change often. Poll it only
216
+ for a pull request currently on screen, never globally.
217
+
218
+ ## Reviewing pull requests
219
+
220
+ Rendering is [`@pierre/diffs`](https://diffs.com) rather than a hand-built
221
+ viewer. It is Apache-2.0, built on Shiki, does stacked and split layouts, and
222
+ ships an annotation framework meant for review comments and CI annotations. File
223
+ trees come from [`@pierre/trees`](https://trees.software).
224
+
225
+ `@pierre/trees` is at `1.0.0-beta.6` and depends on a Preact 11 beta, so a
226
+ Preact beta ends up bundled next to React 19. Worth watching, and worth pinning
227
+ the version.
228
+
229
+ The data still comes from the REST pull request files endpoint, because GitHub
230
+ has already computed the patch hunks. That endpoint omits the patch for binary
231
+ files and for files over its size limit, so those need a fallback rather than an
232
+ empty view.
233
+
234
+ The preview pane is narrow, so it shows a stacked diff. Split view is available
235
+ at full width.
236
+
237
+ Review comments anchor to a path, a line and a side, mapped onto the annotation
238
+ framework. Comments whose line no longer exists in the current diff are
239
+ outdated, and the UI has to show them rather than drop them.
240
+
241
+ Long lists outside the diff still need virtualizing. `@tanstack/react-virtual`
242
+ is the fit, and is not installed yet because nothing imports it.
243
+
244
+ ## Security
245
+
246
+ The server holds a real GitHub token, so it is only ever reachable from the
247
+ machine it runs on. `bin/gitxp.js` sets `HOST=127.0.0.1`, because the Nitro
248
+ default binds every interface.
249
+
250
+ The token comes from `gh auth token` and stays on the server side. Server
251
+ functions read it and the browser never receives it. It is re-read on a 401
252
+ rather than cached for the life of the process, and the host comes from
253
+ `gh auth status`, so an enterprise host works without extra configuration.
254
+
255
+ Server functions reject requests whose `Origin` header is not the app itself.
256
+ Any page open in your browser can reach `127.0.0.1`, and that is the realistic
257
+ attacker. This is not built yet, and needs to exist before the server starts
258
+ holding a token.
259
+
260
+ A local process can read the port and send no `Origin` at all. Defending against
261
+ that would need a secret generated at boot and carried in the URL. That is not
262
+ planned.
263
+
264
+ ## Development
265
+
266
+ ```bash
267
+ npm install
268
+ npm run dev # vite dev server on port 3000
269
+ npm run build # produces .output/
270
+ npm start # runs bin/gitxp.js against .output, same path as npx
271
+ npm run lint
272
+ npm run typecheck
273
+ npm run format
274
+ ```
275
+
276
+ `npm run lint` does not typecheck. `npm run typecheck` catches what it misses,
277
+ including unsound casts on SQLite rows.
278
+
279
+ Modules under `src/server` import with explicit `.ts` extensions and stay inside
280
+ the TypeScript syntax Node can strip, so they run directly without the bundler:
281
+
282
+ ```bash
283
+ GITXP_HOME=/tmp/gitxp-scratch node some-script.ts
284
+ ```
285
+
286
+ ## Publishing
287
+
288
+ `prepublishOnly` runs the build, and the `files` field ships `.output` and
289
+ `bin`. Everything else is a build-time concern and lives in `devDependencies`.
290
+
291
+ `dependencies` is empty on purpose. npm installs a package's dependencies when
292
+ you run it through npx, so anything listed there is downloaded by every user.
293
+ Nitro already inlines the framework into `.output`, which is about 1.4 MB
294
+ against a 278 MB development tree.
295
+
296
+ Check what would be published before releasing:
297
+
298
+ ```bash
299
+ npm pack --dry-run
300
+ ```
301
+
302
+ ## Project layout
303
+
304
+ ```
305
+ bin/gitxp.js CLI entry point, boots the built server
306
+ src/server/db/ SQLite store, migrations, per-resource sync state
307
+ src/server/github/ gh token, API client with ETags and rate limit accounting
308
+ src/server/sync/ per-resource sync jobs, scheduler, change emitter
309
+ src/functions/ server functions, the boundary the client calls
310
+ src/db-collections/ client collections mirroring the local database
311
+ src/routes/ file-based routes (TanStack Router)
312
+ src/components/ui/ shadcn components, all of them
313
+ src/integrations/ TanStack Query setup
314
+ .output/ build output, generated
315
+ ```
316
+
317
+ ## Built with
318
+
319
+ [TanStack Start](https://tanstack.com/start) with Nitro, TanStack Router,
320
+ Query, DB, Table and Form, Tailwind, and shadcn/ui. Diffs and file trees come
321
+ from [`@pierre/diffs`](https://diffs.com) and
322
+ [`@pierre/trees`](https://trees.software).
323
+
324
+ Every shadcn component is installed under `src/components/ui`. The full list is
325
+ in `AGENTS.md`.
package/bin/gitxp.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from 'node:child_process'
3
+ import { existsSync } from 'node:fs'
4
+ import { createServer } from 'node:net'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const serverEntry = new URL('../.output/server/index.mjs', import.meta.url)
8
+
9
+ if (!existsSync(fileURLToPath(serverEntry))) {
10
+ console.error('Build output is missing. Run `npm run build` first.')
11
+ process.exit(1)
12
+ }
13
+
14
+ try {
15
+ execFileSync('gh', ['auth', 'status'], { stdio: 'ignore' })
16
+ } catch (error) {
17
+ console.error(
18
+ error.code === 'ENOENT'
19
+ ? 'gitxp needs the GitHub CLI. Install it from https://cli.github.com, then run `gh auth login`.'
20
+ : 'gitxp needs an authenticated GitHub CLI. Run `gh auth login`.',
21
+ )
22
+ process.exit(1)
23
+ }
24
+
25
+ const args = process.argv.slice(2)
26
+ const portFlag = args.indexOf('--port')
27
+ const requestedPort = portFlag === -1 ? null : Number(args[portFlag + 1])
28
+
29
+ if (requestedPort !== null && !Number.isInteger(requestedPort)) {
30
+ console.error('--port needs a number, for example `gitxp --port 4000`.')
31
+ process.exit(1)
32
+ }
33
+
34
+ const port = requestedPort ?? (await findFreePort())
35
+ const url = `http://127.0.0.1:${port}`
36
+
37
+ process.env.HOST = '127.0.0.1'
38
+ process.env.PORT = String(port)
39
+
40
+ await import(serverEntry.href)
41
+ await waitUntilReady(url)
42
+
43
+ console.log(`gitxp is running at ${url}`)
44
+
45
+ if (!args.includes('--no-open')) {
46
+ openBrowser(url)
47
+ }
48
+
49
+ function findFreePort() {
50
+ return new Promise((resolve, reject) => {
51
+ const probe = createServer()
52
+ probe.on('error', reject)
53
+ probe.listen(0, '127.0.0.1', () => {
54
+ const { port } = probe.address()
55
+ probe.close(() => resolve(port))
56
+ })
57
+ })
58
+ }
59
+
60
+ async function waitUntilReady(target) {
61
+ for (let attempt = 0; attempt < 100; attempt++) {
62
+ try {
63
+ await fetch(target, { method: 'HEAD' })
64
+ return
65
+ } catch {
66
+ await new Promise((resolve) => setTimeout(resolve, 50))
67
+ }
68
+ }
69
+ throw new Error(`Server did not start at ${target}`)
70
+ }
71
+
72
+ function openBrowser(target) {
73
+ const [command, commandArgs] =
74
+ process.platform === 'darwin'
75
+ ? ['open', [target]]
76
+ : process.platform === 'win32'
77
+ ? ['cmd', ['/c', 'start', '', target]]
78
+ : ['xdg-open', [target]]
79
+
80
+ try {
81
+ execFileSync(command, commandArgs, { stdio: 'ignore' })
82
+ } catch {
83
+ console.error(`Could not open a browser. Open ${target} manually.`)
84
+ }
85
+ }
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "gitxp",
3
+ "version": "0.0.1",
4
+ "description": "An alternative GitHub UI that runs locally on top of the GitHub CLI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "gitxp": "bin/gitxp.js"
9
+ },
10
+ "files": [
11
+ ".output",
12
+ "bin"
13
+ ],
14
+ "engines": {
15
+ "node": ">=22.13.0"
16
+ },
17
+ "imports": {
18
+ "#/*": "./src/*"
19
+ },
20
+ "scripts": {
21
+ "dev": "vite dev --port 3000",
22
+ "generate-routes": "tsr generate",
23
+ "build": "vite build",
24
+ "start": "node bin/gitxp.js",
25
+ "preview": "vite preview",
26
+ "lint": "eslint",
27
+ "typecheck": "tsc --noEmit",
28
+ "format": "prettier --write . && eslint --fix",
29
+ "check": "prettier --check .",
30
+ "prepublishOnly": "vite build"
31
+ },
32
+ "devDependencies": {
33
+ "@base-ui/react": "^1.7.0",
34
+ "@hookform/resolvers": "^5.9.1",
35
+ "@pierre/diffs": "^1.3.6",
36
+ "@pierre/trees": "^1.0.0-beta.6",
37
+ "@shadcn/react": "^0.3.0",
38
+ "@tailwindcss/typography": "^0.5.16",
39
+ "@tailwindcss/vite": "^4.1.18",
40
+ "@tanstack/devtools-vite": "latest",
41
+ "@tanstack/eslint-config": "latest",
42
+ "@tanstack/match-sorter-utils": "latest",
43
+ "@tanstack/query-db-collection": "latest",
44
+ "@tanstack/react-db": "latest",
45
+ "@tanstack/react-devtools": "latest",
46
+ "@tanstack/react-form": "latest",
47
+ "@tanstack/react-query": "latest",
48
+ "@tanstack/react-query-devtools": "latest",
49
+ "@tanstack/react-router": "latest",
50
+ "@tanstack/react-router-devtools": "latest",
51
+ "@tanstack/react-router-ssr-query": "latest",
52
+ "@tanstack/react-start": "latest",
53
+ "@tanstack/react-table": "latest",
54
+ "@tanstack/router-cli": "^1.132.0",
55
+ "@types/node": "^22.10.2",
56
+ "@types/react": "^19.2.0",
57
+ "@types/react-dom": "^19.2.0",
58
+ "@vitejs/plugin-react": "^6.0.1",
59
+ "class-variance-authority": "^0.7.1",
60
+ "clsx": "^2.1.1",
61
+ "cmdk": "^1.1.1",
62
+ "date-fns": "^4.4.0",
63
+ "embla-carousel-react": "^8.6.0",
64
+ "eslint": "^9.20.0",
65
+ "input-otp": "^1.5.0",
66
+ "lucide-react": "^0.577.0",
67
+ "next-themes": "^0.4.6",
68
+ "nitro": "3.0.260610-beta",
69
+ "prettier": "^3.8.1",
70
+ "radix-ui": "^1.6.7",
71
+ "react": "^19.2.0",
72
+ "react-day-picker": "^10.0.1",
73
+ "react-dom": "^19.2.0",
74
+ "react-hook-form": "^7.87.0",
75
+ "react-resizable-panels": "^4.12.3",
76
+ "recharts": "^3.8.0",
77
+ "sonner": "^2.0.8",
78
+ "tailwind-merge": "^3.0.2",
79
+ "tailwindcss": "^4.1.18",
80
+ "tw-animate-css": "^1.3.6",
81
+ "typescript": "^6.0.2",
82
+ "vaul": "^1.1.2",
83
+ "vite": "^8.0.0",
84
+ "zod": "^4.3.6"
85
+ },
86
+ "pnpm": {
87
+ "onlyBuiltDependencies": [
88
+ "esbuild",
89
+ "lightningcss"
90
+ ]
91
+ }
92
+ }