From 8751ba26ebf7b7761b9e237f2bf3453623dd1018 Mon Sep 17 00:00:00 2001 From: polwex Date: Mon, 6 Oct 2025 10:13:39 +0700 Subject: added frontend WS connection for demonstration purposes --- app/app/nostrill.hoon | 59 +- app/lib/nostrill.hoon | 19 +- app/lib/websockets.hoon | 3 + arvo/eyre.hoon | 46 +- gui/.envrc | 10 + gui/.gitignore | 44 + gui/README.md | 69 + gui/bun.lock | 650 +++++ gui/devenv.lock | 103 + gui/devenv.nix | 57 + gui/devenv.yaml | 15 + gui/eslint.config.js | 23 + gui/index.html | 16 + gui/package.json | 39 + gui/public/favicon.svg | 22 + gui/public/fonts/Inter | 1 + gui/public/fonts/Source_Code_Pro | 1 + gui/public/nostril-icon.png | Bin 0 -> 764000 bytes gui/src/App.tsx | 46 + gui/src/Router.tsx | 39 + gui/src/assets/crowspinner.gif | Bin 0 -> 83062 bytes gui/src/assets/icons/bell.svg | 3 + gui/src/assets/icons/comet.svg | 23 + gui/src/assets/icons/copy.svg | 5 + gui/src/assets/icons/crow.svg | 29 + gui/src/assets/icons/emoji.svg | 5 + gui/src/assets/icons/home.svg | 3 + gui/src/assets/icons/key.png | Bin 0 -> 16383 bytes gui/src/assets/icons/key.svg | 5 + gui/src/assets/icons/logo.png | Bin 0 -> 33892 bytes gui/src/assets/icons/logo.svg | 8 + gui/src/assets/icons/messages.svg | 8 + gui/src/assets/icons/nostr.svg | 3 + gui/src/assets/icons/pals.svg | 3 + gui/src/assets/icons/profile.svg | 3 + gui/src/assets/icons/quote.svg | 3 + gui/src/assets/icons/radio.svg | 3 + gui/src/assets/icons/reply.svg | 3 + gui/src/assets/icons/rt.svg | 3 + gui/src/assets/icons/rumors.svg | 3 + gui/src/assets/icons/settings.svg | 3 + gui/src/assets/icons/youtube.svg | 1 + gui/src/assets/reacts/chad.png | Bin 0 -> 20099 bytes gui/src/assets/reacts/cringe.png | Bin 0 -> 21923 bytes gui/src/assets/reacts/cry.png | Bin 0 -> 18471 bytes gui/src/assets/reacts/doom.png | Bin 0 -> 14749 bytes gui/src/assets/reacts/facepalm.png | Bin 0 -> 188497 bytes gui/src/assets/reacts/galaxy.png | Bin 0 -> 43137 bytes gui/src/assets/reacts/gigachad.png | Bin 0 -> 19087 bytes gui/src/assets/reacts/pepechin.png | Bin 0 -> 22389 bytes gui/src/assets/reacts/pepeeyes.png | Bin 0 -> 18480 bytes gui/src/assets/reacts/pepegmi.png | Bin 0 -> 23024 bytes gui/src/assets/reacts/pepesad.png | Bin 0 -> 18323 bytes gui/src/assets/reacts/pika.png | Bin 0 -> 11475 bytes gui/src/assets/reacts/pink.png | Bin 0 -> 26288 bytes gui/src/assets/reacts/soy.png | Bin 0 -> 12655 bytes gui/src/assets/reacts/yeschad.png | Bin 0 -> 29394 bytes gui/src/assets/triangles.svg | 18 + gui/src/components/Avatar.tsx | 62 + gui/src/components/Icon.tsx | 137 + gui/src/components/NotificationCenter.tsx | 192 ++ gui/src/components/Sigil.tsx | 50 + gui/src/components/WsWidget.tsx | 123 + gui/src/components/composer/Composer.tsx | 205 ++ gui/src/components/composer/Snippets.tsx | 86 + gui/src/components/feed/PostList.tsx | 33 + gui/src/components/layout/Sidebar.tsx | 80 + gui/src/components/modals/Modal.tsx | 72 + gui/src/components/modals/ShipModal.tsx | 48 + gui/src/components/modals/UserModal.tsx | 65 + gui/src/components/post/Body.tsx | 174 ++ gui/src/components/post/Card.tsx | 12 + gui/src/components/post/External.tsx | 40 + gui/src/components/post/Footer.tsx | 260 ++ gui/src/components/post/Header.tsx | 40 + gui/src/components/post/Loader.tsx | 148 + gui/src/components/post/Media.tsx | 35 + gui/src/components/post/Post.tsx | 85 + gui/src/components/post/PostWrapper.tsx | 14 + gui/src/components/post/Quote.tsx | 64 + gui/src/components/post/RP.tsx | 47 + gui/src/components/post/Reactions.tsx | 134 + gui/src/components/post/StatsModal.tsx | 106 + gui/src/components/post/wrappers/Nostr.tsx | 15 + gui/src/components/post/wrappers/NostrIcon.tsx | 25 + gui/src/components/profile/Editor.tsx | 262 ++ gui/src/components/profile/Profile.tsx | 67 + gui/src/hooks/useWs.tsx | 326 +++ gui/src/logic/api.ts | 15 + gui/src/logic/bunts.ts | 51 + gui/src/logic/constants.ts | 36 + gui/src/logic/emojis.json | 3613 ++++++++++++++++++++++++ gui/src/logic/nostrill.ts | 139 + gui/src/logic/requests/nostrill.ts | 224 ++ gui/src/logic/trill/helpers.ts | 10 + gui/src/logic/utils.ts | 459 +++ gui/src/main.tsx | 9 + gui/src/pages/Feed.tsx | 182 ++ gui/src/pages/Settings.tsx | 255 ++ gui/src/pages/Thread.tsx | 127 + gui/src/pages/User.tsx | 212 ++ gui/src/state/state.ts | 150 + gui/src/styles/NotificationCenter.css | 263 ++ gui/src/styles/Profile.css | 325 +++ gui/src/styles/Settings.css | 339 +++ gui/src/styles/ThemeProvider.tsx | 446 +++ gui/src/styles/ThemeSwitcher.css | 252 ++ gui/src/styles/ThemeSwitcher.tsx | 131 + gui/src/styles/feed.css | 134 + gui/src/styles/styles.css | 704 +++++ gui/src/styles/trill.css | 623 ++++ gui/src/types/nostr.ts | 12 + gui/src/types/nostrill.ts | 24 + gui/src/types/notifications.ts | 28 + gui/src/types/trill.ts | 420 +++ gui/src/types/twatter.ts | 336 +++ gui/src/types/ui.ts | 53 + gui/src/types/urbit.ts | 8 + gui/src/vite-env.d.ts | 1 + gui/tsconfig.app.json | 30 + gui/tsconfig.json | 7 + gui/tsconfig.node.json | 25 + gui/vite.config.ts | 14 + 123 files changed, 14015 insertions(+), 46 deletions(-) create mode 100644 gui/.envrc create mode 100644 gui/.gitignore create mode 100644 gui/README.md create mode 100644 gui/bun.lock create mode 100644 gui/devenv.lock create mode 100644 gui/devenv.nix create mode 100644 gui/devenv.yaml create mode 100644 gui/eslint.config.js create mode 100644 gui/index.html create mode 100644 gui/package.json create mode 100644 gui/public/favicon.svg create mode 120000 gui/public/fonts/Inter create mode 120000 gui/public/fonts/Source_Code_Pro create mode 100644 gui/public/nostril-icon.png create mode 100644 gui/src/App.tsx create mode 100644 gui/src/Router.tsx create mode 100644 gui/src/assets/crowspinner.gif create mode 100644 gui/src/assets/icons/bell.svg create mode 100644 gui/src/assets/icons/comet.svg create mode 100644 gui/src/assets/icons/copy.svg create mode 100644 gui/src/assets/icons/crow.svg create mode 100644 gui/src/assets/icons/emoji.svg create mode 100644 gui/src/assets/icons/home.svg create mode 100644 gui/src/assets/icons/key.png create mode 100644 gui/src/assets/icons/key.svg create mode 100644 gui/src/assets/icons/logo.png create mode 100644 gui/src/assets/icons/logo.svg create mode 100644 gui/src/assets/icons/messages.svg create mode 100644 gui/src/assets/icons/nostr.svg create mode 100644 gui/src/assets/icons/pals.svg create mode 100644 gui/src/assets/icons/profile.svg create mode 100644 gui/src/assets/icons/quote.svg create mode 100644 gui/src/assets/icons/radio.svg create mode 100644 gui/src/assets/icons/reply.svg create mode 100644 gui/src/assets/icons/rt.svg create mode 100644 gui/src/assets/icons/rumors.svg create mode 100644 gui/src/assets/icons/settings.svg create mode 100644 gui/src/assets/icons/youtube.svg create mode 100644 gui/src/assets/reacts/chad.png create mode 100644 gui/src/assets/reacts/cringe.png create mode 100644 gui/src/assets/reacts/cry.png create mode 100644 gui/src/assets/reacts/doom.png create mode 100644 gui/src/assets/reacts/facepalm.png create mode 100644 gui/src/assets/reacts/galaxy.png create mode 100644 gui/src/assets/reacts/gigachad.png create mode 100644 gui/src/assets/reacts/pepechin.png create mode 100644 gui/src/assets/reacts/pepeeyes.png create mode 100644 gui/src/assets/reacts/pepegmi.png create mode 100644 gui/src/assets/reacts/pepesad.png create mode 100644 gui/src/assets/reacts/pika.png create mode 100644 gui/src/assets/reacts/pink.png create mode 100644 gui/src/assets/reacts/soy.png create mode 100644 gui/src/assets/reacts/yeschad.png create mode 100644 gui/src/assets/triangles.svg create mode 100644 gui/src/components/Avatar.tsx create mode 100644 gui/src/components/Icon.tsx create mode 100644 gui/src/components/NotificationCenter.tsx create mode 100644 gui/src/components/Sigil.tsx create mode 100644 gui/src/components/WsWidget.tsx create mode 100644 gui/src/components/composer/Composer.tsx create mode 100644 gui/src/components/composer/Snippets.tsx create mode 100644 gui/src/components/feed/PostList.tsx create mode 100644 gui/src/components/layout/Sidebar.tsx create mode 100644 gui/src/components/modals/Modal.tsx create mode 100644 gui/src/components/modals/ShipModal.tsx create mode 100644 gui/src/components/modals/UserModal.tsx create mode 100644 gui/src/components/post/Body.tsx create mode 100644 gui/src/components/post/Card.tsx create mode 100644 gui/src/components/post/External.tsx create mode 100644 gui/src/components/post/Footer.tsx create mode 100644 gui/src/components/post/Header.tsx create mode 100644 gui/src/components/post/Loader.tsx create mode 100644 gui/src/components/post/Media.tsx create mode 100644 gui/src/components/post/Post.tsx create mode 100644 gui/src/components/post/PostWrapper.tsx create mode 100644 gui/src/components/post/Quote.tsx create mode 100644 gui/src/components/post/RP.tsx create mode 100644 gui/src/components/post/Reactions.tsx create mode 100644 gui/src/components/post/StatsModal.tsx create mode 100644 gui/src/components/post/wrappers/Nostr.tsx create mode 100644 gui/src/components/post/wrappers/NostrIcon.tsx create mode 100644 gui/src/components/profile/Editor.tsx create mode 100644 gui/src/components/profile/Profile.tsx create mode 100644 gui/src/hooks/useWs.tsx create mode 100644 gui/src/logic/api.ts create mode 100644 gui/src/logic/bunts.ts create mode 100644 gui/src/logic/constants.ts create mode 100644 gui/src/logic/emojis.json create mode 100644 gui/src/logic/nostrill.ts create mode 100644 gui/src/logic/requests/nostrill.ts create mode 100644 gui/src/logic/trill/helpers.ts create mode 100644 gui/src/logic/utils.ts create mode 100644 gui/src/main.tsx create mode 100644 gui/src/pages/Feed.tsx create mode 100644 gui/src/pages/Settings.tsx create mode 100644 gui/src/pages/Thread.tsx create mode 100644 gui/src/pages/User.tsx create mode 100644 gui/src/state/state.ts create mode 100644 gui/src/styles/NotificationCenter.css create mode 100644 gui/src/styles/Profile.css create mode 100644 gui/src/styles/Settings.css create mode 100644 gui/src/styles/ThemeProvider.tsx create mode 100644 gui/src/styles/ThemeSwitcher.css create mode 100644 gui/src/styles/ThemeSwitcher.tsx create mode 100644 gui/src/styles/feed.css create mode 100644 gui/src/styles/styles.css create mode 100644 gui/src/styles/trill.css create mode 100644 gui/src/types/nostr.ts create mode 100644 gui/src/types/nostrill.ts create mode 100644 gui/src/types/notifications.ts create mode 100644 gui/src/types/trill.ts create mode 100644 gui/src/types/twatter.ts create mode 100644 gui/src/types/ui.ts create mode 100644 gui/src/types/urbit.ts create mode 100644 gui/src/vite-env.d.ts create mode 100644 gui/tsconfig.app.json create mode 100644 gui/tsconfig.json create mode 100644 gui/tsconfig.node.json create mode 100644 gui/vite.config.ts diff --git a/app/app/nostrill.hoon b/app/app/nostrill.hoon index 2a1fa2f..271f774 100644 --- a/app/app/nostrill.hoon +++ b/app/app/nostrill.hoon @@ -30,8 +30,8 @@ ^- (quip card:agent:gall agent:gall) =/ default (default-state:lib bowl) :_ this(state default) - :~ shim-binding:cards - == + bindings:cards + :: ++ on-save ^- vase @@ -42,7 +42,9 @@ ^- (quip card:agent:gall agent:gall) =/ old-state !<(versioned-state old-state) ?- -.old-state - %0 `this(state old-state) + %0 :_ this(state old-state) + bindings:cards + == :: `this(state (default-state:lib bowl)) :: @@ -60,31 +62,48 @@ == ++ handle-ws-handshake =/ order !<([@ inbound-request:eyre] vase) + ~& >> nostrill-ws-handshake=order + =/ url url.request.order + =/ pat=(unit path) (rush url stap) + ?~ pat ~& "pat-parsing-failed" `this + =/ ok=? ?+ u.pat .n + [%nostrill-ui ~] authenticated.order + [%nostrill ~] .y :: TODO which nostr clients do we filter + [%nostr-shim ~] .y :: TODO deprecate? + == :_ this - :: TODO refuse if...? - (accept-handshake:ws -.order) + ?: ok (accept-handshake:ws -.order) (refuse-handshake:ws -.order) :: we behave like a Server here, mind you. messages from clients, not relays ++ handle-ws-msg - =/ order !<([wid=@ msg=websocket-message:eyre] vase) + =/ order !<([wid=@ =path msg=websocket-message:eyre] vase) :: ~& opcode=op=opcode.msg.order :: 0 for continuation, 1 for text, 2 for binary, 9 for ping 0xa for pong =/ msg message.msg.order ?~ msg `this - =/ jsons=@t q.data.u.msg - ~& >> ws-msg-jsons=jsons - =/ jsonm (de:json:html jsons) - ?~ jsonm `this - =/ client-msg (parse-client-msg:nreq u.jsonm) - ?~ client-msg ~& "wrong nostr ws msg from client" `this - :: TODO de-json thing and handle whatever - =^ cs state ?- -.u.client-msg - %req `state - %event (handle-client-event:mutan -.order event.u.client-msg) - %auth `state - %close `state + =/ wsdata=@t q.data.u.msg + ~& >> ws-msg-data=[path.order wsdata] + |^ + ?+ path.order `this + [%nostrill-ui ~] handle-ui-ws + [%nostrill ~] handle-nostr-client-ws == - [cs this] :: - :: + ++ handle-ui-ws + =/ cs (ui-ws-res:lib -.order wsdata) + [cs this] + ++ handle-nostr-client-ws + =/ jsonm (de:json:html wsdata) + ?~ jsonm `this + =/ client-msg (parse-client-msg:nreq u.jsonm) + ?~ client-msg ~& "wrong nostr ws msg from client" `this + :: TODO de-json thing and handle whatever + =^ cs state ?- -.u.client-msg + %req `state + %event (handle-client-event:mutan -.order event.u.client-msg) + %auth `state + %close `state + == + [cs this] + -- ++ handle-comms =/ pok (cast-poke:coms q.vase) ?: ?=(%dbug -.pok) (debug +.pok) diff --git a/app/lib/nostrill.hoon b/app/lib/nostrill.hoon index 41caff2..933dc7f 100644 --- a/app/lib/nostrill.hoon +++ b/app/lib/nostrill.hoon @@ -1,5 +1,6 @@ /- post=trill-post, nsur=nostr, sur=nostrill, gate=trill-gate, comms=nostrill-comms -/+ trill=trill-post, nostr-keys, sr=sortug, jsonlib=json-nostrill +/+ trill=trill-post, nostr-keys, sr=sortug, jsonlib=json-nostrill, + ws=websockets |% :: ++ default-state |= =bowl:gall ^- state:sur @@ -28,12 +29,28 @@ ~& >> total=total-received $(l t.l) +++ ui-ws-res |= [wid=@ msg=@t] + + =/ resmsg (cat 3 msg (cat 3 msg msg)) + =/ octs (as-octs:mimes:html resmsg) + =/ res-event=websocket-event:eyre [%message 1 `octs] + (give-ws-payload:ws wid res-event) :: ++ cards |_ =bowl:gall ++ shim-binding ^- card:agent:gall [%pass /binding %arvo %e %connect [~ /nostr-shim] dap.bowl] + + ++ relay-binding ^- card:agent:gall + [%pass /binding %arvo %e %connect [~ /nostrill] dap.bowl] + ++ ui-binding ^- card:agent:gall + [%pass /binding %arvo %e %connect [~ /nostrill-ui] dap.bowl] + ++ bindings + :~ shim-binding + relay-binding + ui-binding + == ++ update-ui |= =fact:ui:sur ^- card:agent:gall =/ jon (fact:en:jsonlib fact) [%give %fact ~[/ui] %json !>(jon)] diff --git a/app/lib/websockets.hoon b/app/lib/websockets.hoon index 9faea96..4d1f952 100644 --- a/app/lib/websockets.hoon +++ b/app/lib/websockets.hoon @@ -10,5 +10,8 @@ ++ accept-handshake |= wid=@ =/ response [%accept ~] (give-ws-payload wid response) + ++ refuse-handshake |= wid=@ + =/ response [%reject ~] + (give-ws-payload wid response) -- diff --git a/arvo/eyre.hoon b/arvo/eyre.hoon index 0abd1c3..6fcd05f 100644 --- a/arvo/eyre.hoon +++ b/arvo/eyre.hoon @@ -820,7 +820,7 @@ =^ ?(invalid=@uv [suv=@uv =identity som=(list move)]) state (session-for-request:authentication request) ?@ - - :: the request provided a session cookie that's not (or no longer) + :: the request provided a session coocokie that's not (or no longer) :: valid. to make sure they're aware, tell them 401 :: ::NOTE some code duplication with below, but request handling deserves @@ -944,7 +944,6 @@ ?: &(?=([~ @ ^] cached) ?=(%'GET' method.request)) (handle-cache-req authenticated request u.val.u.cached) :: - ~& >> eyre-request-action=-.action ?- -.action %gen =/ bek=beak [our desk.generator.action da+now] @@ -1043,21 +1042,26 @@ ++ ws-event |= [wid=@ event=websocket-event] =/ conn (~(get by connections.state) duct) + :: ~& ws-conn=conn ?~ conn `state ?. ?=(%app -.action.u.conn) `state + =/ url url.request.inbound-request.u.conn + =/ pat=(unit path) (rush url stap) + ?~ pat ~& error-parsing-path=pat `state =/ app app.action.u.conn - ~& ws-event=[wid app] - =/ identity [%ours ~] - =/ wsid (scot %p wid) + =/ identity identity.u.conn + =/ wsid (scot %ud wid) + :: ~& >> ws-event=[identity app pat wsid] :: TODO damn how + :: ~& eyre-ws-event=-.event ?+ -.event `state %message :_ state :~ %+ deal-as - /run-ws-app-request/(scot %uw (cut 3 [2 4] eny)) + /run-ws-app-request/[wsid] :^ identity our app :+ %poke %websocket-server-message - !>([wid message.event]) + !>([wid u.pat message.event]) == %disconnect =. connections.state (~(del by connections.state) duct) @@ -1070,30 +1074,33 @@ ++ ws-handshake |= [wid=@ secure=? =address:eyre =request:http] ^- [(list move) server-state] - ~& >>> sending-ws-handshake=[wid eyre-id] =/ host=(unit @t) (get-header:http 'host' header-list.request) =/ [=action suburl=@t] (get-action-for-binding host url.request) :: TODO enable other actions - ?> ?=(%app -.action) + ?. ?=(%app -.action) `state :: TODO!! get clear what all the identity thing has to be - =/ =identity [%ours ~] =/ app app.action - =^ ?(invalid=@uv [suv=@uv fi=^identity som=(list move)]) state + =^ ?(invalid=@uv [@uv identity (list move)]) state (session-for-request:authentication request) - =/ connection=outstanding-connection + =/ [session-id=@uv =identity som=(list move)] ?@ - ~& invalid-session=- - [action [.n secure address request] [invalid identity] ~ 0] - [action [.n secure address request] [suv identity] ~ 0] + [invalid [%fake *@p] ~] - + + =/ authenticated ?=(%ours -.identity) + =/ connection=outstanding-connection + [action [authenticated secure address request] [session-id identity] ~ 0] =. connections.state (~(put by connections.state) duct connection) :: eyre-id is assigned way up in this arm =/ wsid (scot %ud wid) :_ state + ~& som=som + %+ weld som :~ %+ deal-as /ws-watch-response/[wsid] [identity our app %watch /websocket-server/[wsid]] @@ -3202,7 +3209,6 @@ ++ handle-ws-response |= [wid=@ event=websocket-event] ^- [(list move) server-state] - ~& eyre-handle-ws-response=event :: TODO remove if not accepted? =. connections.state ?. ?=(%reject -.event) connections.state @@ -3655,7 +3661,6 @@ ^- [(list move) _http-server-gate] :: =/ task=task ((harden task) wrapped-task) - ~& > eyre-task=[-.task duct=duct] :: :: XX handle more error notifications :: @@ -3952,7 +3957,6 @@ =^ moves server-state.ax (set-response:server +.task) [moves http-server-gate] %websocket-event - ~& websocket-event=+.task =^ moves server-state.ax (ws-event:server +.task) [moves http-server-gate] @@ -3965,8 +3969,6 @@ ++ take ~/ %eyre-take |= [=wire =duct dud=(unit goof) =sign] - ~& >>> duct=duct - ~& >> take-wire-eyre=wire ^- [(list move) _http-server-gate] => %= . sign @@ -4001,7 +4003,6 @@ ++ watch-ws-response =/ event-args [[eny duct now rof] server-state.ax] ?> ?=([@ *] t.wire) - ~& >> ws-sign=[`@t`-.sign `@t`+<.sign ((soft @t) +>-.sign)] ?+ sign `http-server-gate [%gall %unto %watch-ack *] ?~ p.p.sign @@ -4017,14 +4018,12 @@ [moves http-server-gate] [%gall %unto %fact *] =/ mark p.cage.p.sign - ~& > eyre-ws-response-fact=mark ?. ?=(%websocket-response mark) =/ handle-gall-error handle-gall-error:(per-server-event event-args) =^ moves server-state.ax (handle-gall-error leaf+"eyre bad mark {(trip mark)}" ~) [moves http-server-gate] - ~& websocket-vase=q.q.cage.p.sign =/ event !<([@ websocket-event] q.cage.p.sign) =/ handle-ws-response handle-ws-response:(per-server-event event-args) =^ moves server-state.ax @@ -4033,7 +4032,6 @@ == ++ run-ws-app-request - ~& run-ws-app-req=sign `http-server-gate @@ -4063,7 +4061,6 @@ =/ event-args [[eny duct now rof] server-state.ax] :: ?> ?=([@ *] t.wire) - ~& >> http-sign=[`@t`-.sign `@t`+<.sign ((soft @t) +>-.sign)] ?: ?=([%gall %unto %watch-ack *] sign) ?~ p.p.sign :: received a positive acknowledgment: take no action @@ -4085,7 +4082,6 @@ :: ?> ?=([%gall %unto %fact *] sign) =/ =mark p.cage.p.sign - ~& eyre-watch-response-fact=mark =/ =vase q.cage.p.sign ?. ?= ?(%http-response-header %http-response-data %http-response-cancel) mark diff --git a/gui/.envrc b/gui/.envrc new file mode 100644 index 0000000..7e9a2d6 --- /dev/null +++ b/gui/.envrc @@ -0,0 +1,10 @@ +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +# `use devenv` supports the same options as the `devenv shell` command. +# +# To silence the output, use `--quiet`. +# +# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true +use devenv diff --git a/gui/.gitignore b/gui/.gitignore new file mode 100644 index 0000000..356ff08 --- /dev/null +++ b/gui/.gitignore @@ -0,0 +1,44 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/gui/README.md b/gui/README.md new file mode 100644 index 0000000..7959ce4 --- /dev/null +++ b/gui/README.md @@ -0,0 +1,69 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/gui/bun.lock b/gui/bun.lock new file mode 100644 index 0000000..77fd532 --- /dev/null +++ b/gui/bun.lock @@ -0,0 +1,650 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "front", + "dependencies": { + "@tailwindcss/vite": "^4.1.14", + "@tanstack/react-query": "^5.85.9", + "any-ascii": "^0.3.3", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", + "tailwindcss": "^4.1.14", + "urbit-api": "file:../../../urbit/bun/http-api", + "urbit-ob": "file:../../../urbit/bun/urbit-ob", + "urbit-sigils": "file:../../../urbit/bun/sigil-ts", + "wouter": "^3.7.1", + "zustand": "^5.0.8", + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.28.4", "", {}, "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw=="], + + "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], + + "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/types": "^7.28.4", "debug": "^4.3.1" } }, "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ=="], + + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog=="], + + "@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.37.0", "", {}, "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.38", "", {}, "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.4", "", { "os": "none", "cpu": "arm64" }, "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.0", "lightningcss": "1.30.1", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.14" } }, "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.14", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.5.1" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.14", "@tailwindcss/oxide-darwin-arm64": "4.1.14", "@tailwindcss/oxide-darwin-x64": "4.1.14", "@tailwindcss/oxide-freebsd-x64": "4.1.14", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.14", "@tailwindcss/oxide-linux-arm64-musl": "4.1.14", "@tailwindcss/oxide-linux-x64-gnu": "4.1.14", "@tailwindcss/oxide-linux-x64-musl": "4.1.14", "@tailwindcss/oxide-wasm32-wasi": "4.1.14", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.14", "@tailwindcss/oxide-win32-x64-msvc": "4.1.14" } }, "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.14", "", { "os": "android", "cpu": "arm64" }, "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14", "", { "os": "linux", "cpu": "arm" }, "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.14", "", { "os": "linux", "cpu": "x64" }, "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.14", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.5", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.14", "", { "os": "win32", "cpu": "x64" }, "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.14", "", { "dependencies": { "@tailwindcss/node": "4.1.14", "@tailwindcss/oxide": "4.1.14", "tailwindcss": "4.1.14" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.90.2", "", {}, "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.90.2", "", { "dependencies": { "@tanstack/query-core": "5.90.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/invariant": ["@types/invariant@2.2.37", "", {}, "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="], + + "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="], + + "@types/react-dom": ["@types/react-dom@19.2.0", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.45.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/type-utils": "8.45.0", "@typescript-eslint/utils": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.45.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.45.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.45.0", "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0" } }, "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.45.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.45.0", "", {}, "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.45.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.45.0", "@typescript-eslint/tsconfig-utils": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.45.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.4", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "any-ascii": ["any-ascii@0.3.3", "", {}, "sha512-8hm+zPrc1VnlxD5eRgMo9F9k2wEMZhbZVLKwA/sPKIt6ywuz7bI9uV/yb27uvc8fv8q6Wl2piJT51q1saKX0Jw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="], + + "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001748", "", {}, "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deep-rename-keys": ["deep-rename-keys@0.2.1", "", { "dependencies": { "kind-of": "^3.0.2", "rename-keys": "^1.1.2" } }, "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.230", "", {}, "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.4.0", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.37.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.23", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@2.0.3", "", {}, "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], + + "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], + + "is-buffer": ["is-buffer@1.1.6", "", {}, "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + + "react-hot-toast": ["react-hot-toast@2.6.0", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="], + + "rename-keys": ["rename-keys@1.2.0", "", {}, "sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.52.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "svgson": ["svgson@5.3.1", "", { "dependencies": { "deep-rename-keys": "^0.2.1", "xml-reader": "2.4.3" } }, "sha512-qdPgvUNWb40gWktBJnbJRelWcPzkLed/ShhnRsjbayXz8OtdPOzbil9jtiZdrYvSDumAz/VNQr6JaNfPx/gvPA=="], + + "tailwindcss": ["tailwindcss@4.1.14", "", {}, "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "transformation-matrix": ["transformation-matrix@3.1.0", "", {}, "sha512-oYubRWTi2tYFHAL2J8DLvPIqIYcYZ0fSOi2vmSy042Ho4jBW2ce6VP7QfD44t65WQz6bw5w1Pk22J7lcUpaTKA=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.45.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.45.0", "@typescript-eslint/parser": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/utils": "8.45.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg=="], + + "undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "urbit-api": ["@urbit/http-api@file:../../../urbit/bun/http-api", { "devDependencies": { "@types/bun": "latest", "typescript": "^5" } }], + + "urbit-ob": ["urbit-ob-ts@file:../../../urbit/bun/urbit-ob", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], + + "urbit-sigils": ["sigil-ts@file:../../../urbit/bun/sigil-ts", { "dependencies": { "invariant": "^2.2.4", "react": "^19.1.0", "svgson": "^5.3.1", "transformation-matrix": "^3.0.0" }, "devDependencies": { "@types/bun": "latest", "@types/invariant": "^2.2.37", "@types/react": "^19.1.2" }, "peerDependencies": { "typescript": "^5" } }], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "vite": ["vite@7.1.9", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wouter": ["wouter@3.7.1", "", { "dependencies": { "mitt": "^3.0.1", "regexparam": "^3.0.0", "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-od5LGmndSUzntZkE2R5CHhoiJ7YMuTIbiXsa0Anytc2RATekgv4sfWRAxLEULBrp7ADzinWQw8g470lkT8+fOw=="], + + "xml-lexer": ["xml-lexer@0.2.2", "", { "dependencies": { "eventemitter3": "^2.0.0" } }, "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w=="], + + "xml-reader": ["xml-reader@2.4.3", "", { "dependencies": { "eventemitter3": "^2.0.0", "xml-lexer": "^0.2.2" } }, "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + } +} diff --git a/gui/devenv.lock b/gui/devenv.lock new file mode 100644 index 0000000..19bac94 --- /dev/null +++ b/gui/devenv.lock @@ -0,0 +1,103 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1758064567, + "owner": "cachix", + "repo": "devenv", + "rev": "bc697443a9653586e5be5150b4458f3096a93f67", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1757974173, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "302af509428169db34f268324162712d10559f74", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1755783167, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "4a880fb247d24fbca57269af672e8f78935b0328", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + } + }, + "root": "root", + "version": 7 +} diff --git a/gui/devenv.nix b/gui/devenv.nix new file mode 100644 index 0000000..e4e3748 --- /dev/null +++ b/gui/devenv.nix @@ -0,0 +1,57 @@ +{ + pkgs, + lib, + config, + inputs, + ... +}: { + # https://devenv.sh/basics/ + env.GREET = "devenv"; + + # https://devenv.sh/packages/ + packages = with pkgs; [ + git + nodePackages.typescript-language-server + nodePackages.prettier + ]; + + # https://devenv.sh/languages/ + # languages.rust.enable = true; + languages.javascript = { + enable = true; + bun.enable = true; + }; + + # https://devenv.sh/processes/ + # processes.cargo-watch.exec = "cargo-watch"; + + # https://devenv.sh/services/ + # services.postgres.enable = true; + + # https://devenv.sh/scripts/ + scripts.hello.exec = '' + echo hello from $GREET + ''; + + enterShell = '' + hello + git --version + ''; + + # https://devenv.sh/tasks/ + # tasks = { + # "myproj:setup".exec = "mytool build"; + # "devenv:enterShell".after = [ "myproj:setup" ]; + # }; + + # https://devenv.sh/tests/ + enterTest = '' + echo "Running tests" + git --version | grep --color=auto "${pkgs.git.version}" + ''; + + # https://devenv.sh/git-hooks/ + # git-hooks.hooks.shellcheck.enable = true; + + # See full reference at https://devenv.sh/reference/options/ +} diff --git a/gui/devenv.yaml b/gui/devenv.yaml new file mode 100644 index 0000000..116a2ad --- /dev/null +++ b/gui/devenv.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + +# If you're using non-OSS software, you can set allowUnfree to true. +# allowUnfree: true + +# If you're willing to use a package that's vulnerable +# permittedInsecurePackages: +# - "openssl-1.1.1w" + +# If you have more than one devenv you can merge them +#imports: +# - ./backend diff --git a/gui/eslint.config.js b/gui/eslint.config.js new file mode 100644 index 0000000..d94e7de --- /dev/null +++ b/gui/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/gui/index.html b/gui/index.html new file mode 100644 index 0000000..94cc361 --- /dev/null +++ b/gui/index.html @@ -0,0 +1,16 @@ + + + + + + + + Nostrill + + + +
+ + + + \ No newline at end of file diff --git a/gui/package.json b/gui/package.json new file mode 100644 index 0000000..4ddb8b3 --- /dev/null +++ b/gui/package.json @@ -0,0 +1,39 @@ +{ + "name": "front", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.14", + "@tanstack/react-query": "^5.85.9", + "any-ascii": "^0.3.3", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", + "tailwindcss": "^4.1.14", + "urbit-api": "file:../../../urbit/bun/http-api", + "urbit-ob": "file:../../../urbit/bun/urbit-ob", + "urbit-sigils": "file:../../../urbit/bun/sigil-ts", + "wouter": "^3.7.1", + "zustand": "^5.0.8" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react": "^5.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2" + } +} diff --git a/gui/public/favicon.svg b/gui/public/favicon.svg new file mode 100644 index 0000000..790198c --- /dev/null +++ b/gui/public/favicon.svg @@ -0,0 +1,22 @@ + + + + diff --git a/gui/public/fonts/Inter b/gui/public/fonts/Inter new file mode 120000 index 0000000..7a1c26c --- /dev/null +++ b/gui/public/fonts/Inter @@ -0,0 +1 @@ +/home/y/code/fonts/Inter \ No newline at end of file diff --git a/gui/public/fonts/Source_Code_Pro b/gui/public/fonts/Source_Code_Pro new file mode 120000 index 0000000..ab04caf --- /dev/null +++ b/gui/public/fonts/Source_Code_Pro @@ -0,0 +1 @@ +/home/y/code/fonts/Source_Code_Pro \ No newline at end of file diff --git a/gui/public/nostril-icon.png b/gui/public/nostril-icon.png new file mode 100644 index 0000000..73be722 Binary files /dev/null and b/gui/public/nostril-icon.png differ diff --git a/gui/src/App.tsx b/gui/src/App.tsx new file mode 100644 index 0000000..415cb66 --- /dev/null +++ b/gui/src/App.tsx @@ -0,0 +1,46 @@ +import { useEffect, useState } from "react"; +import useLocalState from "@/state/state"; +import Router from "./Router"; +import "@/styles/styles.css"; +import { ThemeProvider } from "@/styles/ThemeProvider"; +import spinner from "@/assets/crowspinner.gif"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "react-hot-toast"; +const queryClient = new QueryClient(); + +// const isMobile = MOBILE_BROWSER_REGEX.test(navigator.userAgent); + +function App() { + const [loading, setLoading] = useState(true); + console.log("NOSTRILL INIT"); + const { init, modal } = useLocalState((s) => ({ + init: s.init, + modal: s.modal, + })); + useEffect(() => { + init().then((_res: any) => { + setLoading(false); + }); + }, []); + if (loading) + return ( +
+ +

Syncing with your Urbit...

+
+ ); + else + return ( + + + {/* {isMobile ? : } */} + + {modal && modal} + + + + ); +} + +export default App; diff --git a/gui/src/Router.tsx b/gui/src/Router.tsx new file mode 100644 index 0000000..ee3aa0d --- /dev/null +++ b/gui/src/Router.tsx @@ -0,0 +1,39 @@ +import Sidebar from "@/components/layout/Sidebar"; + +// new +import Feed from "@/pages/Feed"; +import Settings from "@/pages/Settings"; +import Thread from "@/pages/Thread"; +import { Switch, Router, Redirect, Route } from "wouter"; + +export default function r() { + return ( + + + +
+ + + + +
+
+ +
+ ); +} +function toGlobal() { + return ; +} + +export function P404() { + return

404

; +} +export function ErrorPage({ msg }: { msg: string }) { + return ( +
+ +

{msg}

+
+ ); +} diff --git a/gui/src/assets/crowspinner.gif b/gui/src/assets/crowspinner.gif new file mode 100644 index 0000000..d0033d3 Binary files /dev/null and b/gui/src/assets/crowspinner.gif differ diff --git a/gui/src/assets/icons/bell.svg b/gui/src/assets/icons/bell.svg new file mode 100644 index 0000000..98e88cd --- /dev/null +++ b/gui/src/assets/icons/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/comet.svg b/gui/src/assets/icons/comet.svg new file mode 100644 index 0000000..2d5c3f5 --- /dev/null +++ b/gui/src/assets/icons/comet.svg @@ -0,0 +1,23 @@ + + + + + diff --git a/gui/src/assets/icons/copy.svg b/gui/src/assets/icons/copy.svg new file mode 100644 index 0000000..714e9f5 --- /dev/null +++ b/gui/src/assets/icons/copy.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/gui/src/assets/icons/crow.svg b/gui/src/assets/icons/crow.svg new file mode 100644 index 0000000..e967970 --- /dev/null +++ b/gui/src/assets/icons/crow.svg @@ -0,0 +1,29 @@ + + + + + diff --git a/gui/src/assets/icons/emoji.svg b/gui/src/assets/icons/emoji.svg new file mode 100644 index 0000000..7a957fd --- /dev/null +++ b/gui/src/assets/icons/emoji.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/gui/src/assets/icons/home.svg b/gui/src/assets/icons/home.svg new file mode 100644 index 0000000..64d7984 --- /dev/null +++ b/gui/src/assets/icons/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/key.png b/gui/src/assets/icons/key.png new file mode 100644 index 0000000..2efe10b Binary files /dev/null and b/gui/src/assets/icons/key.png differ diff --git a/gui/src/assets/icons/key.svg b/gui/src/assets/icons/key.svg new file mode 100644 index 0000000..c2ac4b9 --- /dev/null +++ b/gui/src/assets/icons/key.svg @@ -0,0 +1,5 @@ + + + key + + diff --git a/gui/src/assets/icons/logo.png b/gui/src/assets/icons/logo.png new file mode 100644 index 0000000..fdb3f22 Binary files /dev/null and b/gui/src/assets/icons/logo.png differ diff --git a/gui/src/assets/icons/logo.svg b/gui/src/assets/icons/logo.svg new file mode 100644 index 0000000..7cbac7c --- /dev/null +++ b/gui/src/assets/icons/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/gui/src/assets/icons/messages.svg b/gui/src/assets/icons/messages.svg new file mode 100644 index 0000000..8a0b9c3 --- /dev/null +++ b/gui/src/assets/icons/messages.svg @@ -0,0 +1,8 @@ + + + + message + + + + diff --git a/gui/src/assets/icons/nostr.svg b/gui/src/assets/icons/nostr.svg new file mode 100644 index 0000000..80760a8 --- /dev/null +++ b/gui/src/assets/icons/nostr.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/pals.svg b/gui/src/assets/icons/pals.svg new file mode 100644 index 0000000..04b17a3 --- /dev/null +++ b/gui/src/assets/icons/pals.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/profile.svg b/gui/src/assets/icons/profile.svg new file mode 100644 index 0000000..f94e63d --- /dev/null +++ b/gui/src/assets/icons/profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/quote.svg b/gui/src/assets/icons/quote.svg new file mode 100644 index 0000000..5b847e3 --- /dev/null +++ b/gui/src/assets/icons/quote.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/radio.svg b/gui/src/assets/icons/radio.svg new file mode 100644 index 0000000..5c98c15 --- /dev/null +++ b/gui/src/assets/icons/radio.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/reply.svg b/gui/src/assets/icons/reply.svg new file mode 100644 index 0000000..db86cfd --- /dev/null +++ b/gui/src/assets/icons/reply.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/rt.svg b/gui/src/assets/icons/rt.svg new file mode 100644 index 0000000..43b4a36 --- /dev/null +++ b/gui/src/assets/icons/rt.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/rumors.svg b/gui/src/assets/icons/rumors.svg new file mode 100644 index 0000000..2df5165 --- /dev/null +++ b/gui/src/assets/icons/rumors.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/settings.svg b/gui/src/assets/icons/settings.svg new file mode 100644 index 0000000..5c5e400 --- /dev/null +++ b/gui/src/assets/icons/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/gui/src/assets/icons/youtube.svg b/gui/src/assets/icons/youtube.svg new file mode 100644 index 0000000..46d7db9 --- /dev/null +++ b/gui/src/assets/icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gui/src/assets/reacts/chad.png b/gui/src/assets/reacts/chad.png new file mode 100644 index 0000000..51cfa0d Binary files /dev/null and b/gui/src/assets/reacts/chad.png differ diff --git a/gui/src/assets/reacts/cringe.png b/gui/src/assets/reacts/cringe.png new file mode 100644 index 0000000..d39a50c Binary files /dev/null and b/gui/src/assets/reacts/cringe.png differ diff --git a/gui/src/assets/reacts/cry.png b/gui/src/assets/reacts/cry.png new file mode 100644 index 0000000..f70fb28 Binary files /dev/null and b/gui/src/assets/reacts/cry.png differ diff --git a/gui/src/assets/reacts/doom.png b/gui/src/assets/reacts/doom.png new file mode 100644 index 0000000..e6df1f4 Binary files /dev/null and b/gui/src/assets/reacts/doom.png differ diff --git a/gui/src/assets/reacts/facepalm.png b/gui/src/assets/reacts/facepalm.png new file mode 100644 index 0000000..a03def9 Binary files /dev/null and b/gui/src/assets/reacts/facepalm.png differ diff --git a/gui/src/assets/reacts/galaxy.png b/gui/src/assets/reacts/galaxy.png new file mode 100644 index 0000000..3c496d3 Binary files /dev/null and b/gui/src/assets/reacts/galaxy.png differ diff --git a/gui/src/assets/reacts/gigachad.png b/gui/src/assets/reacts/gigachad.png new file mode 100644 index 0000000..5f3c2e1 Binary files /dev/null and b/gui/src/assets/reacts/gigachad.png differ diff --git a/gui/src/assets/reacts/pepechin.png b/gui/src/assets/reacts/pepechin.png new file mode 100644 index 0000000..dafd907 Binary files /dev/null and b/gui/src/assets/reacts/pepechin.png differ diff --git a/gui/src/assets/reacts/pepeeyes.png b/gui/src/assets/reacts/pepeeyes.png new file mode 100644 index 0000000..e57d5e6 Binary files /dev/null and b/gui/src/assets/reacts/pepeeyes.png differ diff --git a/gui/src/assets/reacts/pepegmi.png b/gui/src/assets/reacts/pepegmi.png new file mode 100644 index 0000000..7c3cae4 Binary files /dev/null and b/gui/src/assets/reacts/pepegmi.png differ diff --git a/gui/src/assets/reacts/pepesad.png b/gui/src/assets/reacts/pepesad.png new file mode 100644 index 0000000..51891fd Binary files /dev/null and b/gui/src/assets/reacts/pepesad.png differ diff --git a/gui/src/assets/reacts/pika.png b/gui/src/assets/reacts/pika.png new file mode 100644 index 0000000..791594b Binary files /dev/null and b/gui/src/assets/reacts/pika.png differ diff --git a/gui/src/assets/reacts/pink.png b/gui/src/assets/reacts/pink.png new file mode 100644 index 0000000..59fdc6a Binary files /dev/null and b/gui/src/assets/reacts/pink.png differ diff --git a/gui/src/assets/reacts/soy.png b/gui/src/assets/reacts/soy.png new file mode 100644 index 0000000..33dbe33 Binary files /dev/null and b/gui/src/assets/reacts/soy.png differ diff --git a/gui/src/assets/reacts/yeschad.png b/gui/src/assets/reacts/yeschad.png new file mode 100644 index 0000000..e001332 Binary files /dev/null and b/gui/src/assets/reacts/yeschad.png differ diff --git a/gui/src/assets/triangles.svg b/gui/src/assets/triangles.svg new file mode 100644 index 0000000..0b45c01 --- /dev/null +++ b/gui/src/assets/triangles.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gui/src/components/Avatar.tsx b/gui/src/components/Avatar.tsx new file mode 100644 index 0000000..a071655 --- /dev/null +++ b/gui/src/components/Avatar.tsx @@ -0,0 +1,62 @@ +import useLocalState from "@/state/state"; +import Sigil from "./Sigil"; +import { isValidPatp } from "urbit-ob"; +import type { UserProfile, UserType } from "@/types/nostrill"; +import Icon from "@/components/Icon"; +import UserModal from "./modals/UserModal"; + +export default function ({ + user, + userString, + size, + color, + noClickOnName, + profile, + picOnly = false, +}: { + user: UserType; + userString: string; + size: number; + color?: string; + noClickOnName?: boolean; + profile?: UserProfile; + picOnly?: boolean; +}) { + const { setModal } = useLocalState((s) => ({ setModal: s.setModal })); + // TODO revisit this when %whom updates + console.log({ profile }); + const avatarInner = profile ? ( + + ) : "urbit" in user && isValidPatp(user.urbit) ? ( + + ) : ( + + ); + const avatar = ( +
+ {avatarInner} +
+ ); + if (picOnly) return avatar; + + const tooLong = (s: string) => (s.length > 15 ? " too-long" : ""); + function openModal(e: React.MouseEvent) { + if (noClickOnName) return; + e.stopPropagation(); + setModal(); + } + const name = ( +
+ {profile ? ( +

{profile.name}

+ ) : "urbit" in user ? ( +

+ {user.urbit.length > 28 ? "Anon" : user.urbit} +

+ ) : ( +

{user.nostr}

+ )} +
+ ); + return
{name}
; +} diff --git a/gui/src/components/Icon.tsx b/gui/src/components/Icon.tsx new file mode 100644 index 0000000..797a87b --- /dev/null +++ b/gui/src/components/Icon.tsx @@ -0,0 +1,137 @@ +import { useTheme } from "@/styles/ThemeProvider"; + +import bellSvg from "@/assets/icons/bell.svg"; +import cometSvg from "@/assets/icons/comet.svg"; +import copySvg from "@/assets/icons/copy.svg"; +import crowSvg from "@/assets/icons/crow.svg"; +import emojiSvg from "@/assets/icons/emoji.svg"; +import homeSvg from "@/assets/icons/home.svg"; +import keySvg from "@/assets/icons/key.svg"; +import messagesSvg from "@/assets/icons/messages.svg"; +import nostrSvg from "@/assets/icons/nostr.svg"; +import palsSvg from "@/assets/icons/pals.svg"; +import profileSvg from "@/assets/icons/profile.svg"; +import quoteSvg from "@/assets/icons/quote.svg"; +import radioSvg from "@/assets/icons/radio.svg"; +import replySvg from "@/assets/icons/reply.svg"; +import repostSvg from "@/assets/icons/rt.svg"; +import rumorsSvg from "@/assets/icons/rumors.svg"; +import settingsSvg from "@/assets/icons/settings.svg"; +import youtubeSvg from "@/assets/icons/youtube.svg"; + +export type IconName = + | "bell" + | "comet" + | "copy" + | "crow" + | "emoji" + | "home" + | "key" + | "messages" + | "nostr" + | "pals" + | "profile" + | "quote" + | "radio" + | "reply" + | "repost" + | "rumors" + | "settings" + | "youtube"; + +const iconMap: Record = { + bell: bellSvg, + comet: cometSvg, + copy: copySvg, + crow: crowSvg, + emoji: emojiSvg, + home: homeSvg, + key: keySvg, + messages: messagesSvg, + nostr: nostrSvg, + pals: palsSvg, + profile: profileSvg, + quote: quoteSvg, + radio: radioSvg, + reply: replySvg, + repost: repostSvg, + rumors: rumorsSvg, + settings: settingsSvg, + youtube: youtubeSvg, +}; + +interface IconProps { + name: IconName; + size?: number; + className?: string; + title?: string; + onClick?: (e: React.MouseEvent) => any; + color?: "primary" | "text" | "textSecondary" | "textMuted" | "custom"; + customColor?: string; +} + +const Icon: React.FC = ({ + name, + size = 20, + className = "", + title, + onClick, + color = "text", + customColor, +}) => { + const { theme } = useTheme(); + + // Simple filter based on theme - icons should match text + const getFilter = () => { + // For dark themes, invert the black SVGs to white + if ( + theme.name === "dark" || + theme.name === "noir" || + theme.name === "gruvbox" + ) { + return "invert(1)"; + } + // For light themes with dark text, keep as is + if (theme.name === "light") { + return "none"; + } + // For colored themes, adjust brightness/contrast + if (theme.name === "sepia") { + return "sepia(1) saturate(2) hue-rotate(20deg) brightness(0.8)"; + } + if (theme.name === "ocean") { + return "brightness(0) saturate(100%) invert(13%) sepia(95%) saturate(3207%) hue-rotate(195deg) brightness(94%) contrast(106%)"; + } + if (theme.name === "forest") { + return "brightness(0) saturate(100%) invert(24%) sepia(95%) saturate(1352%) hue-rotate(87deg) brightness(92%) contrast(96%)"; + } + return "none"; + }; + + const iconUrl = iconMap[name]; + + if (!iconUrl) { + console.error(`Icon "${name}" not found`); + return null; + } + + return ( + {title + ); +}; + +export default Icon; diff --git a/gui/src/components/NotificationCenter.tsx b/gui/src/components/NotificationCenter.tsx new file mode 100644 index 0000000..44a6799 --- /dev/null +++ b/gui/src/components/NotificationCenter.tsx @@ -0,0 +1,192 @@ +import { useState } from "react"; +import useLocalState from "@/state/state"; +import Modal from "./modals/Modal"; +import Icon from "./Icon"; +import Avatar from "./Avatar"; +import { useLocation } from "wouter"; +import type { Notification, NotificationType } from "@/types/notifications"; +import "@/styles/NotificationCenter.css"; + +const NotificationCenter = () => { + const [_, navigate] = useLocation(); + const { + notifications, + unreadNotifications, + markNotificationRead, + markAllNotificationsRead, + clearNotifications, + setModal + } = useLocalState((s) => ({ + notifications: s.notifications, + unreadNotifications: s.unreadNotifications, + markNotificationRead: s.markNotificationRead, + markAllNotificationsRead: s.markAllNotificationsRead, + clearNotifications: s.clearNotifications, + setModal: s.setModal + })); + + const [filter, setFilter] = useState<"all" | "unread">("all"); + + const filteredNotifications = filter === "unread" + ? notifications.filter(n => !n.read) + : notifications; + + const handleNotificationClick = (notification: Notification) => { + // Mark as read + if (!notification.read) { + markNotificationRead(notification.id); + } + + // Navigate based on notification type + if (notification.postId) { + // Navigate to post + navigate(`/post/${notification.postId}`); + setModal(null); + } else if (notification.type === "follow" || notification.type === "access_request") { + // Navigate to user profile + navigate(`/feed/${notification.from}`); + setModal(null); + } + }; + + const getNotificationIcon = (type: NotificationType) => { + switch (type) { + case "follow": + case "unfollow": + return "pals"; + case "mention": + case "reply": + return "messages"; + case "repost": + return "repost"; + case "react": + return "emoji"; + case "access_request": + case "access_granted": + return "key"; + default: + return "bell"; + } + }; + + const getNotificationText = (notification: Notification) => { + switch (notification.type) { + case "follow": + return `${notification.from} started following you`; + case "unfollow": + return `${notification.from} unfollowed you`; + case "mention": + return `${notification.from} mentioned you in a post`; + case "reply": + return `${notification.from} replied to your post`; + case "repost": + return `${notification.from} reposted your post`; + case "react": + return `${notification.from} reacted ${notification.reaction || ""} to your post`; + case "access_request": + return `${notification.from} requested access to your feed`; + case "access_granted": + return `${notification.from} granted you access to their feed`; + default: + return notification.message || "New notification"; + } + }; + + const formatTimestamp = (date: Date) => { + const now = new Date(); + const diff = now.getTime() - new Date(date).getTime(); + const minutes = Math.floor(diff / 60000); + const hours = Math.floor(diff / 3600000); + const days = Math.floor(diff / 86400000); + + if (minutes < 1) return "Just now"; + if (minutes < 60) return `${minutes}m ago`; + if (hours < 24) return `${hours}h ago`; + if (days < 7) return `${days}d ago`; + return new Date(date).toLocaleDateString(); + }; + + return ( + setModal(null)}> +
+
+

Notifications

+
+ {unreadNotifications > 0 && ( + + )} + {notifications.length > 0 && ( + + )} +
+
+ +
+ + +
+ +
+ {filteredNotifications.length === 0 ? ( +
+ +

No {filter === "unread" ? "unread " : ""}notifications

+
+ ) : ( + filteredNotifications.map((notification) => ( +
handleNotificationClick(notification)} + > +
+ +
+ +
+
+ +
+

{getNotificationText(notification)}

+ + {formatTimestamp(notification.timestamp)} + +
+
+
+ + {!notification.read &&
} +
+ )) + )} +
+
+ + ); +}; + +export default NotificationCenter; \ No newline at end of file diff --git a/gui/src/components/Sigil.tsx b/gui/src/components/Sigil.tsx new file mode 100644 index 0000000..cbc2e57 --- /dev/null +++ b/gui/src/components/Sigil.tsx @@ -0,0 +1,50 @@ +import Icon from "@/components/Icon"; +import { auraToHex } from "@/logic/utils"; +import { isValidPatp } from "urbit-ob"; +import { sigil } from "urbit-sigils"; +import { reactRenderer } from "urbit-sigils"; + +interface SigilProps { + patp: string; + size: number; + bg?: string; + fg?: string; +} + +const Sigil = (props: SigilProps) => { + const bg = props.bg ? auraToHex(props.bg) : "var(--color-background)"; + const fg = props.fg ? auraToHex(props.fg) : "var(--color-primary)"; + if (props.patp.length > 28) + return ( + + ); + else if (props.patp.length > 15) + // moons + return ( + <> + {sigil({ + patp: props.patp.substring(props.patp.length - 13), + renderer: reactRenderer, + size: props.size, + colors: ["grey", "white"], + })} + + ); + else + return ( + <> + {sigil({ + patp: props.patp, + renderer: reactRenderer, + size: props.size, + colors: [bg, fg], + })} + + ); +}; + +export default Sigil; diff --git a/gui/src/components/WsWidget.tsx b/gui/src/components/WsWidget.tsx new file mode 100644 index 0000000..75c773d --- /dev/null +++ b/gui/src/components/WsWidget.tsx @@ -0,0 +1,123 @@ +import { useWebSocket } from "@/hooks/useWs"; +import { useState } from "react"; + +type WidgetProps = { + url: string; + protocols?: string | string[]; +}; + +export default function WebSocketWidget({ url, protocols }: WidgetProps) { + const { + status, + retryCount, + lastMessage, + error, + bufferedAmount, + send, + reconnectNow, + close, + } = useWebSocket({ + url, + protocols, + onMessage: (ev) => { + // Example: auto reply to pings + console.log(ev.data, "ws event"); + if ( + typeof ev.data === "string" && + // ev.data.toLowerCase().includes("ping") + ev.data.toLowerCase().trim() == "ping" + ) { + try { + console.log("sending pong"); + send("pong"); + } catch {} + } + }, + }); + + const [outbound, setOutbound] = useState(""); + + return ( +
+
+

WebSocketWidget

+ + {status.toUpperCase()} {retryCount ? `(retry ${retryCount})` : ""} + +
+ +
+
+ URL: {url} +
+
+ Buffered: {bufferedAmount} bytes +
+ {error && ( +
+ Error:{" "} + {"message" in error + ? (error as any).message + : String(error.type || "error")} +
+ )} +
+ +
+
Last message:
+
+ {lastMessage + ? typeof lastMessage.data === "string" + ? lastMessage.data + : "(binary)" + : "—"} +
+
+ +
{ + e.preventDefault(); + if (!outbound) return; + send(outbound); + setOutbound(""); + }} + > + setOutbound(e.target.value)} + /> + +
+ +
+ + +
+ +
+ Usage +
+          {`import WebSocketWidget from "./WebSocketWidget";
+
+export default function App() {
+  return (
+    
+ +
+ ); +} +`} +
+
+
+ ); +} diff --git a/gui/src/components/composer/Composer.tsx b/gui/src/components/composer/Composer.tsx new file mode 100644 index 0000000..81d0358 --- /dev/null +++ b/gui/src/components/composer/Composer.tsx @@ -0,0 +1,205 @@ +import useLocalState from "@/state/state"; +import type { Poast } from "@/types/trill"; +import Sigil from "@/components/Sigil"; +import { useState, useEffect, useRef, type FormEvent } from "react"; +import Snippets, { ReplySnippet } from "./Snippets"; +import toast from "react-hot-toast"; +import Icon from "@/components/Icon"; +import { wait } from "@/logic/utils"; + +function Composer({ isAnon }: { isAnon?: boolean }) { + const { api, composerData, addNotification, setComposerData } = useLocalState( + (s) => ({ + api: s.api, + composerData: s.composerData, + addNotification: s.addNotification, + setComposerData: s.setComposerData, + }), + ); + const our = api!.airlock.our!; + const [input, setInput] = useState(""); + const [isExpanded, setIsExpanded] = useState(false); + const [isLoading, setLoading] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (composerData) { + setIsExpanded(true); + if ( + composerData.type === "reply" && + composerData.post && + "trill" in composerData.post + ) { + const author = composerData.post.trill.author; + setInput(`${author} `); + } + // Auto-focus input when composer opens + setTimeout(() => { + inputRef.current?.focus(); + }, 100); // Small delay to ensure the composer is rendered + } + }, [composerData]); + async function poast(e: FormEvent) { + e.preventDefault(); + // TODO + // const parent = replying ? replying : null; + // const tokens = tokenize(input); + // const post: SentPoast = { + // host: parent ? parent.host : our, + // author: our, + // thread: parent ? parent.thread : null, + // parent: parent ? parent.id : null, + // contents: input, + // read: openLock, + // write: openLock, + // tags: input.match(HASHTAGS_REGEX) || [], + // }; + // TODO make it user choosable + setLoading(true); + + const res = + composerData?.type === "reply" && "trill" in composerData.post + ? api!.addReply( + input, + composerData.post.trill.host, + composerData.post.trill.id, + composerData.post.trill.thread || composerData.post.trill.id, + ) + : composerData?.type === "quote" && "trill" in composerData.post + ? api!.addQuote(input, { + ship: composerData.post.trill.host, + id: composerData.post.trill.id, + }) + : !composerData + ? api!.addPost(input) + : wait(500); + const ares = await res; + if (ares) { + // // Check for mentions in the post (ship names starting with ~) + const mentions = input.match(/~[a-z-]+/g); + if (mentions) { + mentions.forEach((mention) => { + if (mention !== our) { + // Don't notify self-mentions + addNotification({ + type: "mention", + from: our, + message: `You mentioned ${mention} in a post`, + }); + } + }); + } + + // If this is a reply, add notification + if ( + composerData?.type === "reply" && + composerData.post && + "trill" in composerData.post + ) { + if (composerData.post.trill.author !== our) { + addNotification({ + type: "reply", + from: our, + message: `You replied to ${composerData.post.trill.author}'s post`, + postId: composerData.post.trill.id, + }); + } + } + + setInput(""); + setComposerData(null); // Clear composer data after successful post + toast.success("post sent"); + setIsExpanded(false); + } + } + const placeHolder = + composerData?.type === "reply" + ? "Write your reply..." + : composerData?.type === "quote" + ? "Add your thoughts..." + : isAnon + ? "> be me" + : "What's going on in Urbit"; + + const clearComposer = (e: React.MouseEvent) => { + e.preventDefault(); + setComposerData(null); + setInput(""); + setIsExpanded(false); + }; + + return ( +
+
+ +
+ +
+ {/* Reply snippets appear above input */} + {composerData && composerData.type === "reply" && ( +
+
+ + Replying to + + +
+ +
+ )} + + {/* Quote context header above input (without snippet) */} + {composerData && composerData.type === "quote" && ( +
+
+ + Quote posting + + +
+
+ )} + +
+ setInput(e.currentTarget.value)} + onFocus={() => setIsExpanded(true)} + placeholder={placeHolder} + /> + +
+ + {/* Quote snippets appear below input */} + {composerData && composerData.type === "quote" && ( +
+ +
+ )} +
+
+ ); +} + +export default Composer; diff --git a/gui/src/components/composer/Snippets.tsx b/gui/src/components/composer/Snippets.tsx new file mode 100644 index 0000000..49d9b88 --- /dev/null +++ b/gui/src/components/composer/Snippets.tsx @@ -0,0 +1,86 @@ +import Quote from "@/components/post/Quote"; +import type { SPID } from "@/types/ui"; +import { NostrSnippet } from "../post/wrappers/Nostr"; + +export default Snippets; +function Snippets({ post }: { post: SPID }) { + return ( + + + + ); +} + +export function ComposerSnippet({ + onClick, + children, +}: { + onClick?: any; + children: any; +}) { + function onc(e: React.MouseEvent) { + e.stopPropagation(); + if (onClick) onClick(); + } + return ( +
+ {onClick && ( +
+ × +
+ )} + {children} +
+ ); +} +function PostSnippet({ post }: { post: SPID }) { + if (!post) return
No post data
; + + try { + if ("trill" in post) return ; + else if ("nostr" in post) return ; + // else if ("twatter" in post) + // return ( + //
+ // + //
+ // ); + // else if ("rumors" in post) + // return ( + //
+ //
+ // + // {}} /> + // {date_diff(post.post.time, "short")} + //
+ //
+ // ); + else return
Unsupported post type
; + } catch (error) { + console.error("Error rendering post snippet:", error); + return
Failed to load post
; + } +} + +export function ReplySnippet({ post }: { post: SPID }) { + if (!post) return
No post to reply to
; + + try { + if ("trill" in post) + return ( +
+ +
+ ); + else if ("nostr" in post) + return ( +
+ +
+ ); + else return
Cannot reply to this post type
; + } catch (error) { + console.error("Error rendering reply snippet:", error); + return
Failed to load reply context
; + } +} diff --git a/gui/src/components/feed/PostList.tsx b/gui/src/components/feed/PostList.tsx new file mode 100644 index 0000000..b09a0e9 --- /dev/null +++ b/gui/src/components/feed/PostList.tsx @@ -0,0 +1,33 @@ +import TrillPost from "@/components/post/Post"; +import type { FC } from "@/types/trill"; +// import { useEffect } from "react"; +// import { useQueryClient } from "@tanstack/react-query"; +// import { toFull } from "../thread/helpers"; + +function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) { + // const qc = useQueryClient(); + // useEffect(() => { + // Object.values(data.feed).forEach((poast) => { + // const queryKey = ["trill-thread", poast.host, poast.id]; + // const existing = qc.getQueryData(queryKey); + // if (!existing || !("fpost" in (existing as any))) { + // qc.setQueryData(queryKey, { + // fpost: toFull(poast), + // }); + // } + // }); + // }, [data]); + return ( + <> + {Object.keys(data.feed) + .sort() + .reverse() + .slice(0, 50) + .map((i) => ( + + ))} + + ); +} + +export default TrillFeed; diff --git a/gui/src/components/layout/Sidebar.tsx b/gui/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..c267e2f --- /dev/null +++ b/gui/src/components/layout/Sidebar.tsx @@ -0,0 +1,80 @@ +import { RADIO, versionNum } from "@/logic/constants"; +import { useLocation } from "wouter"; +import useLocalState from "@/state/state"; +import logo from "@/assets/icons/logo.png"; +import Icon from "@/components/Icon"; +import { ThemeSwitcher } from "@/styles/ThemeSwitcher"; + +function SlidingMenu() { + const [_, navigate] = useLocation(); + const { api, unreadNotifications, setModal } = useLocalState((s) => ({ + api: s.api, + unreadNotifications: s.unreadNotifications, + setModal: s.setModal + })); + + function goto(to: string) { + navigate(to); + } + + function openNotifications() { + // We'll create this component next + import("../NotificationCenter").then(({ default: NotificationCenter }) => { + setModal(); + }); + } + return ( +
+ +

Feeds

+
goto(`/feed/global`)}> + +
Home
+
+
+
+ + {unreadNotifications > 0 && ( + + {unreadNotifications > 99 ? "99+" : unreadNotifications} + + )} +
+
Notifications
+
+
+ +
goto("/chat")} + > + +
Messages
+
+
goto("/pals")}> + +
Pals
+
+
+
goto(`/feed/${api!.airlock.our}`)} + > + +
Profile
+
+
+
goto("/sets")}> + +
Settings
+
+ +
+ ); +} +export default SlidingMenu; diff --git a/gui/src/components/modals/Modal.tsx b/gui/src/components/modals/Modal.tsx new file mode 100644 index 0000000..e7bae78 --- /dev/null +++ b/gui/src/components/modals/Modal.tsx @@ -0,0 +1,72 @@ +import useLocalState from "@/state/state"; +import { useEffect, useRef, useState } from "react"; + +function Modal({ children }: any) { + const { setModal } = useLocalState((s) => ({ setModal: s.setModal })); + function onKey(event: any) { + if (event.key === "Escape") setModal(null); + } + useEffect(() => { + document.addEventListener("keyup", onKey); + return () => { + document.removeEventListener("keyup", onKey); + }; + }, [children]); + + function clickAway(e: React.MouseEvent) { + console.log("clicked away"); + e.stopPropagation(); + if (!modalRef.current || !modalRef.current.contains(e.target)) + setModal(null); + } + const modalRef = useRef(null); + return ( + + ); +} +export default Modal; + +export function Welcome() { + return ( + +
+

Welcome to Nostril!

+

+ Trill is the world's only truly free and sovereign social media + platform, powered by Urbit. +

+

+ Click on the crow icon on the top left to see all available feeds. +

+

The Global feed should be populated by default.

+

Follow people soon so your Global feed doesn't go stale.

+

+ Trill is still on beta. The UI is Mobile only, we recommend you use + your phone or the browser dev tools. Desktop UI is on the works. +

+

+ If you have any feedback please reach out to us on Groups at + ~hoster-dozzod-sortug/trill or here at ~polwex +

+
+
+ ); +} + +export function Tooltip({ children, text, className }: any) { + const [show, toggle] = useState(false); + return ( +
toggle(true)} + onMouseOut={() => toggle(false)} + > + {children} + {show &&
{text}
} +
+ ); +} diff --git a/gui/src/components/modals/ShipModal.tsx b/gui/src/components/modals/ShipModal.tsx new file mode 100644 index 0000000..e823a3a --- /dev/null +++ b/gui/src/components/modals/ShipModal.tsx @@ -0,0 +1,48 @@ +import type { Ship } from "@/types/urbit"; +import Modal from "./Modal"; +import Avatar from "../Avatar"; +import Icon from "@/components/Icon"; +import useLocalState from "@/state/state"; +import { useLocation } from "wouter"; +import toast from "react-hot-toast"; + +export default function ({ ship }: { ship: Ship }) { + const { setModal, api } = useLocalState((s) => ({ + setModal: s.setModal, + api: s.api, + })); + const [_, navigate] = useLocation(); + function close() { + setModal(null); + } + async function copy(e: React.MouseEvent) { + e.stopPropagation(); + await navigator.clipboard.writeText(ship); + toast.success("Copied to clipboard"); + } + return ( + +
+
+ + +
+
+ + + {ship !== api!.airlock.our && ( + <> + + + )} +
+
+
+ ); +} diff --git a/gui/src/components/modals/UserModal.tsx b/gui/src/components/modals/UserModal.tsx new file mode 100644 index 0000000..6e3089d --- /dev/null +++ b/gui/src/components/modals/UserModal.tsx @@ -0,0 +1,65 @@ +import Modal from "./Modal"; +import Avatar from "../Avatar"; +import Icon from "@/components/Icon"; +import useLocalState from "@/state/state"; +import { useLocation } from "wouter"; +import toast from "react-hot-toast"; +import type { UserType } from "@/types/nostrill"; + +export default function ({ + user, + userString, +}: { + user: UserType; + userString: string; +}) { + const { setModal, api, pubkey } = useLocalState((s) => ({ + setModal: s.setModal, + api: s.api, + pubkey: s.pubkey, + })); + const [_, navigate] = useLocation(); + function close() { + setModal(null); + } + const itsMe = + "urbit" in user + ? user.urbit === api?.airlock.our + : "nostr" in user + ? user.nostr === pubkey + : false; + async function copy(e: React.MouseEvent) { + e.stopPropagation(); + await navigator.clipboard.writeText(userString); + toast.success("Copied to clipboard"); + } + return ( + +
+
+ + +
+
+ + + {itsMe && ( + <> + + + )} +
+
+
+ ); +} diff --git a/gui/src/components/post/Body.tsx b/gui/src/components/post/Body.tsx new file mode 100644 index 0000000..b4f1bb2 --- /dev/null +++ b/gui/src/components/post/Body.tsx @@ -0,0 +1,174 @@ +import type { + // TODO ref backend fetching!! + Reference, + Block, + Inline, + Media as MediaType, + ExternalContent, +} from "@/types/trill"; +import Icon from "@/components/Icon"; +import type { PostProps } from "./Post"; +import Media from "./Media"; +import JSONContent, { YoutubeSnippet } from "./External"; +import { useLocation } from "wouter"; +import Quote from "./Quote"; +import PostData from "./Loader"; +import Card from "./Card.tsx"; +import type { Ship } from "@/types/urbit.ts"; + +function Body(props: PostProps) { + const text = props.poast.contents.filter((c) => { + return ( + "paragraph" in c || + "blockquote" in c || + "heading" in c || + "codeblock" in c || + "list" in c + ); + }); + + const media: MediaType[] = props.poast.contents.filter( + (c): c is MediaType => "media" in c, + ); + + const refs = props.poast.contents.filter((c): c is Reference => "ref" in c); + const json = props.poast.contents.filter( + (c): c is ExternalContent => "json" in c, + ); + + return ( +
+
+ {text.map((b, i) => ( + + ))} +
+ {media.length > 0 && } + {refs.map((r, i) => ( + + ))} + +
+ ); +} +export default Body; + +function TextBlock({ block }: { block: Block }) { + const key = JSON.stringify(block); + return "paragraph" in block ? ( +
+ {block.paragraph.map((i, ind) => ( + + ))} +
+ ) : "blockquote" in block ? ( +
+ {block.blockquote.map((i, ind) => ( + + ))} +
+ ) : "heading" in block ? ( + + ) : "codeblock" in block ? ( +
+      
+        {block.codeblock.code}
+      
+    
+ ) : "list" in block ? ( + block.list.ordered ? ( +
    + {block.list.text.map((i, ind) => ( +
  1. + +
  2. + ))} +
+ ) : ( +
    + {block.list.text.map((i, ind) => ( +
  • + +
  • + ))} +
+ ) + ) : null; +} +function Inlin({ i }: { i: Inline }) { + const [_, navigate] = useLocation(); + function gotoShip(e: React.MouseEvent, ship: Ship) { + e.stopPropagation(); + navigate(`/feed/${ship}`); + } + return "text" in i ? ( + {i.text} + ) : "italic" in i ? ( + {i.italic} + ) : "bold" in i ? ( + {i.bold} + ) : "strike" in i ? ( + {i.strike} + ) : "underline" in i ? ( + {i.underline} + ) : "sup" in i ? ( + {i.sup} + ) : "sub" in i ? ( + {i.sub} + ) : "ship" in i ? ( + gotoShip(e, i.ship)} + > + {i.ship} + + ) : "codespan" in i ? ( + {i.codespan} + ) : "link" in i ? ( + + ) : "break" in i ? ( +
+ ) : null; +} + +function LinkParser({ href, show }: { href: string; show: string }) { + const YOUTUBE_REGEX_1 = /(youtube\.com\/watch\?v=)(\w+)/; + const YOUTUBE_REGEX_2 = /(youtu\.be\/)([a-zA-Z0-9-_]+)/; + const m1 = href.match(YOUTUBE_REGEX_1); + const m2 = href.match(YOUTUBE_REGEX_2); + const ytb = m1 && m1[2] ? m1[2] : m2 && m2[2] ? m2[2] : ""; + return ytb ? ( + + ) : ( + {show} + ); +} +function Heading({ string, num }: { string: string; num: number }) { + return num === 1 ? ( +

{string}

+ ) : num === 2 ? ( +

{string}

+ ) : num === 3 ? ( +

{string}

+ ) : num === 4 ? ( +

{string}

+ ) : num === 5 ? ( +
{string}
+ ) : num === 6 ? ( +
{string}
+ ) : null; +} + +function Ref({ r, nest }: { r: Reference; nest: number }) { + if (r.ref.type === "trill") { + const comp = PostData({ + host: r.ref.ship, + id: r.ref.path.slice(1), + nest: nest + 1, + className: "quote-in-post", + })(Quote); + return {comp}; + } + return <>; +} diff --git a/gui/src/components/post/Card.tsx b/gui/src/components/post/Card.tsx new file mode 100644 index 0000000..9309423 --- /dev/null +++ b/gui/src/components/post/Card.tsx @@ -0,0 +1,12 @@ +import Icon from "@/components/Icon"; +import type { IconName } from "@/components/Icon"; + +export default function ({ children, logo, cn}: { cn?: string; logo: IconName; children: any }) { + const className = "trill-post-card" + (cn ? ` ${cn}`: "") + return ( +
+ + {children} +
+ ); +} diff --git a/gui/src/components/post/External.tsx b/gui/src/components/post/External.tsx new file mode 100644 index 0000000..d52aec7 --- /dev/null +++ b/gui/src/components/post/External.tsx @@ -0,0 +1,40 @@ +import type { ExternalContent } from "@/types/trill"; +import Card from "./Card"; + +interface JSONProps { + content: ExternalContent[]; +} + +function JSONContent({ content }: JSONProps) { + return ( + <> + {content.map((c, i) => { + if (!JSON.parse(c.json.content)) return

Error

; + else + return ( +

+ External content from "{c.json.origin}", use + UFA + to display. +

+ ); + })} + + ); +} +export default JSONContent; + +export function YoutubeSnippet({ href, id }: { href: string; id: string }) { + const thumbnail = `https://i.ytimg.com/vi/${id}/hqdefault.jpg`; + // todo styiling + return ( + + + + + + ); +} diff --git a/gui/src/components/post/Footer.tsx b/gui/src/components/post/Footer.tsx new file mode 100644 index 0000000..5b79da0 --- /dev/null +++ b/gui/src/components/post/Footer.tsx @@ -0,0 +1,260 @@ +import type { PostProps } from "./Post"; +import Icon from "@/components/Icon"; +import { useState } from "react"; +import useLocalState from "@/state/state"; +import { useLocation } from "wouter"; +import { displayCount } from "@/logic/utils"; +import { TrillReactModal, stringToReact } from "./Reactions"; +import toast from "react-hot-toast"; +import NostrIcon from "./wrappers/NostrIcon"; +// TODO abstract this somehow + +function Footer({ poast, refetch }: PostProps) { + const [_showMenu, setShowMenu] = useState(false); + const [location, navigate] = useLocation(); + const [reposting, _setReposting] = useState(false); + const { api, setComposerData, setModal, addNotification } = useLocalState( + (s) => ({ + api: s.api, + setComposerData: s.setComposerData, + setModal: s.setModal, + addNotification: s.addNotification, + }), + ); + const our = api!.airlock.our!; + function doReply(e: React.MouseEvent) { + console.log("do reply"); + e.stopPropagation(); + e.preventDefault(); + setComposerData({ type: "reply", post: { trill: poast } }); + // Scroll to top where composer is located + window.scrollTo({ top: 0, behavior: "smooth" }); + // Focus will be handled by the composer component + } + function doQuote(e: React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); + setComposerData({ + type: "quote", + post: { trill: poast }, + }); + // Scroll to top where composer is located + window.scrollTo({ top: 0, behavior: "smooth" }); + } + const childrenCount = poast.children + ? poast.children.length + ? poast.children.length + : Object.keys(poast.children).length + : 0; + const myRP = poast.engagement.shared.find((r) => r.pid.ship === our); + async function cancelRP(e: React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); + const r = await api!.deletePost(our); + if (r) toast.success("Repost deleted"); + refetch(); + if (location.includes(poast.id)) navigate("/"); + } + async function sendRP(e: React.MouseEvent) { + // TODO update backend because contents are only markdown now + e.stopPropagation(); + e.preventDefault(); + // const c = [ + // { + // ref: { + // type: "trill", + // ship: poast.host, + // path: `/${poast.id}`, + // }, + // }, + // ]; + // const post: SentPoast = { + // host: our, + // author: our, + // thread: null, + // parent: null, + // contents: input, + // read: openLock, + // write: openLock, + // tags: [], // TODO + // }; + // const r = await api!.addPost(post, false); + // setReposting(true); + // if (r) { + // setReposting(false); + // toast.success("Your post was published"); + // } + } + function doReact(e: React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); + const modal = ; + setModal(modal); + } + function showReplyCount() { + if (poast.children[0]) fetchAndShow(); // Flatpoast + // else { + // const authors = Object.keys(poast.children).map( + // (i) => poast.children[i].post.author + // ); + // setEngagement({ type: "replies", ships: authors }, poast); + // } + } + async function fetchAndShow() { + // let authors = []; + // for (let i of poast.children as string[]) { + // const res = await scrypoastFull(poast.host, i); + // if (res) + // authors.push(res.post.author || "deleter"); + // } + // setEngagement({ type: "replies", ships: authors }, poast); + } + function showRepostCount() { + // const ships = poast.engagement.shared.map((entry) => entry.host); + // setEngagement({ type: "reposts", ships: ships }, poast); + } + function showQuoteCount() { + // setEngagement({ type: "quotes", quotes: poast.engagement.quoted }, poast); + } + function showReactCount() { + // setEngagement({ type: "reacts", reacts: poast.engagement.reacts }, poast); + } + + const mostCommonReact = Object.values(poast.engagement.reacts).reduce( + (acc: any, item) => { + if (!acc.counts[item]) acc.counts[item] = 0; + acc.counts[item] += 1; + if (!acc.winner || acc.counts[item] > acc.counts[acc.winner]) + acc.winner = item; + return acc; + }, + { counts: {}, winner: "" }, + ).winner; + const reactIcon = stringToReact(mostCommonReact); + + // TODO round up all helpers + + return ( +
+
+
+ + {displayCount(childrenCount)} + +
+ +
+
+
+ + {displayCount(poast.engagement.quoted.length)} + +
+ +
+
+
+ + {displayCount(poast.engagement.shared.length)} + + {reposting ? ( +

...

+ ) : myRP ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ + {displayCount(Object.keys(poast.engagement.reacts).length)} + + {reactIcon} +
+ +
+
+ ); +} +export default Footer; + +// function Menu({ +// poast, +// setShowMenu, +// refetch, +// }: { +// poast: Poast; +// setShowMenu: Function; +// refetch: Function; +// }) { +// const ref = useRef(null); +// const [location, navigate] = useLocation(); +// // TODO this is a mess and the event still propagates +// useEffect(() => { +// const checkIfClickedOutside = (e: any) => { +// e.stopPropagation(); +// if (ref && ref.current && !ref.current.contains(e.target)) +// setShowMenu(false); +// }; +// document.addEventListener("mousedown", checkIfClickedOutside); +// return () => { +// document.removeEventListener("mousedown", checkIfClickedOutside); +// }; +// }, []); +// const { our, setModal, setAlert } = useLocalState(); +// const mine = our === poast.host || our === poast.author; +// async function doDelete(e: React.MouseEvent) { +// e.stopPropagation(); +// deletePost(poast.host, poast.id); +// setAlert("Post deleted"); +// setShowMenu(false); +// refetch(); +// if (location.includes(poast.id)) navigate("/"); +// } +// async function copyLink(e: React.MouseEvent) { +// e.stopPropagation(); +// const link = trillPermalink(poast); +// await navigator.clipboard.writeText(link); +// // some alert +// setShowMenu(false); +// } +// function openStats(e: React.MouseEvent) { +// e.stopPropagation(); +// e.preventDefault(); +// const m = setModal(null)} />; +// setModal(m); +// } +// return ( +//
+// {/*

Share to Groups

*/} +//

+// See Stats +//

+//

+// Permalink +//

+// {mine && ( +//

+// Delete Post +//

+// )} +//
+// ); +// } diff --git a/gui/src/components/post/Header.tsx b/gui/src/components/post/Header.tsx new file mode 100644 index 0000000..4e72fe8 --- /dev/null +++ b/gui/src/components/post/Header.tsx @@ -0,0 +1,40 @@ +import { date_diff } from "@/logic/utils"; +import type { PostProps } from "./Post"; +import { useLocation } from "wouter"; +import useLocalState from "@/state/state"; +function Header(props: PostProps) { + const [_, navigate] = useLocation(); + const profiles = useLocalState((s) => s.profiles); + const profile = profiles.get(props.poast.author); + // console.log("profile", profile); + // console.log(props.poast.author.length, "length"); + function go(e: React.MouseEvent) { + e.stopPropagation(); + } + function openThread(e: React.MouseEvent) { + e.stopPropagation(); + const sel = window.getSelection()?.toString(); + if (!sel) navigate(`/feed/${poast.host}/${poast.id}`); + } + const { poast } = props; + const name = profile ? ( + profile.name + ) : ( +
+

{poast.author}

+
+ ); + return ( +
+
+ {name} +
+
+

+ {date_diff(poast.time, "short")} +

+
+
+ ); +} +export default Header; diff --git a/gui/src/components/post/Loader.tsx b/gui/src/components/post/Loader.tsx new file mode 100644 index 0000000..e45e01a --- /dev/null +++ b/gui/src/components/post/Loader.tsx @@ -0,0 +1,148 @@ +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import spinner from "@/assets/triangles.svg"; +import { useEffect, useRef, useState } from "react"; +import useLocalState from "@/state/state"; +import type { FullNode, PostID } from "@/types/trill"; +import type { Ship } from "@/types/urbit"; +import type { AsyncRes } from "@/types/ui"; +import { toFlat } from "@/logic/trill/helpers"; + +type Props = { + host: Ship; + id: PostID; + nest?: number; // nested quotes + rter?: Ship; + rtat?: number; + rtid?: PostID; + className?: string; +}; +function PostData(props: Props) { + const { api } = useLocalState((s) => ({ + api: s.api, + })); + + const { host, id, nest } = props; + + const [enest, setEnest] = useState(nest || 0); + useEffect(() => { + if (nest) setEnest(nest); + }, [nest]); + + return function (Component: React.ElementType) { + // const [showNested, setShowNested] = useState(nest <= 3); + const handleShowNested = (e: React.MouseEvent) => { + e.stopPropagation(); + setEnest(enest! - 3); + }; + const [dead, setDead] = useState(false); + const [denied, setDenied] = useState(false); + const { isLoading, isError, data, refetch } = useQuery({ + queryKey: ["trill-thread", host, id], + queryFn: fetchNode, + }); + const queryClient = useQueryClient(); + const dataRef = useRef(data); + useEffect(() => { + dataRef.current = data; + }, [data]); + + async function fetchNode(): AsyncRes { + let error = ""; + const res = await api!.scryThread(host, id); + console.log("scry res", res); + if ("error" in res) error = res.error; + if ("ok" in res) return res; + else { + const res2 = await api!.peekThread(host, id); + return res2; + } + } + async function peekTheNode() { + // let timer; + // peekNode({ ship: host, id }); + // timer = setTimeout(() => { + // const gotPost = dataRef.current && "fpost" in dataRef.current; + // setDead(!gotPost); + // // clearTimeout(timer); + // }, 10_000); + } + + // useEffect(() => { + // const path = `${host}/${id}`; + // if (path in peekedPosts) { + // queryClient.setQueryData(["trill-thread", host, id], { + // fpost: peekedPosts[path], + // }); + // } else if (path in deniedPosts) { + // setDenied(true); + // } + // }, [peekedPosts]); + // useEffect(() => { + // const path = `${host}/${id}`; + // if (path in deniedPosts) setDenied(true); + // }, [deniedPosts]); + + // useEffect(() => { + // const l = lastThread; + // if (l && l.thread == id) { + // queryClient.setQueryData(["trill-thread", host, id], { fpost: l }); + // } + // }, [lastThread]); + function retryPeek(e: React.MouseEvent) { + // e.stopPropagation(); + // setDead(false); + // peekTheNode(); + } + if (enest > 3) + return ( +
+
+ +
+
+ ); + else + return data ? ( + dead ? ( +
+
+

{host} did not respond

+ +
+
+ ) : denied ? ( +
+

+ {host} denied you access to this post +

+
+ ) : "error" in data ? ( +
+

Post not found

+

{data.error}

+
+ ) : ( + + ) + ) : // no data + isLoading || isError ? ( +
+ +
+ ) : ( +
+

...

+
+ ); + }; +} +export default PostData; diff --git a/gui/src/components/post/Media.tsx b/gui/src/components/post/Media.tsx new file mode 100644 index 0000000..04ea156 --- /dev/null +++ b/gui/src/components/post/Media.tsx @@ -0,0 +1,35 @@ +import type { Media } from "@/types/trill"; +interface Props { + media: Media[]; +} +function M({ media }: Props) { + return ( +
+ {media.map((m, i) => { + return "video" in m.media ? ( +
+ ); +} +export default M; + +function Images({ urls }: { urls: string[] }) { + return ( + <> + {urls.map((u, i) => ( + + ))} + + ); +} diff --git a/gui/src/components/post/Post.tsx b/gui/src/components/post/Post.tsx new file mode 100644 index 0000000..2965040 --- /dev/null +++ b/gui/src/components/post/Post.tsx @@ -0,0 +1,85 @@ +import type { PostID, Poast, Reference } from "@/types/trill"; + +import Header from "./Header"; +import Body from "./Body"; +import Footer from "./Footer"; +import { useLocation } from "wouter"; +import useLocalState from "@/state/state"; +import RP from "./RP"; +import ShipModal from "../modals/ShipModal"; +import type { Ship } from "@/types/urbit"; +import Sigil from "../Sigil"; +import type { UserProfile } from "@/types/nostrill"; + +export interface PostProps { + poast: Poast; + fake?: boolean; + rter?: Ship; + rtat?: number; + rtid?: PostID; + nest?: number; + refetch?: Function; + profile?: UserProfile; +} +function Post(props: PostProps) { + console.log("post", props); + const { poast } = props; + if (!poast || poast.contents === null) { + return null; + } + const isRP = + poast.contents.length === 1 && + "ref" in poast.contents[0] && + poast.contents[0].ref.type === "trill"; + if (isRP) { + const ref = (poast.contents[0] as Reference).ref; + return ( + + ); + } else return ; +} +export default Post; + +function TrillPost(props: PostProps) { + const { poast, profile, fake } = props; + const setModal = useLocalState((s) => s.setModal); + const [_, navigate] = useLocation(); + function openThread(_e: React.MouseEvent) { + const sel = window.getSelection()?.toString(); + if (!sel) navigate(`/feed/${poast.host}/${poast.id}`); + } + + function openModal(e: React.MouseEvent) { + e.stopPropagation(); + setModal(); + } + const avatar = profile ? ( +
+ +
+ ) : ( +
+ +
+ ); + return ( +
+
{avatar}
+
+
+ + {!fake &&
} +
+
+ ); +} diff --git a/gui/src/components/post/PostWrapper.tsx b/gui/src/components/post/PostWrapper.tsx new file mode 100644 index 0000000..c4e754f --- /dev/null +++ b/gui/src/components/post/PostWrapper.tsx @@ -0,0 +1,14 @@ +import useLocalState from "@/state/state"; +import type { NostrPost, PostWrapper } from "@/types/nostrill"; + +export default Post; +function Post(pw: PostWrapper) { + if ("nostr" in pw) return ; + else return ; +} + +function NostrPost({ post, event, relay }: NostrPost) { + const { profiles } = useLocalState(); + const profile = profiles.get(event.pubkey); + return <>; +} diff --git a/gui/src/components/post/Quote.tsx b/gui/src/components/post/Quote.tsx new file mode 100644 index 0000000..28149f0 --- /dev/null +++ b/gui/src/components/post/Quote.tsx @@ -0,0 +1,64 @@ +import type { FullNode, Poast } from "@/types/trill"; +import { date_diff } from "@/logic/utils"; +import { useLocation } from "wouter"; +import Body from "./Body"; +import Sigil from "../Sigil"; + +// function Quote({ +// data, +// refetch, +// nest, +// }: { +// data: FullNode; +// refetch?: Function; +// nest: number; +// }) { +// const [_, navigate] = useLocation(); +// function gotoQuote(e: React.MouseEvent) { +// e.stopPropagation(); +// navigate(`/feed/${data.host}/${data.id}`); +// } +// return ( +//
+//
+// ( +//
+// +// {data.author} +//
+// ){date_diff(data.time, "short")} +//
+// +//
+// ); +// } +function Quote({ + data, + refetch, + nest, +}: { + data: Poast; + refetch?: Function; + nest: number; +}) { + const [_, navigate] = useLocation(); + function gotoQuote(e: React.MouseEvent) { + e.stopPropagation(); + navigate(`/feed/${data.host}/${data.id}`); + } + return ( +
+
+ ( +
+ + {data.author} +
+ ){date_diff(data.time, "short")} +
+ +
+ ); +} + +export default Quote; diff --git a/gui/src/components/post/RP.tsx b/gui/src/components/post/RP.tsx new file mode 100644 index 0000000..27fa02d --- /dev/null +++ b/gui/src/components/post/RP.tsx @@ -0,0 +1,47 @@ +import Post from "./Post"; +import type { Ship } from "@/types/urbit"; +import type { Poast, FullNode, ID } from "@/types/trill"; +import PostData from "./Loader"; +export default function (props: { + host: string; + id: string; + rter: Ship; + rtat: number; + rtid: ID; + refetch?: Function; +}) { + return PostData(props)(RP); +} + +function RP({ + data, + refetch, + rter, + rtat, + rtid, +}: { + data: FullNode; + refetch: Function; + rter: Ship; + rtat: number; + rtid: ID; +}) { + return ( + + ); +} + +export function toFlat(n: FullNode): Poast { + return { + ...n, + children: !n.children + ? [] + : Object.keys(n.children).map((c) => n.children[c].id), + }; +} diff --git a/gui/src/components/post/Reactions.tsx b/gui/src/components/post/Reactions.tsx new file mode 100644 index 0000000..ae75d8c --- /dev/null +++ b/gui/src/components/post/Reactions.tsx @@ -0,0 +1,134 @@ +import type { Poast } from "@/types/trill"; +import yeschad from "@/assets/reacts/yeschad.png"; +import cringe from "@/assets/reacts/cringe.png"; +import cry from "@/assets/reacts/cry.png"; +import doom from "@/assets/reacts/doom.png"; +import galaxy from "@/assets/reacts/galaxy.png"; +import gigachad from "@/assets/reacts/gigachad.png"; +import pepechin from "@/assets/reacts/pepechin.png"; +import pepeeyes from "@/assets/reacts/pepeeyes.png"; +import pepegmi from "@/assets/reacts/pepegmi.png"; +import pepesad from "@/assets/reacts/pepesad.png"; +import pink from "@/assets/reacts/pink.png"; +import soy from "@/assets/reacts/soy.png"; +import chad from "@/assets/reacts/chad.png"; +import pika from "@/assets/reacts/pika.png"; +import facepalm from "@/assets/reacts/facepalm.png"; +import Icon from "@/components/Icon"; +import emojis from "@/logic/emojis.json"; +import Modal from "../modals/Modal"; +import useLocalState from "@/state/state"; + +export function ReactModal({ send }: { send: (s: string) => Promise }) { + const { setModal } = useLocalState((s) => ({ setModal: s.setModal })); + async function sendReact(e: React.MouseEvent, s: string) { + e.stopPropagation(); + const res = await send(s); + if (res) setModal(null); + } + // todo one more meme + return ( + +
+ sendReact(e, "❤️")}>️️❤️ + sendReact(e, "🤔")}>🤔 + sendReact(e, "😅")}>😅 + sendReact(e, "🤬")}>🤬 + sendReact(e, "😂")}>😂️ + sendReact(e, "🫡")}>🫡️ + sendReact(e, "🤢")}>🤢 + sendReact(e, "😭")}>😭 + sendReact(e, "😱")}>😱 + sendReact(e, "facepalm")} + src={facepalm} + alt="" + /> + sendReact(e, "👍")}>👍️ + sendReact(e, "👎")}>👎️ + sendReact(e, "☝")}>☝️ + sendReact(e, "🤝")}>🤝️ + sendReact(e, "🙏")}>🙏 + sendReact(e, "🤡")}>🤡 + sendReact(e, "👀")}>👀 + sendReact(e, "🎤")}>🎤 + sendReact(e, "💯")}>💯 + sendReact(e, "🔥")}>🔥 + sendReact(e, "yeschad")} src={yeschad} alt="" /> + sendReact(e, "gigachad")} + src={gigachad} + alt="" + /> + sendReact(e, "pika")} src={pika} alt="" /> + sendReact(e, "cringe")} src={cringe} alt="" /> + sendReact(e, "pepegmi")} src={pepegmi} alt="" /> + sendReact(e, "pepesad")} src={pepesad} alt="" /> + sendReact(e, "galaxy")} src={galaxy} alt="" /> + sendReact(e, "pink")} src={pink} alt="" /> + sendReact(e, "soy")} src={soy} alt="" /> + sendReact(e, "cry")} src={cry} alt="" /> + sendReact(e, "doom")} src={doom} alt="" /> +
+
+ ); +} + +export function stringToReact(s: string) { + const em = (emojis as Record)[s.replace(/\:/g, "")]; + if (s === "yeschad") + return ; + if (s === "facepalm") + return ; + if (s === "yes.jpg") + return ; + if (s === "gigachad") + return ; + if (s === "pepechin") + return ; + if (s === "pepeeyes") + return ; + if (s === "pepegmi") + return ; + if (s === "pepesad") + return ; + if (s === "") + return ; + if (s === "cringe") return ; + if (s === "cry") return ; + if (s === "crywojak") return ; + if (s === "doom") return ; + if (s === "galaxy") return ; + if (s === "pink") return ; + if (s === "pinkwojak") return ; + if (s === "soy") return ; + if (s === "chad") return ; + if (s === "pika") return ; + if (em) return {em}; + else if (s.length > 2) return ; + else return {s}; +} + +export function TrillReactModal({ poast }: { poast: Poast }) { + const { api, addNotification } = useLocalState((s) => ({ + api: s.api, + addNotification: s.addNotification, + })); + const our = api!.airlock.our!; + + async function sendReact(s: string) { + const result = await api!.addReact(poast.host, poast.id, s); + // Only add notification if reacting to someone else's post + if (result && poast.author !== our) { + addNotification({ + type: "react", + from: our, + message: `You reacted to ${poast.author}'s post`, + reaction: s, + postId: poast.id, + }); + } + return result; + } + return ; +} diff --git a/gui/src/components/post/StatsModal.tsx b/gui/src/components/post/StatsModal.tsx new file mode 100644 index 0000000..4720b2a --- /dev/null +++ b/gui/src/components/post/StatsModal.tsx @@ -0,0 +1,106 @@ +import type { Poast } from "@/types/trill"; +import Modal from "../modals/Modal"; +import { useState } from "react"; +import Post from "./Post"; +import RP from "./RP"; +import Avatar from "../Avatar"; +import { stringToReact } from "./Reactions"; + +function StatsModal({ poast, close }: { close: any; poast: Poast }) { + const [tab, setTab] = useState("replies"); + const replies = poast.children || []; + const quotes = poast.engagement.quoted; + const reposts = poast.engagement.shared; + const reacts = poast.engagement.reacts; + function set(e: React.MouseEvent, s: string) { + e.stopPropagation(); + setTab(s); + } + // TODO revise the global thingy here + return ( + +
+ {}} /> +
+
set(e, "replies")} + > +

Replies

+
+
set(e, "quotes")} + > +

Quotes

+
+
set(e, "reposts")} + > +

Reposts

+
+
set(e, "reacts")} + > +

Reacts

+
+
+
+ {tab === "replies" ? ( +
+ {replies.map((p) => ( +
+ +
+ ))} +
+ ) : tab === "quotes" ? ( +
+ {quotes.map((p) => ( +
+ +
+ ))} +
+ ) : tab === "reposts" ? ( +
+ {reposts.map((p) => ( +
+ +
+ ))} +
+ ) : tab === "reacts" ? ( +
+ {Object.keys(reacts).map((p) => ( +
+ + {stringToReact(reacts[p])} +
+ ))} +
+ ) : null} +
+
+
+ ); +} +export default StatsModal; diff --git a/gui/src/components/post/wrappers/Nostr.tsx b/gui/src/components/post/wrappers/Nostr.tsx new file mode 100644 index 0000000..2782fb8 --- /dev/null +++ b/gui/src/components/post/wrappers/Nostr.tsx @@ -0,0 +1,15 @@ +import type { NostrMetadata, NostrPost } from "@/types/nostrill"; +import Post from "../Post"; +import useLocalState from "@/state/state"; + +export default NostrPost; +function NostrPost({ data }: { data: NostrPost }) { + const { profiles } = useLocalState((s) => ({ profiles: s.profiles })); + const profile = profiles.get(data.event.pubkey); + + return ; +} + +export function NostrSnippet({ eventId, pubkey, relay }: NostrMetadata) { + return
wtf
; +} diff --git a/gui/src/components/post/wrappers/NostrIcon.tsx b/gui/src/components/post/wrappers/NostrIcon.tsx new file mode 100644 index 0000000..30fbfe9 --- /dev/null +++ b/gui/src/components/post/wrappers/NostrIcon.tsx @@ -0,0 +1,25 @@ +import Icon from "@/components/Icon"; +import useLocalState from "@/state/state"; +import toast from "react-hot-toast"; +import type { Poast } from "@/types/trill"; +export default function ({ poast }: { poast: Poast }) { + const { relays, api } = useLocalState((s) => ({ + relays: s.relays, + api: s.api, + })); + + async function sendToRelay(e: React.MouseEvent) { + e.stopPropagation(); + // + const urls = Object.keys(relays); + await api!.relayPost(poast.host, poast.id, urls); + toast.success("Post relayed"); + } + // TODO round up all helpers + + return ( +
+ +
+ ); +} diff --git a/gui/src/components/profile/Editor.tsx b/gui/src/components/profile/Editor.tsx new file mode 100644 index 0000000..2e4aebc --- /dev/null +++ b/gui/src/components/profile/Editor.tsx @@ -0,0 +1,262 @@ +import { useState } from "react"; +import type { UserProfile, UserType } from "@/types/nostrill"; +import useLocalState from "@/state/state"; +import Icon from "@/components/Icon"; +import toast from "react-hot-toast"; +import Avatar from "../Avatar"; + +interface ProfileEditorProps { + user: UserType; + userString: string; + profile: UserProfile | undefined; + onSave?: () => void; +} + +const ProfileEditor: React.FC = ({ + user, + profile, + userString, + onSave, +}) => { + const { api, profiles } = useLocalState((s) => ({ + api: s.api, + pubkey: s.pubkey, + profiles: s.profiles, + })); + + // Initialize state with existing profile or defaults + const [name, setName] = useState(profile?.name || userString); + const [picture, setPicture] = useState(profile?.picture || ""); + const [about, setAbout] = useState(profile?.about || ""); + const [customFields, setCustomFields] = useState< + Array<{ key: string; value: string }> + >( + Object.entries(profile?.other || {}).map(([key, value]) => ({ + key, + value, + })), + ); + const [isEditing, setIsEditing] = useState(false); + const [isSaving, setIsSaving] = useState(false); + + const handleAddCustomField = () => { + setCustomFields([...customFields, { key: "", value: "" }]); + }; + + const handleUpdateCustomField = ( + index: number, + field: "key" | "value", + newValue: string, + ) => { + const updated = [...customFields]; + updated[index][field] = newValue; + setCustomFields(updated); + }; + + const handleRemoveCustomField = (index: number) => { + setCustomFields(customFields.filter((_, i) => i !== index)); + }; + + const handleSave = async () => { + setIsSaving(true); + try { + // Convert custom fields array to object + const other: Record = {}; + customFields.forEach(({ key, value }) => { + if (key.trim()) { + other[key.trim()] = value; + } + }); + + const nprofile: UserProfile = { + name, + picture, + about, + other, + }; + + // Call API to save profile + if (api && typeof api.createProfile === "function") { + await api.createProfile(nprofile); + } else { + throw new Error("Profile update API not available"); + } + + toast.success("Profile updated successfully"); + setIsEditing(false); + onSave?.(); + } catch (error) { + toast.error("Failed to update profile"); + console.error("Failed to save profile:", error); + } finally { + setIsSaving(false); + } + }; + + const handleCancel = () => { + // Reset to original values + const profile = profiles.get(userString); + if (profile) { + setName(profile.name || userString); + setPicture(profile.picture || ""); + setAbout(profile.about || ""); + setCustomFields( + Object.entries(profile.other || {}).map(([key, value]) => ({ + key, + value, + })), + ); + } + setIsEditing(false); + }; + console.log({ profile }); + console.log({ name, picture, customFields }); + + return ( +
+
+

Edit Profile

+ {!isEditing && ( + + )} +
+ + {isEditing ? ( +
+
+ + setName(e.target.value)} + placeholder="Your display name" + /> +
+ +
+ + setPicture(e.target.value)} + placeholder="https://example.com/avatar.jpg" + /> +
+ {picture ? ( + + ) : ( + + )} +
+
+ +
+ +