From 85108dca7cc7554701d3dfc735dcd9313d35d2a5 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Wed, 31 Jan 2024 14:01:32 -0500 Subject: [PATCH 001/182] Activity agent skeleton --- desk/app/activity.hoon | 57 ++++++++++++++++++++++++++++++++++++ desk/sur/activity.hoon | 65 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 desk/app/activity.hoon create mode 100644 desk/sur/activity.hoon diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon new file mode 100644 index 0000000000..cbf347454c --- /dev/null +++ b/desk/app/activity.hoon @@ -0,0 +1,57 @@ +:: +/- *activity +/+ default-agent, verb, dbug +:: +|% ++$ card card:agent:gall +:: ++$ versioned-state + $% state-0 + == +:: ++$ state-0 + [%0 =stream =indices] +:: +=| state-0 +=* state - +:: +%- agent:dbug +%+ verb | +^- agent:gall +:: +=< + |_ =bowl:gall + +* this . + def ~(. (default-agent this %|) bowl) + :: + ++ on-init `this + :: + ++ on-save !>(state) + :: + ++ on-load + |= =vase + ^- (quip card _this) + =/ old !<(versioned-state old-state) + `this(state old) + :: + ++ on-poke + |= [=mark =vase] + ^- (quip card _this) + `this + :: + ++ on-arvo + |= [=wire =sign-arvo] + ^- (quip card _this) + `this + :: + ++ on-fail on-fail:def + :: + ++ on-leave + |= =path + `this + :: + ++ on-peek + |= =path + ^- (unit (unit cage)) + ~ + -- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon new file mode 100644 index 0000000000..86ff136826 --- /dev/null +++ b/desk/sur/activity.hoon @@ -0,0 +1,65 @@ +|% ++$ state + $: stream=(mop time event) + indices=(map concern stream) + == ++$ concern + $% [%group group-concern] + [%channel channel-concern] + [%dm dm-concern] + [%post post-concern] + [%writ writ-concern] + == ++$ group-concern group=flag ++$ channel-concern [channel=nest group=flag] ++$ dm-concern =whom ++$ post-concern [=post-key channel=nest group=flag] ++$ writ-concern [=writ-key =whom] +:: $event: an instance of activity +:: +:: $flavor: what activity generated the event and where did it happen +:: $content: text of the message that generated the event or a description +:: $level: what "level" of importance this is deemed by originator +:: $read: has this event been seen or interacted with +:: ++$ event [=flavor =level =content read=?] ++$ level ?(%notify %default %trivial) ++$ content + $@ @t + $% [%ship p=ship] + [%emph p=cord] + == ++$ flavor + :: specific occasions TBD + $% [%group group-concern occasion=?(%join %kick)] + [%channel channel-concern occasion=?(%message %mention %reply %notice)] + [%dm dm-concern occasion=?(%message %mention %reply %notice)] + [%post post-concern occasion=?(%message %mention %reply %notice)] + [%writ writ-concern occasion=?(%message %mention %reply %notice)] + == +:: +:: this is similar to the way unreads are handled for DMs. in the case of +:: chats, recency will just happen to be the same as the key time. +:: +++ unreads + =< unreads + |% + +$ unreads + (map whom unread) + +$ unread + $: count=@ud + unread=(unit [message-key count=@ud]) + threads=(map message-key [message-key count=@ud]) + == + +$ update + (pair whom unread) + -- ++$ message-key + $% [%time =id =time] + [%recency =id =time recency=time] + == ++$ whom + $% [%ship p=ship] + [%club p=id:club] + == +-- From 76407018d099437f8ef33531c5101a6f881dbd72 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 5 Feb 2024 15:50:22 -0600 Subject: [PATCH 002/182] subscriber: initial version compiles --- desk/app/groups.hoon | 82 +++++++++++++++++++++++++++------------- desk/lib/subscriber.hoon | 30 +++++++++++++++ 2 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 desk/lib/subscriber.hoon diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index bf87f3a21f..167eea8933 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -2,7 +2,7 @@ /- meta /- e=epic /+ default-agent, verb, dbug -/+ v=volume +/+ v=volume, s=subscriber /+ of /+ epos-lib=saga :: performance, keep warm @@ -14,18 +14,19 @@ +$ card card:agent:gall ++ import-epoch ~2022.10.11 +$ current-state - $: %2 + $: %3 groups=net-groups:g :: $= volume $: base=level:v area=(map flag:g level:v) :: override per group - chan=(map nest:g level:v) :: override per channel + chan=(map nest:g level:v) :: override per channel == :: xeno=gangs:g :: graph -> agent shoal=(map flag:g dude:gall) + =^subs:s == :: -- @@ -288,12 +289,13 @@ ?- -.old %0 $(old (state-0-to-1 old)) %1 $(old (state-1-to-2 old)) - :: - %2 + %2 $(old (state-2-to-3 old)) + :: + %3 =. state old =. cor restore-missing-subs =. cor (emit %pass /groups/role %agent [our.bowl dap.bowl] %poke noun+!>(%verify-cabals)) - =. cor watch-contact + =. cor (watch-contact |) ?: =(okay:g cool) cor =. cor (emil (drop load:epos)) =/ groups ~(tap in ~(key by groups)) @@ -304,7 +306,7 @@ go-abet:go-upgrade:(go-abed:group-core i.groups) $(groups t.groups) == - +$ versioned-state $%(current-state state-1 state-0) + +$ versioned-state $%(current-state state-2 state-1 state-0) +$ state-0 $: %0 groups=net-groups:zero @@ -327,6 +329,20 @@ shoal=(map flag:zero dude:gall) == :: + +$ state-2 + $: %2 + groups=net-groups:g + :: + $= volume + $: base=level:v + area=(map flag:g level:v) :: override per group + chan=(map nest:g level:v) :: override per channel + == + :: + xeno=gangs:g + :: graph -> agent + shoal=(map flag:g dude:gall) + == ++ state-0-to-1 |= state-0 ^- state-1 @@ -334,9 +350,14 @@ :: ++ state-1-to-2 |= state-1 - ^- current-state + ^- state-2 [%2 (groups-1-to-2 groups) volume xeno shoal] :: + ++ state-2-to-3 + |= state-2 + ^- current-state + [%3 groups volume xeno shoal ~] + :: ++ groups-1-to-2 |= groups=net-groups:zero ^- net-groups:g @@ -588,6 +609,14 @@ ^+ cor !! :: +++ subscribe + |= [=wire =dock =path] + |= delay=? + =^ card=(unit card) subs + (~(subscribe s [subs bowl]) wire dock path delay) + ?~ card cor + (emit u.card) +:: ++ cast |= [grp=flag:g gra=flag:g] ^+ cor @@ -632,13 +661,13 @@ == :: ++ watch-contact - (emit %pass /contact %agent [our.bowl %contacts] %watch /contact) + (subscribe /contact [our.bowl %contacts] /contact) :: ++ take-contact |= =sign:agent:gall ?+ -.sign cor %kick - watch-contact + (watch-contact &) :: %watch-ack cor @@ -653,20 +682,20 @@ == :: ++ watch-epic - |= her=ship + |= [her=ship delay=?] ^+ cor =/ =wire /epic =/ =dock [her dap.bowl] ?: (~(has by wex.bowl) [wire dock]) cor - (emit %pass wire %agent [her dap.bowl] %watch /epic) + ((subscribe wire dock wire) delay) :: ++ take-epic |= =sign:agent:gall ^+ cor ?+ -.sign cor %kick - (watch-epic src.bowl) + (watch-epic src.bowl &) :: %fact ?. =(%epic p.cage.sign) @@ -928,18 +957,16 @@ ^+ go-core ?: |(go-has-sub =(our.bowl p.flag)) go-core - (go-sub init) + (go-sub init |) :: ++ go-sub - |= init=_| + |= [init=_| delay=?] ^+ go-core =/ =time ?.(?=(%sub -.net) *time p.net) =/ base=wire (snoc go-area %updates) =/ =path (snoc base ?:(init %init (scot %da time))) - =/ =card - [%pass base %agent [p.flag dap.bowl] %watch path] - =. cor (emit card) + =. cor ((subscribe base [p.flag dap.bowl] path) delay) go-core :: ++ go-watch @@ -1082,11 +1109,11 @@ ++ go-take-update |= =sign:agent:gall ^+ go-core - ?+ -.sign (go-sub |) + ?+ -.sign (go-sub | &) %kick ?> ?=(%sub -.net) ?. ?=(%chi -.saga.net) go-core - (go-sub !load.net) + (go-sub !load.net &) :: %watch-ack =? cor (~(has by xeno) flag) @@ -1119,7 +1146,7 @@ go-core ~& "took lev epic: {}" =. saga.net lev/~ - =. cor (watch-epic p.flag) + =. cor (watch-epic p.flag |) go-core :: ++ go-make-chi @@ -1791,8 +1818,9 @@ =/ =action:g [flag now.bowl %cordon %shut %del-ships %ask ships] (poke-host /rescind act:mar:g !>(action)) ++ get-preview - =/ =task:agent:gall [%watch /groups/(scot %p p.flag)/[q.flag]/preview] - (pass-host /preview task) + %^ subscribe (welp ga-area /preview) + [p.flag dap.bowl] + /groups/(scot %p p.flag)/[q.flag]/preview -- ++ ga-start-join ^+ ga-core @@ -1816,7 +1844,7 @@ ++ ga-watch |= =(pole knot) ^+ ga-core - =. cor (emit get-preview:ga-pass) + =. cor (get-preview:ga-pass |) ga-core :: ++ ga-give-update @@ -1869,7 +1897,7 @@ %kick ?. (~(has by xeno) flag) ga-core ?^ pev.gang ga-core - ga-core(cor (emit get-preview:ga-pass)) + ga-core(cor (get-preview:ga-pass &)) == :: [%join %add ~] @@ -1885,7 +1913,7 @@ =. groups (~(put by groups) flag net group) :: =. cor - go-abet:(go-sub:(go-abed:group-core flag) &) + go-abet:(go-sub:(go-abed:group-core flag) & |) ga-core [%knock ~] ?> ?=(%poke-ack -.sign) @@ -1920,7 +1948,7 @@ ++ ga-invite |= =invite:g =. vit.gang `invite - =. cor (emit get-preview:ga-pass) + =. cor (get-preview:ga-pass |) =. cor ga-give-update ga-core :: diff --git a/desk/lib/subscriber.hoon b/desk/lib/subscriber.hoon new file mode 100644 index 0000000000..458160a323 --- /dev/null +++ b/desk/lib/subscriber.hoon @@ -0,0 +1,30 @@ +=< subscriber +|% ++$ sub + $: =dock + =path + == +:: ++$ subs (map wire sub) +:: +++ subscriber + |_ [=subs bowl:gall] + ++ interval ~s30 + ++ handle-wakeup + |= =wire + ^- [(unit card:agent:gall) _subs] + ?> ?=([%~.~ %retry *] wire) + =/ sub (~(get by subs) t.t.wire) + ?~ sub [~ subs] + :- `[%pass t.wire %agent dock.u.sub %watch path.u.sub] + (~(del by subs) t.t.wire) + ++ subscribe + |= [=wire =dock =path delay=?] + ^- [(unit card:agent:gall) _subs] + ?: (~(has by subs) wire) + ((slog 'Duplicate subscription' >[wire dock]< ~) [~ subs]) + ?. delay [`[%pass wire %agent dock %watch path] subs] + :_ (~(put by subs) wire [dock path]) + `[%pass (weld /~/retry wire) %arvo %b %wait (add now interval)] + -- +-- \ No newline at end of file From d5f258b23e77469d73e1131827248c672716b662 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 6 Feb 2024 17:52:15 -0600 Subject: [PATCH 003/182] subscriber: adding testing hooks --- desk/app/groups.hoon | 35 +++++++++++++++++++++++++++++++++-- desk/lib/subscriber.hoon | 19 ++++++++++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 167eea8933..b2c7396433 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -102,6 +102,8 @@ ?+ q.vase !! %reset-all-perms reset-all-perms %verify-cabals verify-cabals + %test-loop test-loop + %cancel-loop cancel-loop == :: %reset-group-perms @@ -279,6 +281,25 @@ =/ cmd=c-channels:d [%channel nest %del-writers diff] =/ cage [%channel-command !>(cmd)] cr(cards [[%pass /groups/role %agent [p.q.nest %channels-server] %poke cage] cards.cr]) +++ test-loop + =/ =wire /test-loop + =/ =dock [our.bowl dap.bowl] + (emit [%pass wire %agent dock %watch wire]) +++ take-loop + |= =sign:agent:gall + ^+ cor + ?+ -.sign cor + %kick + ((subscribe /test-loop [our.bowl dap.bowl] /test-loop) &) + :: + %watch-ack + cor + == +++ cancel-loop + =^ card=(unit card) subs + (~(cancel s [subs bowl]) /test-loop) + ?~ card cor + (emit u.card) :: :: +load: load next state ++ load @@ -411,6 +432,7 @@ [%groups ~] cor [%groups %ui ~] cor [%gangs %updates ~] cor + [%test-loop ~] (give %kick ~[/test-loop] ~) :: [%epic ~] (give %fact ~ epic+!>(okay:g)) :: @@ -566,6 +588,7 @@ [%epic ~] (take-epic sign) [%helm *] cor [%groups %role ~] cor + [%test-loop ~] (take-loop sign) [?(%hark %groups %chat %heap %diary) ~] cor [%cast ship=@ name=@ ~] (take-cast [(slav %p ship.pole) name.pole] sign) :: @@ -605,9 +628,17 @@ == :: ++ arvo - |= [=wire sign=sign-arvo] + |= [=(pole knot) sign=sign-arvo] ^+ cor - !! + ?+ pole ~|(bad-arvo-take/pole !!) + [%~.~ %cancel-retry rest=*] cor + :: + [%~.~ %retry rest=*] + =^ card=(unit card) subs + (~(handle-wakeup s [subs bowl]) pole) + ?~ card cor + (emit u.card) + == :: ++ subscribe |= [=wire =dock =path] diff --git a/desk/lib/subscriber.hoon b/desk/lib/subscriber.hoon index 458160a323..19c06a1f4e 100644 --- a/desk/lib/subscriber.hoon +++ b/desk/lib/subscriber.hoon @@ -3,6 +3,7 @@ +$ sub $: =dock =path + fires-at=@da == :: +$ subs (map wire sub) @@ -14,9 +15,10 @@ |= =wire ^- [(unit card:agent:gall) _subs] ?> ?=([%~.~ %retry *] wire) + :: ~& ['waking up' wire] =/ sub (~(get by subs) t.t.wire) ?~ sub [~ subs] - :- `[%pass t.wire %agent dock.u.sub %watch path.u.sub] + :- `[%pass t.t.wire %agent dock.u.sub %watch path.u.sub] (~(del by subs) t.t.wire) ++ subscribe |= [=wire =dock =path delay=?] @@ -24,7 +26,18 @@ ?: (~(has by subs) wire) ((slog 'Duplicate subscription' >[wire dock]< ~) [~ subs]) ?. delay [`[%pass wire %agent dock %watch path] subs] - :_ (~(put by subs) wire [dock path]) - `[%pass (weld /~/retry wire) %arvo %b %wait (add now interval)] + :: ~& ['subscribing with delay' wire] + =/ fires-at (add now interval) + :_ (~(put by subs) wire [dock path fires-at]) + `[%pass (weld /~/retry wire) %arvo %b %wait fires-at] + ++ cancel + |= =wire + ^- [(unit card:agent:gall) _subs] + =/ sub (~(get by subs) wire) + ?~ sub + ((slog 'No such subscription' >[wire]< ~) [~ subs]) + :: ~& ['cancelling' wire] + :_ (~(del by subs) wire) + `[%pass (weld /~/cancel-retry wire) %arvo %b %rest fires-at.u.sub] -- -- \ No newline at end of file From 867fffd88ebf0f58980968786a862e619fa34e08 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 7 Feb 2024 18:58:48 -0600 Subject: [PATCH 004/182] subscriber: adding tests to verify --- desk/app/groups.hoon | 23 ----------------------- desk/tests/app/groups.hoon | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 desk/tests/app/groups.hoon diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index b2c7396433..5248dccae3 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -102,8 +102,6 @@ ?+ q.vase !! %reset-all-perms reset-all-perms %verify-cabals verify-cabals - %test-loop test-loop - %cancel-loop cancel-loop == :: %reset-group-perms @@ -281,25 +279,6 @@ =/ cmd=c-channels:d [%channel nest %del-writers diff] =/ cage [%channel-command !>(cmd)] cr(cards [[%pass /groups/role %agent [p.q.nest %channels-server] %poke cage] cards.cr]) -++ test-loop - =/ =wire /test-loop - =/ =dock [our.bowl dap.bowl] - (emit [%pass wire %agent dock %watch wire]) -++ take-loop - |= =sign:agent:gall - ^+ cor - ?+ -.sign cor - %kick - ((subscribe /test-loop [our.bowl dap.bowl] /test-loop) &) - :: - %watch-ack - cor - == -++ cancel-loop - =^ card=(unit card) subs - (~(cancel s [subs bowl]) /test-loop) - ?~ card cor - (emit u.card) :: :: +load: load next state ++ load @@ -432,7 +411,6 @@ [%groups ~] cor [%groups %ui ~] cor [%gangs %updates ~] cor - [%test-loop ~] (give %kick ~[/test-loop] ~) :: [%epic ~] (give %fact ~ epic+!>(okay:g)) :: @@ -588,7 +566,6 @@ [%epic ~] (take-epic sign) [%helm *] cor [%groups %role ~] cor - [%test-loop ~] (take-loop sign) [?(%hark %groups %chat %heap %diary) ~] cor [%cast ship=@ name=@ ~] (take-cast [(slav %p ship.pole) name.pole] sign) :: diff --git a/desk/tests/app/groups.hoon b/desk/tests/app/groups.hoon new file mode 100644 index 0000000000..5fbaa4fed8 --- /dev/null +++ b/desk/tests/app/groups.hoon @@ -0,0 +1,32 @@ +/- g=groups +/+ *test-agent +/= groups-agent /app/groups +|% +++ dap %groups-test +++ the-wire /groups/(scot %p ~zod)/test/updates +++ the-path (weld the-wire /init) +++ the-dock [~zod dap] +++ the-group [%test 'test' '' '' '' [%open ~ ~] ~ |] +++ retry (weld /~/retry the-wire) +++ test-subscription-loop + %- eval-mare + =/ m (mare ,~) + :: init and add group + ;< * bind:m (do-init %groups-test groups-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev))) + ;< * bind:m (do-poke %group-join !>([[~zod %test] &])) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~zod))) + ;< * bind:m (do-agent /gangs/(scot %p ~zod)/test/join/add the-dock %poke-ack ~) + ;< * bind:m (do-agent the-wire the-dock %watch-ack ~) + ;< bw=bowl bind:m get-bowl + =/ now=time now.bw + :: kick & resubscribe with delay + ;< caz=(list card) bind:m (do-agent the-wire the-dock %kick ~) + =/ next=time (add now ~s30) + ;< * bind:m + (ex-cards caz (ex-arvo retry %b %wait next) ~) + ;< * bind:m (jab-bowl |=(b=bowl b(now next))) + :: wakeup & resubscribe no delay + ;< caz=(list card) bind:m (do-arvo retry %behn %wake ~) + (ex-cards caz (ex-task the-wire the-dock %watch the-path) ~) +-- \ No newline at end of file From 98c3a2daaf0161a9779efe3f22a465c3c6a2a6a2 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Thu, 8 Feb 2024 10:28:01 -0500 Subject: [PATCH 005/182] Initial activity agent --- desk/app/activity.hoon | 113 +++++++++++++++++++++++++++++++++++------ desk/sur/activity.hoon | 45 +++++++++++----- 2 files changed, 129 insertions(+), 29 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index cbf347454c..99b151039a 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -1,5 +1,5 @@ :: -/- *activity +/- a=activity /+ default-agent, verb, dbug :: |% @@ -10,7 +10,7 @@ == :: +$ state-0 - [%0 =stream =indices] + [%0 =stream:a =indices:a =volume:a] :: =| state-0 =* state - @@ -23,35 +23,116 @@ |_ =bowl:gall +* this . def ~(. (default-agent this %|) bowl) + cor ~(. +> [bowl ~]) :: - ++ on-init `this + ++ on-init + =^ cards state + abet:init:cor + [cards this] :: ++ on-save !>(state) :: ++ on-load |= =vase ^- (quip card _this) - =/ old !<(versioned-state old-state) - `this(state old) + =^ cards state + abet:(load:cor vase) + [cards this] :: ++ on-poke |= [=mark =vase] ^- (quip card _this) - `this + =^ cards state + abet:(poke:cor mark vase) + [cards this] + :: + ++ on-watch + |= =path + ^- (qup card _this) + =^ cards state + abet:(watch:cor path) + [cards this] :: ++ on-arvo |= [=wire =sign-arvo] ^- (quip card _this) `this :: - ++ on-fail on-fail:def - :: - ++ on-leave - |= =path - `this - :: - ++ on-peek - |= =path - ^- (unit (unit cage)) - ~ + ++ on-peek peek:cor + ++ on-leave on-leave:def + ++ on-fail on-fail:def -- +|_ [=bowl:gall cards=(list card)] +++ abet [(flop cards) state] +++ cor . +++ emit |=(=card cor(cards [card cards])) +++ emil |=(caz=(list card) cor(cards (welp (flop caz) cards))) +++ give |=(=gift:agent:gall (emit %give gift)) +++ from-self =(our src):bowl +++ init + ^+ cor + =. volume + %- malt + :~ [%dm-invite %notify] + [%dm-post %notify] + [%dm-post-mention %notify] + [%kick %default] + [%join %trivial] + [%post %trivial] + [%post-mention %notify] + [%reply %notify] + [%reply-mention %notify] + [%flag %default] + == + cor +++ load + |= =vase + |^ ^+ cor + =+ !<(old=versioned-state vase) + ?> ?=(%0 -.old) + =. state old + cor +++ poke + |= [=mark =vase] + ^+ cor + ?+ mark ~|(bad-poke+mark !!) + %activity-action + =+ !<(=action:a vase) + ?- -.action + %add + :: TODO + cor + %read + :: TODO + cor + %adjust + cor + == + == +++ watch + |= =(pole knot) + ^+ cor + ?+ pole ~|(bat-watch-path+pole !!) + ~ ?>(from-self cor) + %notifications ?>(from-self cor) + %reads ?>(from-self cor) + == +++ peek + |= =(pole knot) + ^- (unit (unit cage)) + ?+ pole [~ ~] + [%x ~] + ``activity-full+!>([stream indices]) + [%x %all ~] + ``activity-stream+!>((tap:eon:a stream)) + [%x %all start=@ count=@ ~] + ``activity-stream+!>((scag count (top:emp:a stream start))) + [%u %event id=@] + ``loob+!>((has:eon:a (slav %da id.pole))) + [%x %event id=@] + ``activity-event+!>((got:eon:a (slav %da id.pole))) + [%x %unreads ~] + :: TODO get unread summary from each bucket + ``activity-unreads+!>((summarize-unreads indices)) + == +-- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 7a5335f519..7a9b90a053 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -1,24 +1,28 @@ +/+ mp=mop-extensions |% -+$ stream (mop time event) ++$ stream ((mop time event) lte) ++$ eon ((on time event) lte) +++ emp ((mp time event) lte) +$ indices (map index [=stream =reads]) +$ reads [floor=time (set event-id=time)] -+$ derived-unreads [time count=@ud threads=(list [time count=@ud])] +$ index $% [%channel channel-concern] [%dm dm-concern] == -+$ group-concern group=flag -+$ channel-concern [channel=nest group=flag] -+$ dm-concern =whom -+$ post-concern [=message-key channel=nest group=flag] -+$ reply-concern [=message-key target=message-key channel=nest group=flag] -+$ dm-post-concern [=writ-key =whom] -+$ event [=flavor =level] -+$ level ?(%notify %default %trivial) -+$ content - :: same as content of actual message - ~ +:: could do this per-channel/concern ++$ volume (map flavor level) +$ flavor + $? %dm-invite + %dm-post + %kick + %join + %post + %post-mention + %reply + %flag + == ++$ level ?(%notify %default %trivial) ++$ event $% [%dm-invite dm-concern] [%dm-post dm-post-concern =content mention=?] [%kick group-concern =ship] @@ -27,9 +31,24 @@ [%reply reply-concern =content mention=?] [%flag post-concern] == ++$ group-concern group=flag ++$ channel-concern [channel=nest group=flag] ++$ dm-concern =whom ++$ post-concern [=message-key channel=nest group=flag] ++$ reply-concern [=message-key target=message-key channel=nest group=flag] ++$ dm-post-concern [=writ-key =whom] +$ whom $% [%ship p=ship] [%club p=id:club] == +$ message-key [=id =time] ++$ content + :: same as content of actual message + ~ ++$ action + $% [%add ...] + [%read ...] + [%adjust ...] + == ++$ unread-summary [time count=@ud threads=(list [time count=@ud])] -- From cab16e395f9b8ebdf3da3ceca9419b5de964d733 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 12 Feb 2024 11:24:29 -0600 Subject: [PATCH 006/182] scroller: invert list based on reading direction Fixes LAND-1353. This changes our logic about whether or not to invert the list. Rather than relying on load direction, we make the invert/uninvert decision base on reading direction (whether the user perceives to be scrolling up or down). If scrolling up, we invert. If scrolling down, we "uninvert". This avoids jumpiness associated with scrolling "up" in an uninverted state, or "down" in an inverted state. This also moves a few variables around for easier readability, and moves a few variables into useMemo where it seems appropriate. --- ui/src/chat/ChatScroller/ChatScroller.tsx | 194 ++++++++++++++---- .../ChatScroller/ChatScrollerDebugOverlay.tsx | 6 + ui/src/chat/ChatThread/ChatThread.tsx | 1 + 3 files changed, 160 insertions(+), 41 deletions(-) diff --git a/ui/src/chat/ChatScroller/ChatScroller.tsx b/ui/src/chat/ChatScroller/ChatScroller.tsx index 8ca23f68d8..2f4a27c748 100644 --- a/ui/src/chat/ChatScroller/ChatScroller.tsx +++ b/ui/src/chat/ChatScroller/ChatScroller.tsx @@ -172,6 +172,7 @@ export interface ChatScrollerProps { isLoadingOlder: boolean; isLoadingNewer: boolean; replying?: boolean; + inThread?: boolean; /** * Element to be inserted at the top of the list scroll after we've loaded the * entire history. @@ -193,6 +194,7 @@ export default function ChatScroller({ isLoadingOlder, isLoadingNewer, replying = false, + inThread = false, topLoadEndMarker, scrollTo: rawScrollTo = undefined, scrollerRef, @@ -268,41 +270,96 @@ export default function ChatScroller({ return count - 1; }, [messageKeys, count, scrollTo]); - useObjectChangeLogging( - { - isAtTop, - isAtBottom, - hasLoadedNewest, - hasLoadedOldest, - loadDirection, - anchorIndex, - isLoadingOlder, - isLoadingNewer, - }, - logger - ); - const virtualizerRef = useRef(); + // We need to track whether we're force scrolling so that we don't attempt + // to change reading direction or load new content while we're in the + // middle of a forced scroll. + const isForceScrolling = useRef(false); /** * Set scroll position, bypassing virtualizer change logic. */ const forceScroll = useCallback((offset: number) => { + if (isForceScrolling.current) return; + isForceScrolling.current = true; const virt = virtualizerRef.current; if (!virt) return; virt.scrollOffset = offset; virt.scrollElement?.scrollTo?.({ top: offset }); + setTimeout(() => { + isForceScrolling.current = false; + }, 300); }, []); - const isEmpty = count === 0 && hasLoadedNewest && hasLoadedOldest; + const isEmpty = useMemo( + () => count === 0 && hasLoadedNewest && hasLoadedOldest, + [count, hasLoadedNewest, hasLoadedOldest] + ); const contentHeight = virtualizerRef.current?.getTotalSize() ?? 0; const scrollElementHeight = scrollElementRef.current?.clientHeight ?? 0; - const isScrollable = contentHeight > scrollElementHeight; + const isScrollable = useMemo( + () => contentHeight > scrollElementHeight, + [contentHeight, scrollElementHeight] + ); + + const { clientHeight, scrollTop, scrollHeight } = + scrollElementRef.current ?? { + clientHeight: 0, + scrollTop: 0, + scrollHeight: 0, + }; + // Prevent list from being at the end of new messages and old messages + // at the same time -- can happen if there are few messages loaded. + const atEndThreshold = Math.min( + (scrollHeight - clientHeight) / 2, + thresholds.atEndThreshold + ); + const isAtExactScrollEnd = scrollHeight - scrollTop === clientHeight; + const isAtScrollBeginning = scrollTop === 0; + const isAtScrollEnd = + scrollTop + clientHeight >= scrollHeight - atEndThreshold; + const readingDirectionRef = useRef(''); + + // Determine whether the list should be inverted based on reading direction + // and whether the content is scrollable or if we're scrolling to a specific + // message. + // If the user is scrolling up, we want to keep the list inverted. + // If the user is scrolling down, we want to keep the list normal. + // If the user is at the bottom, we want it inverted (this is set in the readingDirection + // conditions further below). + // If the content is not scrollable, we want it inverted. + // If we're scrolling to a specific message, we want it normal because we + // assume the user is reading from that message down. + // However, if we're scrolling to a particular message in a thread, we want it inverted. + const isInverted = isEmpty ? false - : !isScrollable - ? true - : loadDirection === 'older'; + : userHasScrolled && readingDirectionRef.current === 'down' + ? false + : userHasScrolled && readingDirectionRef.current === 'up' + ? true + : scrollElementRef.current?.clientHeight && !isScrollable + ? true + : scrollTo && !inThread + ? false + : true; + + useObjectChangeLogging( + { + isAtTop, + isAtBottom, + hasLoadedNewest, + hasLoadedOldest, + loadDirection, + anchorIndex, + isLoadingOlder, + isLoadingNewer, + isInverted, + userHasScrolled, + }, + logger + ); + // We want to render newest messages first, but we receive them oldest-first. // This is a simple way to reverse the order without having to reverse a big array. const transformIndex = useCallback( @@ -427,25 +484,13 @@ export default function ChatScroller({ if (anchorIndex !== null && !userHasScrolled) { scrollToAnchor(); } - const { clientHeight, scrollTop, scrollHeight } = - scrollElementRef.current ?? { - clientHeight: 0, - scrollTop: 0, - scrollHeight: 0, - }; - // Prevent list from being at the end of new messages and old messages - // at the same time -- can happen if there are few messages loaded. - const atEndThreshold = Math.min( - (scrollHeight - clientHeight) / 2, - thresholds.atEndThreshold - ); - const isAtScrollBeginning = scrollTop === 0; - const isAtScrollEnd = - scrollTop + clientHeight >= scrollHeight - atEndThreshold; - const nextAtTop = - (isInverted && isAtScrollEnd) || (!isInverted && isAtScrollBeginning); - const nextAtBottom = - (isInverted && isAtScrollBeginning) || (!isInverted && isAtScrollEnd); + const nextAtTop = isForceScrolling.current + ? false + : (isInverted && isAtScrollEnd) || (!isInverted && isAtScrollBeginning); + const nextAtBottom = isForceScrolling.current + ? false + : (isInverted && isAtScrollBeginning) || (!isInverted && isAtScrollEnd); + setIsAtTop(nextAtTop); setIsAtBottom(nextAtBottom); }, [ @@ -453,7 +498,8 @@ export default function ChatScroller({ anchorIndex, userHasScrolled, scrollToAnchor, - scrollElementRef, + isAtScrollBeginning, + isAtScrollEnd, ]), }); virtualizerRef.current = virtualizer; @@ -487,9 +533,18 @@ export default function ChatScroller({ // When the list inverts, we need to flip the scroll position in order to appear to stay in the same place. // We do this here as opposed to in an effect so that virtualItems is correct in time for this render. const lastIsInverted = useRef(isInverted); - if (userHasScrolled && isInverted !== lastIsInverted.current) { + if ( + userHasScrolled && + isInverted !== lastIsInverted.current && + !isLoadingOlder && + !isLoadingNewer + ) { logger.log('inverting chat scroller'); - forceScroll(contentHeight - virtualizer.scrollOffset); + const offset = contentHeight - virtualizerRef.current.scrollOffset; + // We need to subtract the height of the scroll element to get the correct + // offset when inverting. + const newOffset = offset - scrollElementHeight; + forceScroll(newOffset); lastIsInverted.current = isInverted; } @@ -499,6 +554,61 @@ export default function ChatScroller({ // TODO: Distentangle virtualizer init to avoid this. const finalHeight = contentHeight ?? virtualizer.getTotalSize(); + const { scrollDirection } = virtualizerRef.current ?? {}; + + if (userHasScrolled && !isForceScrolling.current) { + logger.log('setting reading direction'); + + if (isInverted) { + if ( + scrollDirection === 'backward' && + readingDirectionRef.current !== 'down' + ) { + logger.log( + 'isInverted and scrollDirection is backward setting reading direction to down' + ); + readingDirectionRef.current = 'down'; + } + + if ( + scrollDirection === 'forward' && + readingDirectionRef.current !== 'up' + ) { + logger.log( + 'isInverted and scrollDirection is forward setting reading direction to up' + ); + readingDirectionRef.current = 'up'; + } + } else { + if ( + scrollDirection === 'backward' && + readingDirectionRef.current !== 'up' + ) { + logger.log( + 'not isInverted and scrollDirection is backward setting reading direction to up' + ); + readingDirectionRef.current = 'up'; + } + + if ( + scrollDirection === 'forward' && + readingDirectionRef.current !== 'down' + ) { + logger.log( + 'not isInverted and scrollDirection is forward setting reading direction to down' + ); + readingDirectionRef.current = 'down'; + } + + if (scrollDirection === null && isAtExactScrollEnd) { + logger.log( + 'not isInverted and scrollDirection is null setting reading direction to up' + ); + readingDirectionRef.current = 'up'; + } + } + } + return ( <>
+ + diff --git a/ui/src/chat/ChatThread/ChatThread.tsx b/ui/src/chat/ChatThread/ChatThread.tsx index f3c3444c15..b610693709 100644 --- a/ui/src/chat/ChatThread/ChatThread.tsx +++ b/ui/src/chat/ChatThread/ChatThread.tsx @@ -234,6 +234,7 @@ export default function ChatThread() { hasLoadedNewest={false} hasLoadedOldest={false} onAtBottom={onAtBottom} + inThread /> )}
From 3ff34a1e8ee9390fc156fd3b8b4ae85eb7f7f017 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 12 Feb 2024 11:26:49 -0600 Subject: [PATCH 007/182] subscriber: added to channels --- desk/app/channels.hoon | 75 ++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 1a444e9127..c568df10c3 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -8,7 +8,7 @@ /- c=channels, g=groups, ha=hark /- meta /+ default-agent, verb, dbug, sparse, neg=negotiate -/+ utils=channel-utils, volume +/+ utils=channel-utils, volume, s=subscriber :: performance, keep warm /+ channel-json :: @@ -24,7 +24,7 @@ |% +$ card card:agent:gall +$ current-state - $: %2 + $: %3 =v-channels:c voc=(map [nest:c plan:c] (unit said:c)) pins=(list nest:c) ::TODO vestigial, in groups-ui now, remove me @@ -33,6 +33,8 @@ :: .pending-ref-edits: for migration, see also +poke %negotiate-notif :: pending-ref-edits=(jug ship [=kind:c name=term]) + :: delayed resubscribes + =^subs:s == -- =| current-state @@ -99,9 +101,13 @@ :: ++ safe-watch |= [=wire =dock =path] + |= delay=? ^+ cor ?: (~(has by wex.bowl) wire dock) cor - (emit %pass wire %agent dock %watch path) + =^ card=(unit card) subs + (~(subscribe s [subs bowl]) wire dock path delay) + ?~ card cor + (emit u.card) :: ++ load |= =vase @@ -109,13 +115,25 @@ =+ !<(old=versioned-state vase) =? old ?=(%0 -.old) (state-0-to-1 old) =? old ?=(%1 -.old) (state-1-to-2 old) - ?> ?=(%2 -.old) + =? old ?=(%2 -.old) (state-2-to-3 old) + ?> ?=(%3 -.old) =. state old inflate-io :: - +$ versioned-state $%(state-2 state-1 state-0) - +$ state-2 current-state + +$ versioned-state $%(state-3 state-2 state-1 state-0) + +$ state-3 current-state :: + +$ state-2 + $: %2 + =v-channels:c + voc=(map [nest:c plan:c] (unit said:c)) + pins=(list nest:c) ::TODO vestigial, in groups-ui now, remove me + hidden-posts=(set id-post:c) + :: + :: .pending-ref-edits: for migration, see also +poke %negotiate-notif + :: + pending-ref-edits=(jug ship [=kind:c name=term]) + == +$ state-1 $: %1 v-channels=(map nest:c v-channel-1) @@ -123,6 +141,12 @@ pins=(list nest:c) hidden-posts=(set id-post:c) == + ++ state-2-to-3 + |= s=state-2 + ^- state-3 + %= s - %3 + pending-ref-edits [pending-ref-edits.s ~] + == ++ v-channel-1 |^ ,[global local] +$ global @@ -289,13 +313,13 @@ :: :: watch all the subscriptions we expect to have :: - =. cor watch-groups + =. cor (watch-groups |) :: =. cor %+ roll ~(tap by v-channels) |= [[=nest:c *] core=_cor] - ca-abet:ca-safe-sub:(ca-abed:ca-core:core nest) + ca-abet:(ca-safe-sub:(ca-abed:ca-core:core nest) |) :: cor :: @@ -411,7 +435,7 @@ |= [=nest:c =plan:c] ?. (~(has by v-channels) nest) =/ wire (said-wire nest plan) - (safe-watch wire [ship.nest server] wire) + ((safe-watch wire [ship.nest server] wire) |) ::TODO not guaranteed to resolve, we might have partial backlog ca-abet:(ca-said:(ca-abed:ca-core nest) plan) :: @@ -476,7 +500,7 @@ :: [%groups ~] ?+ -.sign !! - %kick watch-groups + %kick (watch-groups &) %watch-ack ?~ p.sign cor @@ -566,8 +590,11 @@ ++ emit |=(=card ca-core(cor (^emit card))) ++ emil |=(caz=(list card) ca-core(cor (^emil caz))) ++ give |=(=gift:agent:gall ca-core(cor (^give gift))) - ++ safe-watch |=([=wire =dock =path] ca-core(cor (^safe-watch +<))) ++ ca-perms ~(. perms:utils our.bowl now.bowl nest group.perm.perm.channel) + ++ safe-watch + |= [=wire =dock =path] + |= delay=? + ca-core(cor ((^safe-watch wire dock path) delay)) ++ ca-abet %_ cor v-channels @@ -608,7 +635,7 @@ =. last-read.remark.channel now.bowl =. ca-core ca-give-unread =. ca-core (ca-response %join group) - ca-safe-sub + (ca-safe-sub |) :: :: handle an action from the client :: @@ -687,9 +714,11 @@ (~(has by wex.bowl) [ca-sub-wire ship.nest dap.bowl]) :: ++ ca-safe-sub + |= delay=? ?: ca-has-sub ca-core - ?^ posts.channel ca-start-updates + ?^ posts.channel (ca-start-updates delay) =. load.net.channel | + %. delay %^ safe-watch (weld ca-area /checkpoint) [ship.nest server] ?. =(our.bowl ship.nest) =/ count ?:(=(%diary kind.nest) '20' '100') @@ -697,9 +726,11 @@ /[kind.nest]/[name.nest]/checkpoint/time-range/(scot %da *@da) :: ++ ca-start-updates + |= delay=? :: not most optimal time, should maintain last heard time instead =/ tim=(unit time) (bind (ram:on-v-posts:c posts.channel) head) + %. delay %^ safe-watch ca-sub-wire [ship.nest server] /[kind.nest]/[name.nest]/updates/(scot %da (fall tim *@da)) :: @@ -726,7 +757,7 @@ =/ =wire (weld ca-area /create) (emit %pass wire %agent [our.bowl server] %watch path) :: - %kick ca-safe-sub + %kick (ca-safe-sub &) %watch-ack ?~ p.sign ca-core %- (slog leaf+"{}: Failed creation" u.p.sign) @@ -741,14 +772,14 @@ =. ca-core ca-give-unread =. ca-core (emit %pass (weld ca-area /create) %agent [ship.nest server] %leave ~) - ca-safe-sub + (ca-safe-sub |) == :: ++ ca-take-update |= =sign:agent:gall ^+ ca-core ?+ -.sign ca-core - %kick ca-safe-sub + %kick (ca-safe-sub &) %watch-ack ?~ p.sign ca-core %- (slog leaf+"{}: Failed subscription" u.p.sign) @@ -767,7 +798,7 @@ ^+ ca-core ?+ -.sign ca-core :: only if kicked prematurely - %kick ?:(load.net.channel ca-core ca-safe-sub) + %kick ?:(load.net.channel ca-core (ca-safe-sub &)) %watch-ack ?~ p.sign ca-core %- (slog leaf+"{}: Failed partial checkpoint" u.p.sign) @@ -786,7 +817,7 @@ ^+ ca-core ?+ -.sign ca-core :: only hit if kicked prematurely (we %leave after the first %fact) - %kick ca-sync-backlog + %kick (ca-sync-backlog &) %watch-ack ?~ p.sign ca-core %- (slog leaf+"{}: Failed backlog" u.p.sign) @@ -805,9 +836,9 @@ ^+ ca-core =. load.net.channel & =. ca-core (ca-apply-checkpoint chk &) - =. ca-core ca-start-updates + =. ca-core (ca-start-updates |) =. ca-core (ca-fetch-contacts chk) - =. ca-core ca-sync-backlog + =. ca-core (ca-sync-backlog |) =/ wire (weld ca-area /checkpoint) (emit %pass wire %agent [ship.nest server] %leave ~) :: @@ -848,8 +879,10 @@ ca-core :: ++ ca-sync-backlog + |= delay=? =/ checkpoint-start (pry:on-v-posts:c posts.channel) ?~ checkpoint-start ca-core + %. delay %^ safe-watch (weld ca-area /backlog) [ship.nest server] %+ welp /[kind.nest]/[name.nest]/checkpoint/time-range @@ -1565,7 +1598,7 @@ ++ ca-recheck |= sects=(set sect:g) :: if our read permissions restored, re-subscribe - ?: (can-read:ca-perms our.bowl) ca-safe-sub + ?: (can-read:ca-perms our.bowl) (ca-safe-sub |) ca-core :: :: assorted helpers From 1de4be56b3fe4d514b6ab06d9a3107e7b3a265bd Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 12 Feb 2024 11:32:44 -0600 Subject: [PATCH 008/182] scroller: clarify logging on setting direction to up at exact scrollend --- ui/src/chat/ChatScroller/ChatScroller.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/chat/ChatScroller/ChatScroller.tsx b/ui/src/chat/ChatScroller/ChatScroller.tsx index 2f4a27c748..935cf5b47f 100644 --- a/ui/src/chat/ChatScroller/ChatScroller.tsx +++ b/ui/src/chat/ChatScroller/ChatScroller.tsx @@ -602,7 +602,7 @@ export default function ChatScroller({ if (scrollDirection === null && isAtExactScrollEnd) { logger.log( - 'not isInverted and scrollDirection is null setting reading direction to up' + "not isInverted, scrollDirection is null, and we're at the bottom setting reading direction to up" ); readingDirectionRef.current = 'up'; } From 979b16be80b94089c2befa24ccb65a643c92883c Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 12 Feb 2024 19:16:38 -0600 Subject: [PATCH 009/182] channels: adding test for core subscriptions --- desk/app/channels.hoon | 18 ++++++++- desk/tests/app/channels.hoon | 78 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 desk/tests/app/channels.hoon diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index c568df10c3..666697e01b 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -85,8 +85,9 @@ ++ on-arvo |= [=wire sign=sign-arvo] ^- (quip card _this) - ~& strange-diary-arvo+wire - `this + =^ cards state + abet:(arvo:cor wire sign) + [cards this] -- |_ [=bowl:gall cards=(list card)] ++ abet [(flop cards) state] @@ -569,6 +570,19 @@ ``loob+!>((~(has by v-channels) kind.pole ship name.pole)) == :: +++ arvo + |= [=(pole knot) sign=sign-arvo] + ^+ cor + ?+ pole ~|(bad-arvo-take/pole !!) + [%~.~ %cancel-retry rest=*] cor + :: + [%~.~ %retry rest=*] + =^ card=(unit card) subs + (~(handle-wakeup s [subs bowl]) pole) + ?~ card cor + (emit u.card) + == +:: ++ unreads ^- unreads:c %- ~(gas by *unreads:c) diff --git a/desk/tests/app/channels.hoon b/desk/tests/app/channels.hoon new file mode 100644 index 0000000000..416d90d6e1 --- /dev/null +++ b/desk/tests/app/channels.hoon @@ -0,0 +1,78 @@ +/- g=groups, c=channels +/+ *test-agent +/= channels-agent /app/channels +|% +++ dap %channels-test +++ server-dap %channels-test-server +++ negotiate /~/negotiate/inner-watch/~zod/[server-dap] +++ sub-wire /chat/(scot %p ~zod)/test +++ negotiate-wire (weld negotiate /chat/(scot %p ~zod)/test) +++ chk-wire (weld negotiate-wire /checkpoint) +++ chk-path /chat/test/checkpoint/before/100 +++ the-dock [~zod server-dap] +++ the-nest [%chat ~zod %test] +++ the-group [~zod %test] +++ test-checkpoint-sub + %- eval-mare + =/ m (mare ,~) + ;< * bind:m channel-join + =/ retry (weld /~/retry (weld sub-wire /checkpoint)) + (check-subscription-loop chk-wire chk-wire the-dock chk-path retry) +++ test-updates-sub + %- eval-mare + =/ m (mare ,~) + ;< * bind:m channel-join + :: get checkpoint and start updates + =/ =cage [%channel-checkpoint !>(*u-checkpoint:c)] + ;< * bind:m (do-agent chk-wire the-dock %fact cage) + =/ updates-wire (weld negotiate-wire /updates) + :: kicking updates retries back to checkpoint + =/ updates-retry (weld /~/retry (weld sub-wire /checkpoint)) + ;< * bind:m (do-agent updates-wire the-dock %watch-ack ~) + (check-subscription-loop updates-wire chk-wire the-dock chk-path updates-retry) +++ test-backlog-sub + %- eval-mare + =/ m (mare ,~) + ;< * bind:m channel-join + ;< bw=bowl bind:m get-bowl + :: get checkpoint and start updates + =/ last-post-time (add now.bw 1) + =/ last-post=v-post:c + :- [last-post-time ~ ~] + [0 [[~ ~dev last-post-time] %chat ~]] + =/ posts=v-posts:c + (gas:on-v-posts:c *v-posts:c ~[[last-post-time `last-post]]) + =/ checkpoint *u-checkpoint:c + =/ =cage [%channel-checkpoint !>(checkpoint(posts posts))] + ;< * bind:m (do-agent chk-wire the-dock %fact cage) + =/ backlog-wire (weld negotiate-wire /backlog) + =/ backlog-path + %+ welp /chat/test/checkpoint/time-range + /(scot %da *@da)/(scot %da last-post-time) + =/ backlog-retry (weld /~/retry (weld sub-wire /backlog)) + ;< * bind:m (do-agent backlog-wire the-dock %watch-ack ~) + (check-subscription-loop backlog-wire backlog-wire the-dock backlog-path backlog-retry) +++ check-subscription-loop + |= [sub=wire resub=wire =dock =path retry-wire=wire] + =/ m (mare ,~) + ^- form:m + ;< bw=bowl bind:m get-bowl + =/ now=time now.bw + :: kick & resubscribe with delay + ;< caz=(list card) bind:m (do-agent sub dock %kick ~) + =/ next=time (add now ~s30) + ;< * bind:m + (ex-cards caz (ex-arvo retry-wire %b %wait next) ~) + ;< * bind:m (jab-bowl |=(b=bowl b(now next))) + :: wakeup & resubscribe no delay + ;< caz=(list card) bind:m (do-arvo retry-wire %behn %wake ~) + (ex-cards caz (ex-task resub dock %watch path) ~) +++ channel-join + =/ m (mare ,(list card)) + ^- form:m + :: join channel + ;< * bind:m (do-init dap channels-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev))) + ;< * bind:m (do-poke %channel-action !>([%channel the-nest %join the-group])) + (do-agent chk-wire the-dock %watch-ack ~) +-- \ No newline at end of file From dd6035d683da765b9dd32a2f297a8f7450dd96e7 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 13 Feb 2024 09:28:45 -0600 Subject: [PATCH 010/182] scroller: adjust some conditions to prevent loading loop when exiting/reopening threads or clicking 'go to latest' --- ui/src/chat/ChatScroller/ChatScroller.tsx | 25 +++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ui/src/chat/ChatScroller/ChatScroller.tsx b/ui/src/chat/ChatScroller/ChatScroller.tsx index 935cf5b47f..d545bb924f 100644 --- a/ui/src/chat/ChatScroller/ChatScroller.tsx +++ b/ui/src/chat/ChatScroller/ChatScroller.tsx @@ -36,7 +36,7 @@ import { PageTuple, ReplyTuple } from '@/types/channel'; import { useShowDevTools } from '@/state/local'; import ChatScrollerDebugOverlay from './ChatScrollerDebugOverlay'; -const logger = createDevLogger('ChatScroller', false); +const logger = createDevLogger('ChatScroller', true); interface CustomScrollItemData { type: 'custom'; @@ -281,6 +281,7 @@ export default function ChatScroller({ */ const forceScroll = useCallback((offset: number) => { if (isForceScrolling.current) return; + logger.log('force scrolling to', offset); isForceScrolling.current = true; const virt = virtualizerRef.current; if (!virt) return; @@ -371,9 +372,11 @@ export default function ChatScroller({ * Scroll to current anchor index */ const scrollToAnchor = useCallback(() => { - logger.log('scrolling to anchor'); const virt = virtualizerRef.current; if (!virt || anchorIndex === null) return; + logger.log('scrolling to anchor', { + anchorIndex, + }); const index = transformIndex(anchorIndex); const [nextOffset] = virt.getOffsetForIndex(index, 'center'); const measurement = virt.measurementsCache[index]; @@ -386,6 +389,7 @@ export default function ChatScroller({ // Reset scroll when scrollTo changes useEffect(() => { + if (scrollTo === undefined) return; logger.log('scrollto changed'); resetUserHasScrolled(); scrollToAnchor(); @@ -458,7 +462,11 @@ export default function ChatScroller({ // By default, the virtualizer tries to keep the position of the topmost // item on screen pinned, but we need to override that behavior to keep a // message centered or to stay at the bottom of the chat. - if (anchorIndex !== null && !userHasScrolled) { + if ( + anchorIndex !== null && + !userHasScrolled && + !isForceScrolling.current + ) { // Fix for no-param-reassign scrollToAnchor(); } else { @@ -481,7 +489,11 @@ export default function ChatScroller({ // Called by the virtualizer whenever any layout property changes. // We're using it to keep track of top and bottom thresholds. onChange: useCallback(() => { - if (anchorIndex !== null && !userHasScrolled) { + if ( + anchorIndex !== null && + !userHasScrolled && + !isForceScrolling.current + ) { scrollToAnchor(); } const nextAtTop = isForceScrolling.current @@ -534,16 +546,15 @@ export default function ChatScroller({ // We do this here as opposed to in an effect so that virtualItems is correct in time for this render. const lastIsInverted = useRef(isInverted); if ( - userHasScrolled && isInverted !== lastIsInverted.current && !isLoadingOlder && !isLoadingNewer ) { - logger.log('inverting chat scroller'); const offset = contentHeight - virtualizerRef.current.scrollOffset; // We need to subtract the height of the scroll element to get the correct // offset when inverting. const newOffset = offset - scrollElementHeight; + logger.log('inverting chat scroller, setting offset to', newOffset); forceScroll(newOffset); lastIsInverted.current = isInverted; } @@ -557,8 +568,6 @@ export default function ChatScroller({ const { scrollDirection } = virtualizerRef.current ?? {}; if (userHasScrolled && !isForceScrolling.current) { - logger.log('setting reading direction'); - if (isInverted) { if ( scrollDirection === 'backward' && From 6e0f398feba2369534169eca8a133d6ca3b87044 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 13 Feb 2024 12:12:35 -0600 Subject: [PATCH 011/182] scroller: fix an issue (scrollTo not triggering) caused by delaying forceScrolls on inversion flips --- ui/src/chat/ChatScroller/ChatScroller.tsx | 38 ++++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/ui/src/chat/ChatScroller/ChatScroller.tsx b/ui/src/chat/ChatScroller/ChatScroller.tsx index d545bb924f..ab29cb13c9 100644 --- a/ui/src/chat/ChatScroller/ChatScroller.tsx +++ b/ui/src/chat/ChatScroller/ChatScroller.tsx @@ -36,7 +36,7 @@ import { PageTuple, ReplyTuple } from '@/types/channel'; import { useShowDevTools } from '@/state/local'; import ChatScrollerDebugOverlay from './ChatScrollerDebugOverlay'; -const logger = createDevLogger('ChatScroller', true); +const logger = createDevLogger('ChatScroller', false); interface CustomScrollItemData { type: 'custom'; @@ -279,18 +279,23 @@ export default function ChatScroller({ /** * Set scroll position, bypassing virtualizer change logic. */ - const forceScroll = useCallback((offset: number) => { - if (isForceScrolling.current) return; - logger.log('force scrolling to', offset); - isForceScrolling.current = true; - const virt = virtualizerRef.current; - if (!virt) return; - virt.scrollOffset = offset; - virt.scrollElement?.scrollTo?.({ top: offset }); - setTimeout(() => { - isForceScrolling.current = false; - }, 300); - }, []); + const forceScroll = useCallback( + (offset: number, bypassDelay = false) => { + if (isForceScrolling.current && !bypassDelay) return; + if (!inThread) { + logger.log('force scrolling to', offset); + } + isForceScrolling.current = true; + const virt = virtualizerRef.current; + if (!virt) return; + virt.scrollOffset = offset; + virt.scrollElement?.scrollTo?.({ top: offset }); + setTimeout(() => { + isForceScrolling.current = false; + }, 300); + }, + [inThread] + ); const isEmpty = useMemo( () => count === 0 && hasLoadedNewest && hasLoadedOldest, @@ -357,6 +362,7 @@ export default function ChatScroller({ isLoadingNewer, isInverted, userHasScrolled, + isForceScrolling: isForceScrolling.current, }, logger ); @@ -390,7 +396,7 @@ export default function ChatScroller({ // Reset scroll when scrollTo changes useEffect(() => { if (scrollTo === undefined) return; - logger.log('scrollto changed'); + logger.log('scrollto changed', scrollTo?.toString()); resetUserHasScrolled(); scrollToAnchor(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -555,7 +561,9 @@ export default function ChatScroller({ // offset when inverting. const newOffset = offset - scrollElementHeight; logger.log('inverting chat scroller, setting offset to', newOffset); - forceScroll(newOffset); + // We need to bypass the delay here because we're inverting the scroll + // immediately after the user has scrolled in this case. + forceScroll(newOffset, true); lastIsInverted.current = isInverted; } From ea5ea6705169b15d4178fdddbe945a0bfb8d2efe Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Thu, 15 Feb 2024 12:09:32 -0500 Subject: [PATCH 012/182] Actions --- desk/sur/activity.hoon | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 7a9b90a053..55ebe7ac93 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -9,7 +9,6 @@ $% [%channel channel-concern] [%dm dm-concern] == -:: could do this per-channel/concern +$ volume (map flavor level) +$ flavor $? %dm-invite @@ -34,9 +33,9 @@ +$ group-concern group=flag +$ channel-concern [channel=nest group=flag] +$ dm-concern =whom ++$ dm-post-concern [=message-key =whom] +$ post-concern [=message-key channel=nest group=flag] +$ reply-concern [=message-key target=message-key channel=nest group=flag] -+$ dm-post-concern [=writ-key =whom] +$ whom $% [%ship p=ship] [%club p=id:club] @@ -46,9 +45,14 @@ :: same as content of actual message ~ +$ action - $% [%add ...] - [%read ...] - [%adjust ...] + $% [%add =event] + [%read =read-action] + [%adjust =flavor =level] == +$ unread-summary [time count=@ud threads=(list [time count=@ud])] ++$ read-action + $% [%last-seen =time] + [%thread =time] + [%post =time] + == -- From 51b0581cb4721eb9a06fd4c26e0b40229ea3840b Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 15 Feb 2024 11:22:16 -0600 Subject: [PATCH 013/182] subscriber: correct unsubscribe behavior --- desk/lib/subscriber.hoon | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/desk/lib/subscriber.hoon b/desk/lib/subscriber.hoon index 19c06a1f4e..6f2f1fb4bb 100644 --- a/desk/lib/subscriber.hoon +++ b/desk/lib/subscriber.hoon @@ -8,6 +8,7 @@ :: +$ subs (map wire sub) :: +++ verb | ++ subscriber |_ [=subs bowl:gall] ++ interval ~s30 @@ -15,7 +16,7 @@ |= =wire ^- [(unit card:agent:gall) _subs] ?> ?=([%~.~ %retry *] wire) - :: ~& ['waking up' wire] + ~? verb ['waking up' wire] =/ sub (~(get by subs) t.t.wire) ?~ sub [~ subs] :- `[%pass t.t.wire %agent dock.u.sub %watch path.u.sub] @@ -26,18 +27,20 @@ ?: (~(has by subs) wire) ((slog 'Duplicate subscription' >[wire dock]< ~) [~ subs]) ?. delay [`[%pass wire %agent dock %watch path] subs] - :: ~& ['subscribing with delay' wire] + ~? verb ['subscribing with delay' wire] =/ fires-at (add now interval) :_ (~(put by subs) wire [dock path fires-at]) `[%pass (weld /~/retry wire) %arvo %b %wait fires-at] - ++ cancel + ++ unsubscribe |= =wire - ^- [(unit card:agent:gall) _subs] + ^- [(list card:agent:gall) _subs] =/ sub (~(get by subs) wire) ?~ sub ((slog 'No such subscription' >[wire]< ~) [~ subs]) - :: ~& ['cancelling' wire] + ~? verb ['cancelling' wire] :_ (~(del by subs) wire) - `[%pass (weld /~/cancel-retry wire) %arvo %b %rest fires-at.u.sub] + :~ [%pass (weld /~/cancel-retry wire) %arvo %b %rest fires-at.u.sub] + [%pass wire %agent dock.u.sub %leave ~] + == -- -- \ No newline at end of file From b2a48e1735ce104c591cb625d2d5bfe7404f5ac8 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Thu, 15 Feb 2024 17:40:42 -0500 Subject: [PATCH 014/182] Change unread representation to better handle threads and sketch out actions --- desk/app/activity.hoon | 55 ++++++++++++++++++++++++++++++++++++------ desk/sur/activity.hoon | 13 ++++++---- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 99b151039a..191ee07cee 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -69,6 +69,7 @@ ++ emil |=(caz=(list card) cor(cards (welp (flop caz) cards))) ++ give |=(=gift:agent:gall (emit %give gift)) ++ from-self =(our src):bowl +:: ++ init ^+ cor =. volume @@ -85,6 +86,7 @@ [%flag %default] == cor +:: ++ load |= =vase |^ ^+ cor @@ -92,6 +94,7 @@ ?> ?=(%0 -.old) =. state old cor +:: ++ poke |= [=mark =vase] ^+ cor @@ -100,15 +103,14 @@ =+ !<(=action:a vase) ?- -.action %add - :: TODO - cor + (add action) %read - :: TODO - cor + (read action) %adjust - cor + (adjust action) == == +:: ++ watch |= =(pole knot) ^+ cor @@ -117,6 +119,7 @@ %notifications ?>(from-self cor) %reads ?>(from-self cor) == +:: ++ peek |= =(pole knot) ^- (unit (unit cage)) @@ -132,7 +135,45 @@ [%x %event id=@] ``activity-event+!>((got:eon:a (slav %da id.pole))) [%x %unreads ~] - :: TODO get unread summary from each bucket - ``activity-unreads+!>((summarize-unreads indices)) + ``activity-unreads+!>(summarize-unreads) + == +:: +++ add + :: TODO add to stream & indices, update unreads, and send facts + |= =action:a + =. +:: +++ read + :: TODO update state and send facts + |= =action:a + ?- -.read-action.action + %last-seen + =/ indy (~(get by indices) index) + ?~ indy cor + =. indices + (~(put by indices) index [stream.indy [time.action events.indy]]) + cor + :: + %thread + =/ indy (~(get by indices) index) + ?~ indy cor + + :: + %post + =/ indy (~(get by indices) index) + ?~ indy cor + == +:: +++ adjust + |= =flavor:a =level:a + =. volume + (~(put by volume) flavor level + cor +:: +++ summarize-unreads + %- ~(run by indices) + |= [=stream:a =reads:a] + :: TODO slice by floor, remove seen messages, handle threads + ~ -- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 55ebe7ac93..1bb52bec91 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -1,10 +1,13 @@ /+ mp=mop-extensions |% -+$ stream ((mop time event) lte) -+$ eon ((on time event) lte) +++ eon ((on time event) lte) ++ emp ((mp time event) lte) +++ mep ((on time event-parent) lte) ++$ stream ((mop time event) lte) +$ indices (map index [=stream =reads]) -+$ reads [floor=time (set event-id=time)] ++$ reads [floor=time =event-parents)] ++$ event-parent [seen=? reply-floor=time] ++$ event-parents ((mop time event-parent) lte) +$ index $% [%channel channel-concern] [%dm dm-concern] @@ -40,13 +43,13 @@ $% [%ship p=ship] [%club p=id:club] == -+$ message-key [=id =time] ++$ message-key [id=(pair ship time) =time] +$ content :: same as content of actual message ~ +$ action $% [%add =event] - [%read =read-action] + [%read =index =read-action] [%adjust =flavor =level] == +$ unread-summary [time count=@ud threads=(list [time count=@ud])] From e50ae1307b17a07a777f894d7c5724426b4bcbe7 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 16 Feb 2024 06:52:29 -0600 Subject: [PATCH 015/182] Fixed issues with package.json files, fixed import order issues in files for this branch --- package-lock.json | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c775186f9..d49436eb65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "landscape-apps-mono", + "name": "homestead", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index ebc8c5a5cb..ebe4ebaccd 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "build:shared": "npm run build --prefix ./packages/shared", "build:web": "npm run build:shared && npm run build --prefix ./apps/tlon-web", "dev:shared": "npm run dev --prefix ./packages/shared", - "dev:android": "concurrently \"npm run dev:shared\" \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run android --prefix ./apps/tlon-mobile\"", + "dev:android": "concurrently \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run android --prefix ./apps/tlon-mobile\"", "dev:ios": "concurrently \"npm run dev:shared\" \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run ios --prefix ./apps/tlon-mobile\"", - "dev:web": "concurrently \"npm run dev:shared\" \"npm run dev-no-ssl --prefix ./apps/tlon-web\"", + "dev:web": "npm run dev --prefix ./apps/tlon-web", "test": "npm run test --workspaces --if-present -- run -u", "prepare": "husky", "postinstall": "patch-package" From a7693825e5f3ad96b3a62ad62311812d60eca813 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 16 Feb 2024 13:28:57 -0600 Subject: [PATCH 016/182] revert change to package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ebe4ebaccd..ebc8c5a5cb 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "build:shared": "npm run build --prefix ./packages/shared", "build:web": "npm run build:shared && npm run build --prefix ./apps/tlon-web", "dev:shared": "npm run dev --prefix ./packages/shared", - "dev:android": "concurrently \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run android --prefix ./apps/tlon-mobile\"", + "dev:android": "concurrently \"npm run dev:shared\" \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run android --prefix ./apps/tlon-mobile\"", "dev:ios": "concurrently \"npm run dev:shared\" \"npm run dev --prefix ./apps/tlon-mobile\" \"npm run ios --prefix ./apps/tlon-mobile\"", - "dev:web": "npm run dev --prefix ./apps/tlon-web", + "dev:web": "concurrently \"npm run dev:shared\" \"npm run dev-no-ssl --prefix ./apps/tlon-web\"", "test": "npm run test --workspaces --if-present -- run -u", "prepare": "husky", "postinstall": "patch-package" From 85f5c1ed5cb68f854af494a716f5929db4d51d07 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 16 Feb 2024 15:25:29 -0600 Subject: [PATCH 017/182] subscriber: add unsubscribe usage and test --- desk/app/channels.hoon | 12 +++++------- desk/app/groups.hoon | 20 +++++++++---------- desk/lib/subscriber.hoon | 17 ++++++++-------- desk/tests/app/groups.hoon | 40 +++++++++++++++++++++++++++++++------- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 666697e01b..8c414c23d2 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -105,10 +105,9 @@ |= delay=? ^+ cor ?: (~(has by wex.bowl) wire dock) cor - =^ card=(unit card) subs + =^ caz=(list card) subs (~(subscribe s [subs bowl]) wire dock path delay) - ?~ card cor - (emit u.card) + (emil caz) :: ++ load |= =vase @@ -146,7 +145,7 @@ |= s=state-2 ^- state-3 %= s - %3 - pending-ref-edits [pending-ref-edits.s ~] + pending-ref-edits [pending-ref-edits.s *^subs:^s] == ++ v-channel-1 |^ ,[global local] @@ -577,10 +576,9 @@ [%~.~ %cancel-retry rest=*] cor :: [%~.~ %retry rest=*] - =^ card=(unit card) subs + =^ caz=(list card) subs (~(handle-wakeup s [subs bowl]) pole) - ?~ card cor - (emit u.card) + (emil caz) == :: ++ unreads diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 5248dccae3..0db8164da1 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -356,7 +356,7 @@ ++ state-2-to-3 |= state-2 ^- current-state - [%3 groups volume xeno shoal ~] + [%3 groups volume xeno shoal *^subs:s] :: ++ groups-1-to-2 |= groups=net-groups:zero @@ -611,19 +611,17 @@ [%~.~ %cancel-retry rest=*] cor :: [%~.~ %retry rest=*] - =^ card=(unit card) subs + =^ caz=(list card) subs (~(handle-wakeup s [subs bowl]) pole) - ?~ card cor - (emit u.card) + (emil caz) == :: ++ subscribe |= [=wire =dock =path] |= delay=? - =^ card=(unit card) subs + =^ caz=(list card) subs (~(subscribe s [subs bowl]) wire dock path delay) - ?~ card cor - (emit u.card) + (emil caz) :: ++ cast |= [grp=flag:g gra=flag:g] @@ -818,7 +816,7 @@ (~(del by groups) flag) (~(put by groups) flag net group) ?. gone cor - =? cor !=(p.flag our.bowl) (emit leave:go-pass) + =? cor !=(p.flag our.bowl) (emil leave:go-pass) =/ =action:g [flag now.bowl %del ~] (give %fact ~[/groups/ui] act:mar:g !>(action)) ++ go-abed @@ -868,10 +866,12 @@ ++ go-pass |% ++ leave - ^- card + ^- (list card) =/ =wire (snoc go-area %updates) =/ =dock [p.flag dap.bowl] - [%pass wire %agent dock %leave ~] + =^ caz=(list card) subs + (~(unsubscribe s [subs bowl]) wire dock) + caz :: ++ remove-self ^- card diff --git a/desk/lib/subscriber.hoon b/desk/lib/subscriber.hoon index 6f2f1fb4bb..62c3737fe7 100644 --- a/desk/lib/subscriber.hoon +++ b/desk/lib/subscriber.hoon @@ -14,33 +14,34 @@ ++ interval ~s30 ++ handle-wakeup |= =wire - ^- [(unit card:agent:gall) _subs] + ^- [(list card:agent:gall) _subs] ?> ?=([%~.~ %retry *] wire) ~? verb ['waking up' wire] =/ sub (~(get by subs) t.t.wire) ?~ sub [~ subs] - :- `[%pass t.t.wire %agent dock.u.sub %watch path.u.sub] + :- ~[[%pass t.t.wire %agent dock.u.sub %watch path.u.sub]] (~(del by subs) t.t.wire) ++ subscribe |= [=wire =dock =path delay=?] - ^- [(unit card:agent:gall) _subs] + ^- [(list card:agent:gall) _subs] ?: (~(has by subs) wire) ((slog 'Duplicate subscription' >[wire dock]< ~) [~ subs]) - ?. delay [`[%pass wire %agent dock %watch path] subs] + ?. delay [~[[%pass wire %agent dock %watch path]] subs] ~? verb ['subscribing with delay' wire] =/ fires-at (add now interval) :_ (~(put by subs) wire [dock path fires-at]) - `[%pass (weld /~/retry wire) %arvo %b %wait fires-at] + ~[[%pass (weld /~/retry wire) %arvo %b %wait fires-at]] ++ unsubscribe - |= =wire + |= [=wire =dock] ^- [(list card:agent:gall) _subs] + =/ leave [%pass wire %agent dock %leave ~] =/ sub (~(get by subs) wire) ?~ sub ((slog 'No such subscription' >[wire]< ~) [~ subs]) ~? verb ['cancelling' wire] :_ (~(del by subs) wire) - :~ [%pass (weld /~/cancel-retry wire) %arvo %b %rest fires-at.u.sub] - [%pass wire %agent dock.u.sub %leave ~] + :~ [%pass (weld /~/retry wire) %arvo %b %rest fires-at.u.sub] + leave == -- -- \ No newline at end of file diff --git a/desk/tests/app/groups.hoon b/desk/tests/app/groups.hoon index 5fbaa4fed8..e81dbe2dfe 100644 --- a/desk/tests/app/groups.hoon +++ b/desk/tests/app/groups.hoon @@ -3,30 +3,56 @@ /= groups-agent /app/groups |% ++ dap %groups-test -++ the-wire /groups/(scot %p ~zod)/test/updates -++ the-path (weld the-wire /init) +++ the-wire /groups/(scot %p ~zod)/test +++ updates-wire (weld the-wire /updates) +++ the-path (weld updates-wire /init) ++ the-dock [~zod dap] ++ the-group [%test 'test' '' '' '' [%open ~ ~] ~ |] -++ retry (weld /~/retry the-wire) +++ flag [~zod %test] +++ retry (weld /~/retry updates-wire) ++ test-subscription-loop %- eval-mare =/ m (mare ,~) :: init and add group ;< * bind:m (do-init %groups-test groups-agent) ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev))) - ;< * bind:m (do-poke %group-join !>([[~zod %test] &])) + ;< * bind:m (do-poke %group-join !>([flag &])) ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~zod))) ;< * bind:m (do-agent /gangs/(scot %p ~zod)/test/join/add the-dock %poke-ack ~) - ;< * bind:m (do-agent the-wire the-dock %watch-ack ~) + ;< * bind:m (do-agent updates-wire the-dock %watch-ack ~) ;< bw=bowl bind:m get-bowl =/ now=time now.bw :: kick & resubscribe with delay - ;< caz=(list card) bind:m (do-agent the-wire the-dock %kick ~) + ;< caz=(list card) bind:m (do-agent updates-wire the-dock %kick ~) =/ next=time (add now ~s30) ;< * bind:m (ex-cards caz (ex-arvo retry %b %wait next) ~) ;< * bind:m (jab-bowl |=(b=bowl b(now next))) :: wakeup & resubscribe no delay ;< caz=(list card) bind:m (do-arvo retry %behn %wake ~) - (ex-cards caz (ex-task the-wire the-dock %watch the-path) ~) + (ex-cards caz (ex-task updates-wire the-dock %watch the-path) ~) +++ test-unsubscribe + %- eval-mare + =/ m (mare ,~) + :: init and add group + ;< * bind:m (do-init %groups-test groups-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev))) + ;< * bind:m (do-poke %group-join !>([flag &])) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~zod))) + ;< * bind:m (do-agent /gangs/(scot %p ~zod)/test/join/add the-dock %poke-ack ~) + ;< * bind:m (do-agent updates-wire the-dock %watch-ack ~) + ;< bw=bowl bind:m get-bowl + =/ now=time now.bw + :: kick & resubscribe with delay + ;< caz=(list card) bind:m (do-agent updates-wire the-dock %kick ~) + =/ next=time (add now ~s30) + ;< caz=(list card) bind:m (do-poke %group-leave !>(flag)) + =/ remove-vase !>([flag now %fleet (silt ~dev ~) %del ~]) + %+ ex-cards caz + :~ (ex-poke (weld the-wire /proxy) the-dock act:mar:g remove-vase) + (ex-fact ~[/groups /groups/ui] %group-leave !>(flag)) + (ex-arvo retry %b %rest next) + (ex-task updates-wire the-dock %leave ~) + (ex-fact ~[/groups/ui] act:mar:g !>([flag now %del ~])) + == -- \ No newline at end of file From 0a81abd3017ed16fca17315b62fc82e4b384ed64 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Fri, 16 Feb 2024 16:28:09 -0500 Subject: [PATCH 018/182] Unread summaries --- desk/app/activity.hoon | 100 ++++++++++++++++++++++++++++++++++++----- desk/sur/activity.hoon | 3 +- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 191ee07cee..59e1831c4e 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -117,7 +117,7 @@ ?+ pole ~|(bat-watch-path+pole !!) ~ ?>(from-self cor) %notifications ?>(from-self cor) - %reads ?>(from-self cor) + %unreads ?>(from-self cor) == :: ++ peek @@ -135,7 +135,7 @@ [%x %event id=@] ``activity-event+!>((got:eon:a (slav %da id.pole))) [%x %unreads ~] - ``activity-unreads+!>(summarize-unreads) + ``activity-unreads+!>((~(run by indices) summarize-unreads)) == :: ++ add @@ -144,27 +144,60 @@ =. :: ++ read - :: TODO update state and send facts |= =action:a ?- -.read-action.action %last-seen =/ indy (~(get by indices) index) ?~ indy cor + =/ new + [stream.indy [time.action events.indy]] =. indices - (~(put by indices) index [stream.indy [time.action events.indy]]) + (~(put by indices) index new) + =. cor + (give-unreads new) cor - :: + :: %thread =/ indy (~(get by indices) index) ?~ indy cor - - :: + =/ new + (put:mep reads.u.indy time.read-action.action [& time.read-action.action]) + =. indices + (~(put by indices) index new) + =. cor + (give-unreads new) + cor + :: %post =/ indy (~(get by indices) index) ?~ indy cor - + =/ old-event-parent (~(get by indy) time.read-action.action) + ?~ old-event-parent cor + =/ new + :- stream.u.indy + (put:mep reads.u.indy time.read-action.action [& reply-floor.old-event.parent]) + =. indices + (~(put by indices) index new) + =. cor + (give-unreads new) + cor + :: + %all + =/ indy (~(get by indices) index) + ?~ indy cor + =/ new + [stream.u.indy [now.bowl ~]] + =. indices + (~(put by indices) index new) + =. cor + (give-unreads new) + cor == :: +++ give-unreads + |= [=stream:a =reads:a] + (give %fact ~[/unreads] activity-index-unreads+!>((summarize-unreads [stream reads]))) +:: ++ adjust |= =flavor:a =level:a =. volume @@ -172,8 +205,53 @@ cor :: ++ summarize-unreads - %- ~(run by indices) |= [=stream:a =reads:a] - :: TODO slice by floor, remove seen messages, handle threads - ~ + ^- unread-summary:a + =. stream (lot:eon stream `floor.reads ~) + =/ event-parents event-parents.reads + :: for each item in reads + :: remove the post from the event stream + :: remove replies older than reply-floor from the event stream + :: then call stream-to-unreads + |- + ?~ event-parents (stream-to-unreads stream) + =/ [[=time =event-parent:a] rest=event-parents:a] (pop:mep reads) + %= $ + event-parents + rest + :: + stream + %^ (dip:eon @) stream + ~ + |= [* =event:a] + ^- [(unit event:a) ? @] + ?> ?=(?(%post %reply %dm-post) -.event) + ?: &(seen.event-parent =(time time.message-key.event)) + [~ ~ ~] + ?. =(-.event %reply) + [`event ~ ~] + ?: (lth time.message-key.event reply-floor) + [~ ~ ~] + [`event ~ ~] + == +++ stream-to-unreads + |= stream:a + ^- unread-summary:a + =/ newest *time + =/ count 0 + =/ threads=(map time [oldest-unread=time count=@ud]) ~ + :: for each event + :: update count and newest + :: if reply, update thread state + |- + ?~ stream + :+ newest count + %+ turn ~(val by threads) + |=([parent=time oldest-unread=time count=@ud] [oldest-unread count])) + =/ [[@ =event:a] rest=stream:a] (pop:eon stream) + ?= threads =(-.event %reply) + :: TODO confirm that using time.message-key here is right + =/ old (~(gut by threads) target.event [time.message-key.event 0]) + (~(put by threads) target.event [oldest-unread.old +(count.old)]) + $(newest time.message-key.event, count +(count), stream rest) -- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 1bb52bec91..f3465aacdb 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -52,10 +52,11 @@ [%read =index =read-action] [%adjust =flavor =level] == -+$ unread-summary [time count=@ud threads=(list [time count=@ud])] ++$ unread-summary [newest=time count=@ud threads=(list [oldest-unread=time count=@ud])] +$ read-action $% [%last-seen =time] [%thread =time] [%post =time] + [%all ~] == -- From 8355629d857cb1f3cf9c933a26e7e122eaec931e Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 16 Feb 2024 15:33:34 -0600 Subject: [PATCH 019/182] groups: remove whitespace --- desk/app/groups.hoon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 0db8164da1..f889939eac 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -20,7 +20,7 @@ $= volume $: base=level:v area=(map flag:g level:v) :: override per group - chan=(map nest:g level:v) :: override per channel + chan=(map nest:g level:v) :: override per channel == :: xeno=gangs:g @@ -336,7 +336,7 @@ $= volume $: base=level:v area=(map flag:g level:v) :: override per group - chan=(map nest:g level:v) :: override per channel + chan=(map nest:g level:v) :: override per channel == :: xeno=gangs:g From f796e74325374cf712c72c5e6f759b1519e98a26 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 20 Feb 2024 06:07:56 -0600 Subject: [PATCH 020/182] scroller: throttle reading direction change to reduce jumpiness, move reading direction change logic into useEffect --- .../src/chat/ChatScroller/ChatScroller.tsx | 153 +++++++++++------- 1 file changed, 91 insertions(+), 62 deletions(-) diff --git a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx index fa3d13f341..7ef0a38738 100644 --- a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx +++ b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx @@ -281,23 +281,18 @@ export default function ChatScroller({ /** * Set scroll position, bypassing virtualizer change logic. */ - const forceScroll = useCallback( - (offset: number, bypassDelay = false) => { - if (isForceScrolling.current && !bypassDelay) return; - if (!inThread) { - logger.log('force scrolling to', offset); - } - isForceScrolling.current = true; - const virt = virtualizerRef.current; - if (!virt) return; - virt.scrollOffset = offset; - virt.scrollElement?.scrollTo?.({ top: offset }); - setTimeout(() => { - isForceScrolling.current = false; - }, 300); - }, - [inThread] - ); + const forceScroll = useCallback((offset: number, bypassDelay = false) => { + if (isForceScrolling.current && !bypassDelay) return; + logger.log('force scrolling to', offset); + isForceScrolling.current = true; + const virt = virtualizerRef.current; + if (!virt) return; + virt.scrollOffset = offset; + virt.scrollElement?.scrollTo?.({ top: offset }); + setTimeout(() => { + isForceScrolling.current = false; + }, 300); + }, []); const isEmpty = useMemo( () => count === 0 && hasLoadedNewest && hasLoadedOldest, @@ -577,56 +572,90 @@ export default function ChatScroller({ const { scrollDirection } = virtualizerRef.current ?? {}; - if (userHasScrolled && !isForceScrolling.current) { - if (isInverted) { - if ( - scrollDirection === 'backward' && - readingDirectionRef.current !== 'down' - ) { - logger.log( - 'isInverted and scrollDirection is backward setting reading direction to down' - ); - readingDirectionRef.current = 'down'; - } + const lastOffset = useRef(null); - if ( - scrollDirection === 'forward' && - readingDirectionRef.current !== 'up' - ) { - logger.log( - 'isInverted and scrollDirection is forward setting reading direction to up' - ); - readingDirectionRef.current = 'up'; - } - } else { - if ( - scrollDirection === 'backward' && - readingDirectionRef.current !== 'up' - ) { - logger.log( - 'not isInverted and scrollDirection is backward setting reading direction to up' - ); - readingDirectionRef.current = 'up'; - } + useEffect(() => { + if (lastOffset.current === null) { + lastOffset.current = virtualizer.scrollOffset; + } - if ( - scrollDirection === 'forward' && - readingDirectionRef.current !== 'down' - ) { - logger.log( - 'not isInverted and scrollDirection is forward setting reading direction to down' - ); - readingDirectionRef.current = 'down'; - } + if (isScrolling) { + lastOffset.current = virtualizer.scrollOffset; + } + }, [isScrolling, virtualizer.scrollOffset]); + + // We use the absolute change in scroll offset to throttle the change in + // reading direction. This is because the scroll direction can change + // rapidly when the user is scrolling, and we don't want to change the + // reading direction too quickly, it can be jumpy. + // There is still a small jump when the user changes direction, but it's + // less noticeable than if we didn't throttle it. + const absoluteOffsetChange = lastOffset.current + ? Math.abs(virtualizer.scrollOffset - lastOffset.current) + : 0; - if (scrollDirection === null && isAtExactScrollEnd) { - logger.log( - "not isInverted, scrollDirection is null, and we're at the bottom setting reading direction to up" - ); - readingDirectionRef.current = 'up'; + useEffect(() => { + if ( + userHasScrolled && + !isForceScrolling.current && + absoluteOffsetChange > 30 + ) { + if (isInverted) { + if ( + scrollDirection === 'backward' && + readingDirectionRef.current !== 'down' + ) { + logger.log( + 'isInverted and scrollDirection is backward setting reading direction to down' + ); + readingDirectionRef.current = 'down'; + } + + if ( + scrollDirection === 'forward' && + readingDirectionRef.current !== 'up' + ) { + logger.log( + 'isInverted and scrollDirection is forward setting reading direction to up' + ); + readingDirectionRef.current = 'up'; + } + } else { + if ( + scrollDirection === 'backward' && + readingDirectionRef.current !== 'up' + ) { + logger.log( + 'not isInverted and scrollDirection is backward setting reading direction to up' + ); + readingDirectionRef.current = 'up'; + } + + if ( + scrollDirection === 'forward' && + readingDirectionRef.current !== 'down' + ) { + logger.log( + 'not isInverted and scrollDirection is forward setting reading direction to down' + ); + readingDirectionRef.current = 'down'; + } + + if (scrollDirection === null && isAtExactScrollEnd) { + logger.log( + "not isInverted, scrollDirection is null, and we're at the bottom setting reading direction to up" + ); + readingDirectionRef.current = 'up'; + } } } - } + }, [ + scrollDirection, + userHasScrolled, + isAtExactScrollEnd, + isInverted, + absoluteOffsetChange, + ]); return ( <> From 4581aba46921d2fef91548e2015190aa3e667d99 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Tue, 20 Feb 2024 13:43:34 -0500 Subject: [PATCH 021/182] Fix syntax errors --- desk/app/activity.hoon | 46 ++++++++++++++++++++++++------------------ desk/desk.bill | 1 + desk/sur/activity.hoon | 22 +++++++++----------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 59e1831c4e..3e25d3136b 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -2,24 +2,26 @@ /- a=activity /+ default-agent, verb, dbug :: -|% -+$ card card:agent:gall -:: -+$ versioned-state - $% state-0 - == -:: -+$ state-0 - [%0 =stream:a =indices:a =volume:a] +=> + |% + +$ card card:agent:gall + :: + +$ versioned-state + $% state-0 + == + :: + +$ state-0 + [%0 =stream:a =indices:a =volume:a] + -- :: =| state-0 =* state - :: -%- agent:dbug -%+ verb | ^- agent:gall :: =< + %+ verb | + %- agent:dbug |_ =bowl:gall +* this . def ~(. (default-agent this %|) bowl) @@ -89,7 +91,7 @@ :: ++ load |= =vase - |^ ^+ cor + ^+ cor =+ !<(old=versioned-state vase) ?> ?=(%0 -.old) =. state old @@ -141,10 +143,12 @@ ++ add :: TODO add to stream & indices, update unreads, and send facts |= =action:a - =. + ^+ cor + cor :: ++ read |= =action:a + ^+ cor ?- -.read-action.action %last-seen =/ indy (~(get by indices) index) @@ -188,7 +192,7 @@ =/ new [stream.u.indy [now.bowl ~]] =. indices - (~(put by indices) index new) + (~(put by indices) index new) =. cor (give-unreads new) cor @@ -196,19 +200,21 @@ :: ++ give-unreads |= [=stream:a =reads:a] + ^+ cor (give %fact ~[/unreads] activity-index-unreads+!>((summarize-unreads [stream reads]))) :: ++ adjust - |= =flavor:a =level:a + |= [=flavor:a =level:a] + ^+ cor =. volume - (~(put by volume) flavor level + (~(put by volume) flavor level) cor :: ++ summarize-unreads |= [=stream:a =reads:a] ^- unread-summary:a =. stream (lot:eon stream `floor.reads ~) - =/ event-parents event-parents.reads + =/ event-parents event-parents.reads :: for each item in reads :: remove the post from the event stream :: remove replies older than reply-floor from the event stream @@ -245,11 +251,11 @@ :: if reply, update thread state |- ?~ stream - :+ newest count + :+ newest count %+ turn ~(val by threads) - |=([parent=time oldest-unread=time count=@ud] [oldest-unread count])) + |=([parent=time oldest-unread=time count=@ud] [oldest-unread count]) =/ [[@ =event:a] rest=stream:a] (pop:eon stream) - ?= threads =(-.event %reply) + =? threads =(-.event %reply) :: TODO confirm that using time.message-key here is right =/ old (~(gut by threads) target.event [time.message-key.event 0]) (~(put by threads) target.event [oldest-unread.old +(count.old)]) diff --git a/desk/desk.bill b/desk/desk.bill index 9d6e30fff0..0d1dc0e3ca 100644 --- a/desk/desk.bill +++ b/desk/desk.bill @@ -7,4 +7,5 @@ %groups-ui %channels %channels-server + %activity == diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index f3465aacdb..64379b978b 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -1,3 +1,4 @@ +/- c=channels /+ mp=mop-extensions |% ++ eon ((on time event) lte) @@ -5,7 +6,7 @@ ++ mep ((on time event-parent) lte) +$ stream ((mop time event) lte) +$ indices (map index [=stream =reads]) -+$ reads [floor=time =event-parents)] ++$ reads [floor=time =event-parents] +$ event-parent [seen=? reply-floor=time] +$ event-parents ((mop time event-parent) lte) +$ index @@ -26,27 +27,24 @@ +$ level ?(%notify %default %trivial) +$ event $% [%dm-invite dm-concern] - [%dm-post dm-post-concern =content mention=?] + [%dm-post dm-post-concern content=story:c mention=?] [%kick group-concern =ship] [%join group-concern =ship] - [%post post-concern =content mention=?] - [%reply reply-concern =content mention=?] + [%post post-concern content=story:c mention=?] + [%reply reply-concern content=story:c mention=?] [%flag post-concern] == -+$ group-concern group=flag -+$ channel-concern [channel=nest group=flag] ++$ group-concern group=flag:c ++$ channel-concern [channel=nest:c group=flag:c] +$ dm-concern =whom +$ dm-post-concern [=message-key =whom] -+$ post-concern [=message-key channel=nest group=flag] -+$ reply-concern [=message-key target=message-key channel=nest group=flag] ++$ post-concern [=message-key channel=nest:c group=flag:c] ++$ reply-concern [=message-key target=message-key channel=nest:c group=flag:c] +$ whom $% [%ship p=ship] - [%club p=id:club] + [%club p=@uvH] == +$ message-key [id=(pair ship time) =time] -+$ content - :: same as content of actual message - ~ +$ action $% [%add =event] [%read =index =read-action] From b41dbd6eefbcefa5b717c5c59e534d0b8be50d16 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Tue, 20 Feb 2024 15:26:33 -0500 Subject: [PATCH 022/182] Fix some compiler errors --- desk/app/activity.hoon | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 3e25d3136b..3479f7fcd6 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -253,11 +253,14 @@ ?~ stream :+ newest count %+ turn ~(val by threads) - |=([parent=time oldest-unread=time count=@ud] [oldest-unread count]) - =/ [[@ =event:a] rest=stream:a] (pop:eon stream) + |= [oldest-unread=time count=@ud] + [oldest-unread count] + =/ [[@ =event:a] rest=stream:a] (pop:eon:a stream) =? threads =(-.event %reply) :: TODO confirm that using time.message-key here is right - =/ old (~(gut by threads) target.event [time.message-key.event 0]) - (~(put by threads) target.event [oldest-unread.old +(count.old)]) + ?> ?=([%reply *] event) + =/ old (~(gut by threads) time.target.event [oldest-unread=time.message-key.event count=0]) + (~(put by threads) time.target.event [oldest-unread.old +(count.old)]) + ?> %.y :: assert that it's one with a message key (post or dm-post) $(newest time.message-key.event, count +(count), stream rest) -- From 13d776ebf1e2c91d8f29c7c3afa6a519c9288500 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 20 Feb 2024 15:35:45 -0600 Subject: [PATCH 023/182] subscriber: make sure we still pass leave in no sub case --- desk/lib/subscriber.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/lib/subscriber.hoon b/desk/lib/subscriber.hoon index 62c3737fe7..51c89583ae 100644 --- a/desk/lib/subscriber.hoon +++ b/desk/lib/subscriber.hoon @@ -37,7 +37,7 @@ =/ leave [%pass wire %agent dock %leave ~] =/ sub (~(get by subs) wire) ?~ sub - ((slog 'No such subscription' >[wire]< ~) [~ subs]) + ((slog 'No such subscription' >[wire]< ~) [~[leave] subs]) ~? verb ['cancelling' wire] :_ (~(del by subs) wire) :~ [%pass (weld /~/retry wire) %arvo %b %rest fires-at.u.sub] From 53b90623ad3c2054733489f9bb8392aec58004ae Mon Sep 17 00:00:00 2001 From: fang Date: Tue, 20 Feb 2024 23:27:15 +0100 Subject: [PATCH 024/182] wip: get activity closer to compiling We also implement logic for finding the "floor", timestamp of oldest unread activity, for a certain category (everything, or a specific thread). (The reader should also note that the struggle with "time id" types continues...) --- desk/app/activity.hoon | 148 +++++++++++++++++++++++++++++------------ desk/sur/activity.hoon | 24 ++++--- 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index 3479f7fcd6..d894a540d1 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -105,11 +105,11 @@ =+ !<(=action:a vase) ?- -.action %add - (add action) + (add +.action) %read - (read action) + (read +.action) %adjust - (adjust action) + (adjust +.action) == == :: @@ -142,60 +142,117 @@ :: ++ add :: TODO add to stream & indices, update unreads, and send facts - |= =action:a + |= =event:a ^+ cor + ::TODO make sure to set the reply-floor to be the parent time, for new threads, + :: +find-floor assumes on this + cor +:: +++ find-floor + |= [=index:a mode=$%([%all ~] [%reply parent=one-id:a])] + ^- new-floor=(unit time) + ?. (~(has by indices) index) ~ + :: starting at the last-known first-unread location (floor), walk towards + :: the present, to find the new first-unread location (new floor) + :: + =/ [=orig=stream =reads] + (~(got by indices) index) + ?> |(?=(%all -.mode) (has:eon:a event-parents.reads parent.mode)) + :: slice off the earlier part of the stream, for efficiency + :: + =/ =stream + =; beginning=time + (lot:eon orig-stream `beginning ~) + ?- -.mode + %all floor.reads + %reply reply-floor:(got:eon:a event-parents.reads parent.mode) + == + =| new-floor=(unit time) + |- + ?~ stream new-floor + :: + =^ [=time =event] stream (pop:eon:a stream) + ?: ?& ?=(%reply -.mode) + ?| !?=(%reply -.event) + =(message-key.event parent.mode) + == == + :: we're in reply mode, and it's not a reply event, or a reply to + :: something else, so, skip + :: + $ + =; is-read=? + :: if we found something that's unread, we need look no further + :: + ?. is-read $(stream ~) + :: otherwise, continue our walk towards the present + :: + $(new-floor `time) + ?+ -.event !! + ?(%dm-post %post) + =* id=one-id message-key.event + =/ par=(unit event-parent:a) (get:mep:a event-parents.reads id) + ?~(par | seen.u.par) + :: + %reply + =* id=one-id message-key.event + =/ par=(unit event-parent:a) (get:mep:a event-parents.reads id) + ?~(par | (gte time reply-floor.u.par)) + == +:: +++ update-floor + |= =index:a + ^+ cor + =/ new-floor (find-floor index %all ~) + =? indices ?=(^ new-floor) + %+ ~(jab by indices) index + |= [=stream =reads] + [stream reads(floor u.new-floor)] cor :: ++ read - |= =action:a + |= [=index:a action=read-action:a] ^+ cor - ?- -.read-action.action - %last-seen - =/ indy (~(get by indices) index) - ?~ indy cor - =/ new - [stream.indy [time.action events.indy]] - =. indices - (~(put by indices) index new) - =. cor - (give-unreads new) - cor - :: + ?- -.action %thread =/ indy (~(get by indices) index) ?~ indy cor =/ new - (put:mep reads.u.indy time.read-action.action [& time.read-action.action]) + =- u.indy(event-parents.reads -) + %+ put:mep:a event-parents.reads.u.indy + =; new-reply-floor=(unit time) + [id.action [& (fall new-reply-floor id.action)]] + (find-floor index %reply id.action) =. indices (~(put by indices) index new) - =. cor - (give-unreads new) - cor + =. cor (update-floor index) + (give-unreads new) :: %post =/ indy (~(get by indices) index) ?~ indy cor - =/ old-event-parent (~(get by indy) time.read-action.action) + =/ old-event-parent (get:mep:a event-parents.reads.u.indy id.action) ?~ old-event-parent cor =/ new - :- stream.u.indy - (put:mep reads.u.indy time.read-action.action [& reply-floor.old-event.parent]) + =- u.indy(event-parents.reads -) + %+ put:mep:a event-parents.reads.u.indy + [id.action u.old-event-parent(seen &)] =. indices (~(put by indices) index new) - =. cor - (give-unreads new) - cor + =. cor (update-floor index) + (give-unreads new) :: %all =/ indy (~(get by indices) index) ?~ indy cor =/ new - [stream.u.indy [now.bowl ~]] + =/ latest=(unit [=time event:a]) + ::REVIEW is this taking the item from the correct end? lol + (ram:eon:a stream.u.indy) + ?~ latest u.indy + u.indy(reads [time.u.latest ~]) =. indices (~(put by indices) index new) - =. cor - (give-unreads new) - cor + (give-unreads new) == :: ++ give-unreads @@ -213,7 +270,7 @@ ++ summarize-unreads |= [=stream:a =reads:a] ^- unread-summary:a - =. stream (lot:eon stream `floor.reads ~) + =. stream (lot:eon:a stream `floor.reads ~) =/ event-parents event-parents.reads :: for each item in reads :: remove the post from the event stream @@ -221,7 +278,7 @@ :: then call stream-to-unreads |- ?~ event-parents (stream-to-unreads stream) - =/ [[=time =event-parent:a] rest=event-parents:a] (pop:mep reads) + =/ [[=time =event-parent:a] rest=event-parents:a] (pop:mep:a reads) %= $ event-parents rest @@ -245,7 +302,7 @@ ^- unread-summary:a =/ newest *time =/ count 0 - =/ threads=(map time [oldest-unread=time count=@ud]) ~ + =/ threads=(map message-id:a [oldest-unread=time count=@ud]) ~ :: for each event :: update count and newest :: if reply, update thread state @@ -256,11 +313,20 @@ |= [oldest-unread=time count=@ud] [oldest-unread count] =/ [[@ =event:a] rest=stream:a] (pop:eon:a stream) - =? threads =(-.event %reply) - :: TODO confirm that using time.message-key here is right - ?> ?=([%reply *] event) - =/ old (~(gut by threads) time.target.event [oldest-unread=time.message-key.event count=0]) - (~(put by threads) time.target.event [oldest-unread.old +(count.old)]) - ?> %.y :: assert that it's one with a message key (post or dm-post) - $(newest time.message-key.event, count +(count), stream rest) + =. count +(count) + =. newest + ?> ?=(?(%dm-post %post %reply) -.event) + ::REVIEW should we take timestamp of parent post if reply?? + :: (in which case we would need to do (max newest time.mk.e)) + time.message-key.event + =? threads ?=(%reply -.event) + =/ old + %+ ~(gut by threads) id.target.event + [oldest-unread=time.message-key.event count=0] + %+ ~(put by threads) id.target.event + :: we don't need to update the timestamp, because we always process the + :: oldest message first + :: + [oldest-unread.old +(count.old)] + $(stream rest) -- diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 64379b978b..d48f26bebc 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -6,9 +6,12 @@ ++ mep ((on time event-parent) lte) +$ stream ((mop time event) lte) +$ indices (map index [=stream =reads]) -+$ reads [floor=time =event-parents] ++$ reads + $: floor=time :: latest time above which everything is read + =event-parents :: + == +$ event-parent [seen=? reply-floor=time] -+$ event-parents ((mop time event-parent) lte) ++$ event-parents ((mop one-id event-parent) lte) +$ index $% [%channel channel-concern] [%dm dm-concern] @@ -44,17 +47,22 @@ $% [%ship p=ship] [%club p=@uvH] == -+$ message-key [id=(pair ship time) =time] ++$ one-id time ::TODO better name or the Lord so help me ++$ message-id (pair ship one-id) ++$ message-key [id=message-id =time] +$ action $% [%add =event] [%read =index =read-action] [%adjust =flavor =level] == -+$ unread-summary [newest=time count=@ud threads=(list [oldest-unread=time count=@ud])] ++$ unread-summary + $: newest=time + count=@ud + threads=(list [oldest-unread=time count=@ud]) + == +$ read-action - $% [%last-seen =time] - [%thread =time] - [%post =time] - [%all ~] + $% [%thread id=one-id] :: mark a whole thread as read + [%post id=one-id] :: mark an individual post as read + [%all ~] :: mark _everything_ as read == -- From 91e281d786716869149b4564877c8f9ab030bf1c Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 06:25:06 -0600 Subject: [PATCH 025/182] chat: make sure data is up to date when we have unreads Fixes LAND-1556. getQueryData was always returning undefined in my testing locally, so rather than only refetching if we have cached data for a query, we just fetch the query if we have an unread for the channel (since we know we *will* use that data later). I also noticed that channelKey in state/channel/keys.ts was using 'channel' rather than 'channels'. 'channel' isn't used as a queryKey anywhere afaict, I assumed this was supposed to be 'channels', so I changed that. --- apps/tlon-web/src/state/bootstrap.ts | 22 ++++++--- apps/tlon-web/src/state/channel/channel.ts | 52 ++++++++++++---------- apps/tlon-web/src/state/channel/keys.ts | 2 +- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/apps/tlon-web/src/state/bootstrap.ts b/apps/tlon-web/src/state/bootstrap.ts index fdf28513e1..85bea123b7 100644 --- a/apps/tlon-web/src/state/bootstrap.ts +++ b/apps/tlon-web/src/state/bootstrap.ts @@ -7,6 +7,7 @@ import { asyncWithDefault } from '@/logic/utils'; import queryClient from '@/queryClient'; import { GroupsInit } from '@/types/ui'; +import { infinitePostQueryFn } from './channel/channel'; import { ChannnelKeys } from './channel/keys'; import { initializeChat } from './chat'; import useContactState from './contact'; @@ -52,13 +53,24 @@ async function startGroups() { queryClient.setQueryData(pinsKey(), pins); initializeChat(chat); - // if we have unreads for cached channels, refetch them - // in advance - Object.keys(unreads || {}).forEach((nest) => { + // if we have unreads fetch them in advance + Object.keys(unreads || {}).forEach(async (nest) => { const unread = unreads[nest]; const queryKey = ChannnelKeys.infinitePostsKey(nest); - if (unread.count > 0 && queryClient.getQueryData(queryKey)) { - queryClient.refetchQueries(queryKey); + + if (unread.count) { + try { + // we don't care about the result, just that it's fetched + // we do this because we may not have cached data for the channel + // (getQueryData was always returning undefined afaict) + await queryClient.fetchInfiniteQuery( + queryKey, + infinitePostQueryFn(nest) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } } }); diff --git a/apps/tlon-web/src/state/channel/channel.ts b/apps/tlon-web/src/state/channel/channel.ts index 1483a5d794..8965f03e5e 100644 --- a/apps/tlon-web/src/state/channel/channel.ts +++ b/apps/tlon-web/src/state/channel/channel.ts @@ -588,6 +588,33 @@ type PageParam = null | { direction: string; }; +export const infinitePostQueryFn = + (nest: Nest, initialTime?: string) => + async ({ pageParam }: { pageParam?: PageParam }) => { + let path = ''; + + if (pageParam) { + const { time, direction } = pageParam; + const ud = decToUd(time); + path = `/${nest}/posts/${direction}/${ud}/${POST_PAGE_SIZE}/outline`; + } else if (initialTime) { + path = `/${nest}/posts/around/${decToUd(initialTime)}/${ + POST_PAGE_SIZE / 2 + }/outline`; + } else { + path = `/${nest}/posts/newest/${POST_PAGE_SIZE}/outline`; + } + + const response = await api.scry({ + app: 'channels', + path, + }); + + return { + ...response, + }; + }; + export function useInfinitePosts(nest: Nest, initialTime?: string) { const [han, flag] = nestToFlag(nest); const queryKey = useMemo(() => [han, 'posts', flag, 'infinite'], [han, flag]); @@ -622,30 +649,7 @@ export function useInfinitePosts(nest: Nest, initialTime?: string) { const { data, ...rest } = useInfiniteQuery({ queryKey, - queryFn: async ({ pageParam }: { pageParam?: PageParam }) => { - let path = ''; - - if (pageParam) { - const { time, direction } = pageParam; - const ud = decToUd(time); - path = `/${nest}/posts/${direction}/${ud}/${POST_PAGE_SIZE}/outline`; - } else if (initialTime) { - path = `/${nest}/posts/around/${decToUd(initialTime)}/${ - POST_PAGE_SIZE / 2 - }/outline`; - } else { - path = `/${nest}/posts/newest/${POST_PAGE_SIZE}/outline`; - } - - const response = await api.scry({ - app: 'channels', - path, - }); - - return { - ...response, - }; - }, + queryFn: infinitePostQueryFn(nest, initialTime), getNextPageParam: (lastPage): PageParam | undefined => { const { older } = lastPage; diff --git a/apps/tlon-web/src/state/channel/keys.ts b/apps/tlon-web/src/state/channel/keys.ts index c4f5220875..fe5dc6055b 100644 --- a/apps/tlon-web/src/state/channel/keys.ts +++ b/apps/tlon-web/src/state/channel/keys.ts @@ -1,6 +1,6 @@ import { nestToFlag } from '@/logic/utils'; -export const channelKey = (...parts: string[]) => ['channel', ...parts]; +export const channelKey = (...parts: string[]) => ['channels', ...parts]; export const infinitePostsKey = (nest: string) => { const [han, flag] = nestToFlag(nest); From d4c38cf449c27154d9d22138b9f7be20d1b6275b Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 09:04:23 -0600 Subject: [PATCH 026/182] chat: make sure dm/club data is up to date when we have unreads Similar changes to commit 91e281d, but for dms/clubs. --- apps/tlon-web/src/state/bootstrap.ts | 27 ++++++++++++- apps/tlon-web/src/state/chat/chat.ts | 60 +++++++++++++++------------- apps/tlon-web/src/state/chat/keys.ts | 3 ++ 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/apps/tlon-web/src/state/bootstrap.ts b/apps/tlon-web/src/state/bootstrap.ts index 85bea123b7..37dd99f47c 100644 --- a/apps/tlon-web/src/state/bootstrap.ts +++ b/apps/tlon-web/src/state/bootstrap.ts @@ -3,13 +3,14 @@ import _ from 'lodash'; import api from '@/api'; import { useChatStore } from '@/chat/useChatStore'; -import { asyncWithDefault } from '@/logic/utils'; +import { asyncWithDefault, whomIsDm } from '@/logic/utils'; import queryClient from '@/queryClient'; import { GroupsInit } from '@/types/ui'; import { infinitePostQueryFn } from './channel/channel'; import { ChannnelKeys } from './channel/keys'; -import { initializeChat } from './chat'; +import { infiniteDMsQueryFn, initializeChat } from './chat'; +import ChatQueryKeys from './chat/keys'; import useContactState from './contact'; import useDocketState from './docket'; import useKilnState from './kiln'; @@ -52,6 +53,7 @@ async function startGroups() { queryClient.setQueryData(['unreads'], unreads); queryClient.setQueryData(pinsKey(), pins); initializeChat(chat); + const { unreads: chatUnreads } = chat; // if we have unreads fetch them in advance Object.keys(unreads || {}).forEach(async (nest) => { @@ -74,6 +76,27 @@ async function startGroups() { } }); + Object.keys(chatUnreads).forEach(async (whom) => { + const unread = chatUnreads[whom]; + const isDM = whomIsDm(whom); + const type = isDM ? 'dm' : 'club'; + const queryKey = ChatQueryKeys.infiniteDmsKey(whom); + + if (unread.count) { + try { + // we don't care about the result, just that it's fetched + // we do this because we may not have cached data for the chat + await queryClient.fetchInfiniteQuery( + queryKey, + infiniteDMsQueryFn(whom, type) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + }); + // make sure we remove the app part from the nest before handing it over useChatStore .getState() diff --git a/apps/tlon-web/src/state/chat/chat.ts b/apps/tlon-web/src/state/chat/chat.ts index 68a8b0fc00..5b39d16887 100644 --- a/apps/tlon-web/src/state/chat/chat.ts +++ b/apps/tlon-web/src/state/chat/chat.ts @@ -1239,6 +1239,37 @@ export function checkWritsForDeliveries(writs: Writs) { }); } +export const infiniteDMsQueryFn = + (whom: string, type: 'dm' | 'club', initialTime?: string) => + async ({ pageParam }: { pageParam?: PageParam }) => { + let path = ''; + + if (pageParam) { + const { time, direction } = pageParam; + const ud = decToUd(time.toString()); + path = `/${type}/${whom}/writs/${direction}/${ud}/${CHAT_PAGE_SIZE}/light`; + } else if (initialTime) { + path = `/${type}/${whom}/writs/around/${decToUd(initialTime)}/${ + CHAT_PAGE_SIZE / 2 + }/light`; + } else { + path = `/${type}/${whom}/writs/newest/${CHAT_PAGE_SIZE}/light`; + } + + const response = await api.scry({ + app: 'chat', + path, + }); + + if (response.writs && useWritsStore.getState().hasSomeUndelivered()) { + checkWritsForDeliveries(response.writs); + } + + return { + ...response, + }; + }; + export function useInfiniteDMs(whom: string, initialTime?: string) { const unread = useDmUnread(whom); const isDM = useMemo(() => whomIsDm(whom), [whom]); @@ -1279,34 +1310,7 @@ export function useInfiniteDMs(whom: string, initialTime?: string) { const { data, ...rest } = useInfiniteQuery({ queryKey, - queryFn: async ({ pageParam }: { pageParam?: PageParam }) => { - let path = ''; - - if (pageParam) { - const { time, direction } = pageParam; - const ud = decToUd(time.toString()); - path = `/${type}/${whom}/writs/${direction}/${ud}/${CHAT_PAGE_SIZE}/light`; - } else if (initialTime) { - path = `/${type}/${whom}/writs/around/${decToUd(initialTime)}/${ - CHAT_PAGE_SIZE / 2 - }/light`; - } else { - path = `/${type}/${whom}/writs/newest/${CHAT_PAGE_SIZE}/light`; - } - - const response = await api.scry({ - app: 'chat', - path, - }); - - if (response.writs && useWritsStore.getState().hasSomeUndelivered()) { - checkWritsForDeliveries(response.writs); - } - - return { - ...response, - }; - }, + queryFn: infiniteDMsQueryFn(whom, type, initialTime), getNextPageParam: (lastPage): PageParam | undefined => { const { older } = lastPage; diff --git a/apps/tlon-web/src/state/chat/keys.ts b/apps/tlon-web/src/state/chat/keys.ts index 3c8e9b2589..f701a21e5b 100644 --- a/apps/tlon-web/src/state/chat/keys.ts +++ b/apps/tlon-web/src/state/chat/keys.ts @@ -1,9 +1,12 @@ const pending = () => ['dms', 'pending']; const unreads = () => ['dms', 'unreads']; +const infiniteDmsKey = (whom: string) => ['dms', whom, 'infinite']; + const ChatQueryKeys = { pending, unreads, + infiniteDmsKey, }; export default ChatQueryKeys; From 903cefa288c3b578bb621b24d429feab1ceb7583 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 09:25:51 -0600 Subject: [PATCH 027/182] channels: redirect on bad id in thread URL Fixes LAND-1450 Not clear where the bad link was generated, but this is where the error was occurring. --- .../tlon-web/src/chat/ChatThread/ChatThread.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx index 4cbdb9a07b..6d410754df 100644 --- a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx +++ b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx @@ -66,7 +66,8 @@ export default function ChatThread() { const { isDragging, isOver } = useDragAndDrop(dropZoneId); const { post: note, isLoading } = usePost(nest, idTime!); const { replies } = note.seal; - if (replies !== null) { + const idTimeIsNumber = !Number.isNaN(Number(idTime)); + if (replies !== null && idTimeIsNumber) { replies.unshift([ bigInt(idTime!), { @@ -119,6 +120,14 @@ export default function ChatThread() { [chName, chShip, name, ship, idTime, activeTab] ); + const returnURLWithoutMsg = useCallback( + () => + `${ + activeTab === 'messages' ? '/dm' : '' + }/groups/${ship}/${name}/channels/chat/${chShip}/${chName}`, + [chName, chShip, name, ship, activeTab] + ); + const onAtBottom = useCallback(() => { const { bottom, delayedRead } = useChatStore.getState(); bottom(true); @@ -155,6 +164,12 @@ export default function ChatThread() { }; }, []); + useEffect(() => { + if (replies == null || !idTimeIsNumber) { + navigate(returnURLWithoutMsg()); + } + }, [replies, idTimeIsNumber, navigate, returnURLWithoutMsg]); + const BackButton = isMobile ? Link : 'div'; return ( From ffde0669ed20d1fc1c138342d13e53b59dc5a6af Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 09:28:38 -0600 Subject: [PATCH 028/182] fix useEffect for redirect, only check if idTimeIsNumber --- apps/tlon-web/src/chat/ChatThread/ChatThread.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx index 6d410754df..0f6f0ca983 100644 --- a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx +++ b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx @@ -165,7 +165,7 @@ export default function ChatThread() { }, []); useEffect(() => { - if (replies == null || !idTimeIsNumber) { + if (!idTimeIsNumber) { navigate(returnURLWithoutMsg()); } }, [replies, idTimeIsNumber, navigate, returnURLWithoutMsg]); From aa1d8154f356b7b4e81080d735aec60c37048541 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 09:45:56 -0600 Subject: [PATCH 029/182] web: update readme and development docs, fixes LAND-1578 --- DEVELOPMENT.md | 12 ++++++------ apps/tlon-web/README.md | 34 +++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0c4124d8ff..32accb8725 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,16 +1,16 @@ # Development -These instructions are for working on Groups as a developer at Tlon. +These instructions are for working on the Tlon app as a developer at Tlon. ## Project Structure -- `/desk`: The folder containing the desk for Groups. This currently contains the agents for all other apps. +- `/desk`: The folder containing the desk for %groups. This currently contains the agents necessary for the Tlon app. -- `/apps/tlon-web`: Groups is built primarily using [React], [Typescript], and [Tailwind CSS]. [Vite] ensures that all code and assets are loaded appropriately, bundles the application for distribution and provides a functional dev environment. +- `/apps/tlon-web`: Tlon is built primarily using [React], [Typescript], and [Tailwind CSS]. [Vite] ensures that all code and assets are loaded appropriately, bundles the application for distribution and provides a functional dev environment. ## Getting Started -To get started using Groups, run `npm install` in the root directory. +To get started working on Tlon, run `npm install` in the root directory. To develop, you'll need a running ship to point to. To do so you first need to add a `.env.local` file to the `apps/tlon-web` directory. This file will not be committed. Adding `VITE_SHIP_URL={URL}` (where **{URL}** is the URL of the ship you would like to point to) will allow you to run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, which lets you work with live data. @@ -59,13 +59,13 @@ Since %groups has already been released and is now in the pill. It is very unlik 7. Commit and install landscape on local `~zod`: 1. `|commit %landscape` 2. `|install our %landscape` -8. Similarly commit and install Groups: +8. Similarly commit and install Tlon: 1. `|commit %groups` 2. `|install our %groups` ## Deploying -Groups is distributed via the Urbit network by way of a [glob](https://developers.urbit.org/reference/additional/dist/glob#-make-glob), or a Landscape application bundle. Instructions are as follows: +Tlon is distributed via the Urbit network by way of a [glob](https://developers.urbit.org/reference/additional/dist/glob#-make-glob), or a Landscape application bundle. Instructions are as follows: 1. Run `npm run build` in the `apps/tlon-web` directory, which outputs to `/dist`. 2. Create or launch an urbit using the -F flag. diff --git a/apps/tlon-web/README.md b/apps/tlon-web/README.md index 020b3faccd..e281a47173 100644 --- a/apps/tlon-web/README.md +++ b/apps/tlon-web/README.md @@ -1,20 +1,44 @@ # homestead/ui -## Preview - -https://tlon-homestead.vercel.app/ - ## Development Requires a local fake ship with homestead desk installed ([guide](./DEVELOPMENT.md)) +From the root directory of `landscape-apps`, run: + +``` +npm install +``` + +If you need to specify a local dev ship on a URL other than `http://localhost:8080`, make a `.env.local` file in the `tlon-web` directory. + +It should look like this: + +``` +VITE_SHIP_URL=http://localhost # (or whatever url you need, including livenet ships) +VITE_SHIP_URL2=http://localhost # (secondary ship if you intend to run two dev servers concurrently) +``` + +and then, either: + +``` +npm run dev:web +``` + +or +``` +npm run dev --prefix ./apps/tlon-web +``` + +Or, from the `tlon-web` directory: + ``` npm run dev ``` ## Mock -Uses fake data to show a UI mockup +Uses fake data to show a UI mockup, run from the `tlon-web` directory ``` npm run mock From ce9ea9d4d1678bc008f31d7c176808d73286c933 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 12:26:39 -0600 Subject: [PATCH 030/182] s3: prevent attempt to upload to insecure s3 endpoint from secure context Fixes LAND-1592 --- apps/tlon-web/src/state/storage/upload.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/tlon-web/src/state/storage/upload.ts b/apps/tlon-web/src/state/storage/upload.ts index 6d84ccaf07..b9826d9ff7 100644 --- a/apps/tlon-web/src/state/storage/upload.ts +++ b/apps/tlon-web/src/state/storage/upload.ts @@ -254,8 +254,11 @@ export const useFileStore = create((set, get) => ({ ACL: 'public-read', }); + const { isSecureContext } = window; + const s3Url = new URL(config.publicUrlBase); + const url = config.publicUrlBase - ? new URL(key, config.publicUrlBase).toString() + ? s3Url.toString() : await getSignedUrl(client, command) .then((res) => res.split('?')[0]) .catch((e) => { @@ -263,6 +266,20 @@ export const useFileStore = create((set, get) => ({ return ''; }); + if ( + isSecureContext && + config.publicUrlBase && + s3Url.protocol !== 'https:' + ) { + updateStatus( + uploader, + key, + 'error', + `S3 upload error: Attempting to upload to non-secure S3 endpoint from secure (https) context.` + ); + return; + } + client .send(command) .then(() => { From d34df6fc0fc0b62f2a2ab121e67024e00ef3478b Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 21 Feb 2024 12:53:26 -0600 Subject: [PATCH 031/182] activity: show contact alias for ship name in notification content Fixes LAND-1544 by ensuring that we show the alias for a ship name (if we have it) in the notification content in the activity view. --- apps/tlon-web/src/notifications/Notification.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/tlon-web/src/notifications/Notification.tsx b/apps/tlon-web/src/notifications/Notification.tsx index 1fe4f0ad62..3f7ada9537 100644 --- a/apps/tlon-web/src/notifications/Notification.tsx +++ b/apps/tlon-web/src/notifications/Notification.tsx @@ -62,7 +62,10 @@ function NotificationContent({ key={`${s}-${index}`} className="mr-1 inline-block rounded bg-blue-soft px-1.5 py-0 text-blue mix-blend-multiply dark:mix-blend-normal" > - + ) : ( {s} From 75fd71b0cf60b508df3e3272edeff43ad456cee5 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Wed, 21 Feb 2024 14:18:17 -0500 Subject: [PATCH 032/182] Compiler errors --- desk/app/activity.hoon | 71 +++++++++++++++++++++--------------------- desk/sur/activity.hoon | 14 +++++---- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index d894a540d1..f0d3d1d7e4 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -17,11 +17,11 @@ =| state-0 =* state - :: +%- agent:dbug +%+ verb | ^- agent:gall :: =< - %+ verb | - %- agent:dbug |_ =bowl:gall +* this . def ~(. (default-agent this %|) bowl) @@ -50,16 +50,13 @@ :: ++ on-watch |= =path - ^- (qup card _this) + ^- (quip card _this) =^ cards state abet:(watch:cor path) [cards this] :: - ++ on-arvo - |= [=wire =sign-arvo] - ^- (quip card _this) - `this - :: + ++ on-arvo on-arvo:def + ++ on-agent on-agent:def ++ on-peek peek:cor ++ on-leave on-leave:def ++ on-fail on-fail:def @@ -76,6 +73,7 @@ ^+ cor =. volume %- malt + ^- (list [flavor:a level:a]) :~ [%dm-invite %notify] [%dm-post %notify] [%dm-post-mention %notify] @@ -118,8 +116,8 @@ ^+ cor ?+ pole ~|(bat-watch-path+pole !!) ~ ?>(from-self cor) - %notifications ?>(from-self cor) - %unreads ?>(from-self cor) + [%notifications ~] ?>(from-self cor) + [%unreads ~] ?>(from-self cor) == :: ++ peek @@ -131,11 +129,11 @@ [%x %all ~] ``activity-stream+!>((tap:eon:a stream)) [%x %all start=@ count=@ ~] - ``activity-stream+!>((scag count (top:emp:a stream start))) + ``activity-stream+!>((scag count.pole (top:emp:a stream start.pole))) [%u %event id=@] - ``loob+!>((has:eon:a (slav %da id.pole))) + ``loob+!>((has:eon:a stream (slav %da id.pole))) [%x %event id=@] - ``activity-event+!>((got:eon:a (slav %da id.pole))) + ``activity-event+!>((got:eon:a stream (slav %da id.pole))) [%x %unreads ~] ``activity-unreads+!>((~(run by indices) summarize-unreads)) == @@ -149,52 +147,52 @@ cor :: ++ find-floor - |= [=index:a mode=$%([%all ~] [%reply parent=one-id:a])] - ^- new-floor=(unit time) + |= [=index:a mode=$%([%all ~] [%reply parent=timid:a])] + ^- (unit time) ?. (~(has by indices) index) ~ :: starting at the last-known first-unread location (floor), walk towards :: the present, to find the new first-unread location (new floor) :: - =/ [=orig=stream =reads] + =/ [orig=stream:a =reads:a] (~(got by indices) index) - ?> |(?=(%all -.mode) (has:eon:a event-parents.reads parent.mode)) + ?> |(?=(%all -.mode) (has:mep:a event-parents.reads parent.mode)) :: slice off the earlier part of the stream, for efficiency :: - =/ =stream + =/ =stream:a =; beginning=time - (lot:eon orig-stream `beginning ~) + (lot:eon:a orig `beginning ~) ?- -.mode %all floor.reads - %reply reply-floor:(got:eon:a event-parents.reads parent.mode) + %reply reply-floor:(got:mep:a event-parents.reads parent.mode) == =| new-floor=(unit time) |- ?~ stream new-floor :: - =^ [=time =event] stream (pop:eon:a stream) + =/ [[=time =event:a] rest=stream:a] (pop:eon:a stream) ?: ?& ?=(%reply -.mode) ?| !?=(%reply -.event) - =(message-key.event parent.mode) + ?&(?=(?(%dm-post %post) -.event) =(message-key.event parent.mode)) == == :: we're in reply mode, and it's not a reply event, or a reply to :: something else, so, skip :: - $ + $(stream rest) =; is-read=? :: if we found something that's unread, we need look no further :: ?. is-read $(stream ~) :: otherwise, continue our walk towards the present :: - $(new-floor `time) + $(new-floor `time, stream rest) ?+ -.event !! ?(%dm-post %post) - =* id=one-id message-key.event + =* id=timid:a q.id.message-key.event =/ par=(unit event-parent:a) (get:mep:a event-parents.reads id) ?~(par | seen.u.par) :: %reply - =* id=one-id message-key.event + =* id=timid:a q.id.message-key.event =/ par=(unit event-parent:a) (get:mep:a event-parents.reads id) ?~(par | (gte time reply-floor.u.par)) == @@ -202,10 +200,10 @@ ++ update-floor |= =index:a ^+ cor - =/ new-floor (find-floor index %all ~) + =/ new-floor=(unit time) (find-floor index %all ~) =? indices ?=(^ new-floor) %+ ~(jab by indices) index - |= [=stream =reads] + |= [=stream:a =reads:a] [stream reads(floor u.new-floor)] cor :: @@ -278,24 +276,25 @@ :: then call stream-to-unreads |- ?~ event-parents (stream-to-unreads stream) - =/ [[=time =event-parent:a] rest=event-parents:a] (pop:mep:a reads) + =/ [[=time =event-parent:a] rest=event-parents:a] (pop:mep:a event-parents) %= $ event-parents rest :: stream - %^ (dip:eon @) stream + =- +.- + %^ (dip:eon:a @) stream ~ - |= [* =event:a] + |= [@ key=@da =event:a] ^- [(unit event:a) ? @] ?> ?=(?(%post %reply %dm-post) -.event) ?: &(seen.event-parent =(time time.message-key.event)) - [~ ~ ~] + [~ | ~] ?. =(-.event %reply) - [`event ~ ~] - ?: (lth time.message-key.event reply-floor) - [~ ~ ~] - [`event ~ ~] + [`event | ~] + ?: (lth time.message-key.event reply-floor.event-parent) + [~ | ~] + [`event | ~] == ++ stream-to-unreads |= stream:a diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index d48f26bebc..4ad572e8f9 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -11,7 +11,7 @@ =event-parents :: == +$ event-parent [seen=? reply-floor=time] -+$ event-parents ((mop one-id event-parent) lte) ++$ event-parents ((mop timid event-parent) lte) +$ index $% [%channel channel-concern] [%dm dm-concern] @@ -20,11 +20,13 @@ +$ flavor $? %dm-invite %dm-post + %dm-post-mention %kick %join %post %post-mention %reply + %reply-mention %flag == +$ level ?(%notify %default %trivial) @@ -47,8 +49,8 @@ $% [%ship p=ship] [%club p=@uvH] == -+$ one-id time ::TODO better name or the Lord so help me -+$ message-id (pair ship one-id) ++$ timid time ++$ message-id (pair ship timid) +$ message-key [id=message-id =time] +$ action $% [%add =event] @@ -61,8 +63,8 @@ threads=(list [oldest-unread=time count=@ud]) == +$ read-action - $% [%thread id=one-id] :: mark a whole thread as read - [%post id=one-id] :: mark an individual post as read - [%all ~] :: mark _everything_ as read + $% [%thread id=timid] :: mark a whole thread as read + [%post id=timid] :: mark an individual post as read + [%all ~] :: mark _everything_ as read == -- From 98930590a5d936baefc5fcc8c6d35d068322e120 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 21 Feb 2024 19:18:56 +0000 Subject: [PATCH 033/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 419cc54feb..5d2dcb14a4 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.ne9pu.6t09k.04g6p.vhedi.q00gm.glob' 0v7.ne9pu.6t09k.04g6p.vhedi.q00gm] + glob-http+['https://bootstrap.urbit.org/glob-0v5.ne6nm.kmonk.pdijp.ci1p7.bgmcn.glob' 0v5.ne6nm.kmonk.pdijp.ci1p7.bgmcn] base+'groups' version+[5 5 1] website+'https://tlon.io' From 9d39b7f1a52fc85113cc602b9bc828eb318942c8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 21 Feb 2024 19:24:46 +0000 Subject: [PATCH 034/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 5d2dcb14a4..34d51d6049 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v5.ne6nm.kmonk.pdijp.ci1p7.bgmcn.glob' 0v5.ne6nm.kmonk.pdijp.ci1p7.bgmcn] + glob-http+['https://bootstrap.urbit.org/glob-0v3pi00.sems6.h3c4b.iloon.bc3ob.glob' 0v3pi00.sems6.h3c4b.iloon.bc3ob] base+'groups' version+[5 5 1] website+'https://tlon.io' From 46313daa6bd92be3ab4131f7f1daa5a8470c9964 Mon Sep 17 00:00:00 2001 From: fang Date: Wed, 21 Feb 2024 20:40:08 +0100 Subject: [PATCH 035/182] channels: support non-knot text in searches On the backend, we were taking the search query directly out of the path. Assuming all paths are well-formed, this meant we had no way of receiving text that didn't fit into the path's @ta (aka knot) character limitations. Here, we first try to decode the query part of the search scry path as a knot-encoded string. Only if that fails do we take the path element as-is. (That way, we maintain backwards compatibility, and continue supporting trivial search cases trivially.) On the frontend, we were injecting the search query into the scry url directly, even if it contained non-url-safe characters. Something further down the stack would url-encode the url string, but the backend wasn't prepared to handle this (and shouldn't have been). Here, we add a "stringToTa()" utility for knot-encoding strings, and make sure to do that to the query before putting it into the url. With the combination of these, we now support arbitrary text (including unicode!) in the search. --- apps/tlon-web/src/logic/utils.ts | 46 ++++++++++++++++++++++ apps/tlon-web/src/state/channel/channel.ts | 5 ++- apps/tlon-web/src/state/chat/search.ts | 5 ++- desk/app/channels.hoon | 2 +- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/tlon-web/src/logic/utils.ts b/apps/tlon-web/src/logic/utils.ts index 52ad3c533e..818ef2d2b8 100644 --- a/apps/tlon-web/src/logic/utils.ts +++ b/apps/tlon-web/src/logic/utils.ts @@ -151,6 +151,52 @@ export function strToSym(str: string): string { return ascii.toLowerCase().replaceAll(/[^a-zA-Z0-9-]/g, '-'); } +// encode the string into @ta-safe format, using logic from +wood. +// for example, 'some Chars!' becomes '~.some.~43.hars~21.' +// this is equivalent to (scot %t string), and is url-safe encoding for +// arbitrary strings. +// +// TODO should probably go into aura-js +export function stringToTa(string: string) { + let out = ''; + for (let i = 0; i < string.length; i += 1) { + const char = string[i]; + let add = ''; + switch (char) { + case ' ': + add = '.'; + break; + case '.': + add = '~.'; + break; + case '~': + add = '~~'; + break; + default: { + const codePoint = string.codePointAt(i); + if (!codePoint) break; + // js strings are encoded in UTF-16, so 16 bits per character. + // codePointAt() reads a _codepoint_ at a character index, and may + // consume up to two js string characters to do so, in the case of + // 16 bit surrogate pseudo-characters. here we detect that case, so + // we can advance the cursor to skip past the additional character. + if (codePoint > 0xFFFF) i += 1; + if ( + (codePoint >= 97 && codePoint <= 122) || // a-z + (codePoint >= 48 && codePoint <= 57) || // 0-9 + char === '-' + ) { + add = char; + } else { + add = `~${codePoint.toString(16)}.`; + } + } + } + out += add; + } + return `~~${out}`; +} + export function makePrettyTime(date: Date) { return format(date, 'HH:mm'); } diff --git a/apps/tlon-web/src/state/channel/channel.ts b/apps/tlon-web/src/state/channel/channel.ts index 1483a5d794..930c6f498a 100644 --- a/apps/tlon-web/src/state/channel/channel.ts +++ b/apps/tlon-web/src/state/channel/channel.ts @@ -17,7 +17,7 @@ import { isNativeApp } from '@/logic/native'; import useReactQueryScry from '@/logic/useReactQueryScry'; import useReactQuerySubscribeOnce from '@/logic/useReactQuerySubscribeOnce'; import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; -import { checkNest, log, nestToFlag, whomIsFlag } from '@/logic/utils'; +import { checkNest, log, nestToFlag, whomIsFlag, stringToTa } from '@/logic/utils'; import queryClient from '@/queryClient'; import { Action, @@ -2416,6 +2416,7 @@ export function useDeleteReplyReactMutation() { } export function useChannelSearch(nest: string, query: string) { + const encodedQuery = stringToTa(query); const { data, ...rest } = useInfiniteQuery({ queryKey: ['channel', 'search', nest, query], enabled: query !== '', @@ -2424,7 +2425,7 @@ export function useChannelSearch(nest: string, query: string) { app: 'channels', path: `/${nest}/search/text/${ decToUd(pageParam.toString()) || '0' - }/20/${query}`, + }/20/${encodedQuery}`, }); return res; }, diff --git a/apps/tlon-web/src/state/chat/search.ts b/apps/tlon-web/src/state/chat/search.ts index 53f25a66f7..a04a05f365 100644 --- a/apps/tlon-web/src/state/chat/search.ts +++ b/apps/tlon-web/src/state/chat/search.ts @@ -6,7 +6,7 @@ import create from 'zustand'; import { persist } from 'zustand/middleware'; import api from '@/api'; -import { createStorageKey, whomIsDm, whomIsMultiDm } from '@/logic/utils'; +import { createStorageKey, whomIsDm, whomIsMultiDm, stringToTa } from '@/logic/utils'; import { ChatMap, ReplyTuple, newChatMap } from '@/types/channel'; import { ChatScan, @@ -115,6 +115,7 @@ export function updateSearchHistory( } export function useInfiniteChatSearch(whom: string, query: string) { + const encodedQuery = stringToTa(query); const type = whomIsDm(whom) ? 'dm' : whomIsMultiDm(whom) ? 'club' : 'chat'; const { data, ...rest } = useInfiniteQuery({ enabled: query !== '', @@ -124,7 +125,7 @@ export function useInfiniteChatSearch(whom: string, query: string) { app: 'chat', path: `/${type}/${whom}/search/text/${ decToUd(pageParam.toString()) || '0' - }/20/${query}`, + }/20/${encodedQuery}`, }); return res; }, diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index bd14e4cee0..d79450475b 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -1295,7 +1295,7 @@ %^ text:ca-search (slav %ud skip.pole) (slav %ud count.pole) - nedl.pole + (fall (slaw %t nedl.pole) nedl.pole) :: [%search %mention skip=@ count=@ nedl=@ ~] :^ ~ ~ %channel-scan !> From 9e5357aed5081847b69badcaf2b6ec562c3fb822 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 21 Feb 2024 14:12:33 -0600 Subject: [PATCH 036/182] channels: using subscriber library --- desk/app/channels.hoon | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 8c414c23d2..57eedfe901 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -266,6 +266,12 @@ :: happen. that way, local subs get established without issue. inflate-io :: +++ unsubscribe + |= [=wire =dock] + ^+ cor + =^ caz=(list card) subs + (~(unsubscribe s [subs bowl]) wire dock) + (emil caz) ++ inflate-io :: initiate version negotiation with our own channels-server :: @@ -309,7 +315,7 @@ =(wire path) == ?: keep cor - (emit %pass pole %agent [sub-ship dude] %leave ~) + (unsubscribe pole [sub-ship dude]) :: :: watch all the subscriptions we expect to have :: @@ -607,6 +613,9 @@ |= [=wire =dock =path] |= delay=? ca-core(cor ((^safe-watch wire dock path) delay)) + ++ unsubscribe + |= [=wire =dock] + ca-core(cor (^unsubscribe wire dock)) ++ ca-abet %_ cor v-channels @@ -767,7 +776,7 @@ ~ =/ =path /[kind.nest]/[name.nest]/create =/ =wire (weld ca-area /create) - (emit %pass wire %agent [our.bowl server] %watch path) + ((safe-watch wire [our.bowl server] path) |) :: %kick (ca-safe-sub &) %watch-ack @@ -782,8 +791,7 @@ =+ !<(=update:c q.cage) =. ca-core (ca-u-channels update) =. ca-core ca-give-unread - =. ca-core - (emit %pass (weld ca-area /create) %agent [ship.nest server] %leave ~) + =. ca-core (unsubscribe (weld ca-area /create) [ship.nest server]) (ca-safe-sub |) == :: @@ -852,7 +860,7 @@ =. ca-core (ca-fetch-contacts chk) =. ca-core (ca-sync-backlog |) =/ wire (weld ca-area /checkpoint) - (emit %pass wire %agent [ship.nest server] %leave ~) + (unsubscribe wire [ship.nest server]) :: ++ ca-fetch-contacts |= chk=u-checkpoint:c @@ -905,7 +913,7 @@ |= chk=u-checkpoint:c =. ca-core (ca-apply-checkpoint chk |) =/ wire (weld ca-area /backlog) - (emit %pass wire %agent [ship.nest server] %leave ~) + (unsubscribe wire [ship.nest server]) :: ++ ca-apply-logs |= =log:c @@ -1128,7 +1136,7 @@ =/ =rope:ha (ca-rope -.kind-data.post id-post ~) ?: (was-mentioned:utils content.post our.bowl) ?. (want-hark %mention) - ca-core + ca-core =/ cs=(list content:ha) ~[[%ship author.post] ' mentioned you: ' (flatten:utils content.post)] (emit (pass-hark (ca-spin rope cs ~))) @@ -1620,7 +1628,7 @@ :: leave the subscription only :: ++ ca-simple-leave - (emit %pass ca-sub-wire %agent [ship.nest server] %leave ~) + (unsubscribe ca-sub-wire [ship.nest server]) :: :: Leave the subscription, tell people about it, and delete our local :: state for the channel From e8233e7f3c6fbef5724b9510725d97452cb0a61e Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 21 Feb 2024 14:42:40 -0600 Subject: [PATCH 037/182] subscriber: adding comments clarifying intentions --- desk/app/channels.hoon | 3 +++ desk/app/groups.hoon | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 57eedfe901..571658bd0a 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -5,6 +5,9 @@ :: XX chat thread entries can no longer be edited. maybe fix before :: release? :: +:: note: all subscriptions are handled by the subscriber library so +:: we can have resubscribe loop protection. +:: /- c=channels, g=groups, ha=hark /- meta /+ default-agent, verb, dbug, sparse, neg=negotiate diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index f889939eac..8affdd7860 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -1,3 +1,8 @@ +:: groups: agent for managing group membership, metadata and permissions +:: +:: note: all subscriptions are handled by the subscriber library so +:: we can have resubscribe loop protection. +:: /- g=groups, zero=groups-0, ha=hark, h=heap, d=channels, c=chat, tac=contacts /- meta /- e=epic From 3f06c98fdbe733c6f803f7b23e9544d4b2280353 Mon Sep 17 00:00:00 2001 From: fang Date: Wed, 21 Feb 2024 21:59:53 +0100 Subject: [PATCH 038/182] chat: support knot-encoded search queries Obviously chat implements its own (similar) search logic, so we need to update that to match. (The frontend code was already updated in 46313da.) --- desk/app/chat.hoon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/app/chat.hoon b/desk/app/chat.hoon index ffe2d19408..98beece545 100644 --- a/desk/app/chat.hoon +++ b/desk/app/chat.hoon @@ -1339,7 +1339,7 @@ %^ text:search:cu-pact (slav %ud skip.pole) (slav %ud count.pole) - nedl.pole + (fall (slaw %t nedl.pole) nedl.pole) :: [%search %mention skip=@ count=@ nedl=@ ~] %- some @@ -1712,7 +1712,7 @@ %^ text:search:di-pact (slav %ud skip.pole) (slav %ud count.pole) - nedl.pole + (fall (slaw %t nedl.pole) nedl.pole) :: [%search %mention skip=@ count=@ nedl=@ ~] %- some From e910c27a575886602e280dad510320b25b2f8bc3 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 21 Feb 2024 17:11:29 -0600 Subject: [PATCH 039/182] ops: restore talk canary deploy --- .github/workflows/deploy-canary-talk.yml | 86 ++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/deploy-canary-talk.yml diff --git a/.github/workflows/deploy-canary-talk.yml b/.github/workflows/deploy-canary-talk.yml new file mode 100644 index 0000000000..b5f6da1a43 --- /dev/null +++ b/.github/workflows/deploy-canary-talk.yml @@ -0,0 +1,86 @@ +name: Deploy Talk (canary) +on: + workflow_dispatch: + inputs: + tag: + type: string + required: false + description: Enter the tag to deploy + default: v5.6.0 +env: + tag: ${{ github.event.inputs.tag || 'v5.6.0' }} +jobs: + build-frontend: + runs-on: ubuntu-latest + name: "Build Frontend" + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ env.tag }} + - uses: actions/setup-node@v3 + with: + node-version-file: ./ui/.nvmrc + - working-directory: ./ui + run: | + npm ci + npm run build:chat + - uses: actions/upload-artifact@v3 + with: + name: "ui-dist" + path: ui/dist + glob: + runs-on: ubuntu-latest + name: 'Make a glob' + needs: build-frontend + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ env.tag }} + - uses: actions/download-artifact@v3 + with: + name: 'ui-dist' + path: ui/dist + - id: 'auth' + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GCP_SERVICE_KEY }}' + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v1' + - name: 'glob' + uses: ./.github/actions/glob + with: + folder: 'ui/dist/*' + docket: 'talk/desk.docket-0' + - name: Commit and Push Glob + run: | + git config --global user.name github-actions + git config --global user.email github-actions@github.com + git add talk/desk.docket-0 + git commit -n -m "update glob: ${{ steps.glob.outputs.hash }} [skip actions]" || echo "No changes to commit" + sleep 10 + INPUT=${{ env.tag }} + BRANCH=${INPUT:-"v5.6.0"} + git pull origin $BRANCH --rebase --autostash + git push + deploy: + runs-on: ubuntu-latest + name: "Release to ~binnec-dozzod-marnus (canary)" + needs: glob + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ env.tag }} + - id: 'auth' + uses: 'google-github-actions/auth@v1' + with: + credentials_json: '${{ secrets.GCP_SERVICE_KEY }}' + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v1' + - id: deploy + name: Deploy + run: + ./.github/helpers/deploy.sh tloncorp/landscape-apps talk binnec-dozzod-marnus us-central1-a mainnet-tlon-other-2d ${{ env.tag }} + env: + SSH_SEC_KEY: ${{ secrets.GCP_SSH_SEC_KEY }} + SSH_PUB_KEY: ${{ secrets.GCP_SSH_PUB_KEY }} + URBIT_REPO_TAG: ${{ vars.URBIT_REPO_TAG }} \ No newline at end of file From f18bc469f1a6b25fb5c4734be400ff8726839a2f Mon Sep 17 00:00:00 2001 From: Alec Ananian <1013230+alecananian@users.noreply.github.com> Date: Thu, 22 Feb 2024 07:34:33 -0800 Subject: [PATCH 040/182] mobile: fix display of new groups in list (#3276) --- .../src/components/Sidebar/GroupsSidebarItem.tsx | 1 + apps/tlon-web/src/components/Sidebar/Sidebar.tsx | 2 +- apps/tlon-web/src/nav/MobileRoot.tsx | 13 ++++--------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/tlon-web/src/components/Sidebar/GroupsSidebarItem.tsx b/apps/tlon-web/src/components/Sidebar/GroupsSidebarItem.tsx index 31a1a4e1a9..f1bbb31ed9 100644 --- a/apps/tlon-web/src/components/Sidebar/GroupsSidebarItem.tsx +++ b/apps/tlon-web/src/components/Sidebar/GroupsSidebarItem.tsx @@ -63,6 +63,7 @@ const GroupsSidebarItem = React.memo( /> ) } + className={isNew ? 'pr-10' : undefined} to={`/groups/${flag}`} {...handlers} > diff --git a/apps/tlon-web/src/components/Sidebar/Sidebar.tsx b/apps/tlon-web/src/components/Sidebar/Sidebar.tsx index 78fe2c9007..8b64e64ce3 100644 --- a/apps/tlon-web/src/components/Sidebar/Sidebar.tsx +++ b/apps/tlon-web/src/components/Sidebar/Sidebar.tsx @@ -87,7 +87,7 @@ export default function Sidebar() { Object.keys(invitedGroups).forEach((flag) => { accum.set(flag, 'invited'); }); - Object.keys(newGroups).forEach((flag) => { + newGroups.forEach(([flag]) => { accum.set(flag, 'new'); }); return accum; diff --git a/apps/tlon-web/src/nav/MobileRoot.tsx b/apps/tlon-web/src/nav/MobileRoot.tsx index ffba0d19c6..6c083a7d25 100644 --- a/apps/tlon-web/src/nav/MobileRoot.tsx +++ b/apps/tlon-web/src/nav/MobileRoot.tsx @@ -45,14 +45,6 @@ export default function MobileRoot() { [pinnedGroups] ); - const newGroupsOptions = useMemo( - () => - Object.keys(newGroups).map(([flag]) => ( - - )), - [newGroups] - ); - const hasPinnedGroups = !!pinnedGroupsOptions.length; const hasLoadingGroups = !!loadingGroups.length; const hasGangsWithClaims = !!gangsWithClaims.length; @@ -132,7 +124,10 @@ export default function MobileRoot() { ))} - {hasNewGroups && newGroupsOptions} + {hasNewGroups && + newGroups.map(([flag]) => ( + + ))} {hasPendingGangs && ( From 401bc2c778103bc81a2ca09f714643d4aa7e6fb5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 22 Feb 2024 15:38:46 +0000 Subject: [PATCH 041/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 34d51d6049..b304d50f44 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v3pi00.sems6.h3c4b.iloon.bc3ob.glob' 0v3pi00.sems6.h3c4b.iloon.bc3ob] + glob-http+['https://bootstrap.urbit.org/glob-0v7.ahc2a.qqtl8.6q39i.o4dca.i65jo.glob' 0v7.ahc2a.qqtl8.6q39i.o4dca.i65jo] base+'groups' version+[5 5 1] website+'https://tlon.io' From 2a4ebf636373424e67d9f058b0f30e728c943dc1 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Thu, 22 Feb 2024 16:28:23 -0500 Subject: [PATCH 042/182] %add action --- desk/app/activity.hoon | 83 ++++++++++++++++++++++++++++++++++++++++-- desk/sur/activity.hoon | 16 +++++--- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index f0d3d1d7e4..fb1d9648c6 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -77,6 +77,8 @@ :~ [%dm-invite %notify] [%dm-post %notify] [%dm-post-mention %notify] + [%dm-reply %notify] + [%dm-reply-mention %notify] [%kick %default] [%join %trivial] [%post %trivial] @@ -139,12 +141,85 @@ == :: ++ add - :: TODO add to stream & indices, update unreads, and send facts |= =event:a ^+ cor - ::TODO make sure to set the reply-floor to be the parent time, for new threads, - :: +find-floor assumes on this - cor + =. cor + (give %fact ~[/] activity-event+!>(event)) + =? cor (notifiable event) + (give %fact ~[/notifications] activity-event+!>(event)) + =. stream + (put:eon:a stream now.bowl event) + ?+ -.event cor + %dm-post + =/ index [%dm whom.event] + =/ indy (~(get by indices) index) + ?~ indy cor + =/ new + :* (put:eon:a stream.u.indy now.bowl event) + floor.reads.u.indy + %^ put:mep:a event-parents.reads.u.indy + now.bowl + [| now.bowl] + == + =. indices + (~(put by indices) index new) + cor + %dm-reply + =/ index [%dm whom.event] + =/ indy (~(get by indices) index) + ?~ indy cor + =/ new + :- (put:eon:a stream.u.indy now.bowl event) + reads.u.indy + =. indices + (~(put by indices) index new) + cor + %post + =/ index [%channel channel.event group.event] + =/ indy (~(get by indices) index) + ?~ indy cor + =/ new + :* (put:eon:a stream.u.indy now.bowl event) + floor.reads.u.indy + %^ put:mep:a event-parents.reads.u.indy + now.bowl + [| now.bowl] + == + =. indices + (~(put by indices) index new) + cor + %reply + =/ index [%channel channel.event group.event] + =/ indy (~(get by indices) index) + ?~ indy cor + =/ new + :- (put:eon:a stream.u.indy now.bowl event) + reads.u.indy + =. indices + (~(put by indices) index new) + cor + == +++ notifiable + |= =event:a + .= %notify + (~(gut by volume) (determine-flavor event) %default) +++ determine-flavor + |= =event:a + ^- flavor:a + ?- -.event + %dm-invite %dm-invite + %kick %kick + %join %join + %flag %flag + %post + ?. mention.event %post %post-mention + %reply + ?. mention.event %reply %reply-mention + %dm-post + ?. mention.event %dm-post %dm-post-mention + %dm-reply + ?. mention.event %dm-reply %dm-reply-mention + == :: ++ find-floor |= [=index:a mode=$%([%all ~] [%reply parent=timid:a])] diff --git a/desk/sur/activity.hoon b/desk/sur/activity.hoon index 4ad572e8f9..f6b1306532 100644 --- a/desk/sur/activity.hoon +++ b/desk/sur/activity.hoon @@ -21,6 +21,8 @@ $? %dm-invite %dm-post %dm-post-mention + %dm-reply + %dm-reply-mention %kick %join %post @@ -33,18 +35,20 @@ +$ event $% [%dm-invite dm-concern] [%dm-post dm-post-concern content=story:c mention=?] + [%dm-reply dm-reply-concern content=story:c mention=?] [%kick group-concern =ship] [%join group-concern =ship] [%post post-concern content=story:c mention=?] [%reply reply-concern content=story:c mention=?] [%flag post-concern] == -+$ group-concern group=flag:c -+$ channel-concern [channel=nest:c group=flag:c] -+$ dm-concern =whom -+$ dm-post-concern [=message-key =whom] -+$ post-concern [=message-key channel=nest:c group=flag:c] -+$ reply-concern [=message-key target=message-key channel=nest:c group=flag:c] ++$ group-concern group=flag:c ++$ channel-concern [channel=nest:c group=flag:c] ++$ dm-concern =whom ++$ dm-post-concern [=message-key =whom] ++$ dm-reply-concern [=message-key target=message-key =whom] ++$ post-concern [=message-key channel=nest:c group=flag:c] ++$ reply-concern [=message-key target=message-key channel=nest:c group=flag:c] +$ whom $% [%ship p=ship] [%club p=@uvH] From 7f17b6fa6e06d1d15b1bd3827a774a3fdaffc488 Mon Sep 17 00:00:00 2001 From: ~midsum-salrux Date: Thu, 22 Feb 2024 16:44:47 -0500 Subject: [PATCH 043/182] Handle first event insertion in index --- desk/app/activity.hoon | 56 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/desk/app/activity.hoon b/desk/app/activity.hoon index fb1d9648c6..951a2388e3 100644 --- a/desk/app/activity.hoon +++ b/desk/app/activity.hoon @@ -152,12 +152,13 @@ ?+ -.event cor %dm-post =/ index [%dm whom.event] - =/ indy (~(get by indices) index) - ?~ indy cor + =? indices !(~(has by indices) index) + (~(put by indices) index [*stream:a *reads:a]) + =/ indy (~(got by indices) index) =/ new - :* (put:eon:a stream.u.indy now.bowl event) - floor.reads.u.indy - %^ put:mep:a event-parents.reads.u.indy + :* (put:eon:a stream.indy now.bowl event) + floor.reads.indy + %^ put:mep:a event-parents.reads.indy now.bowl [| now.bowl] == @@ -166,22 +167,24 @@ cor %dm-reply =/ index [%dm whom.event] - =/ indy (~(get by indices) index) - ?~ indy cor + =? indices !(~(has by indices) index) + (~(put by indices) index *[stream:a reads:a]) + =/ indy (~(got by indices) index) =/ new - :- (put:eon:a stream.u.indy now.bowl event) - reads.u.indy + :- (put:eon:a stream.indy now.bowl event) + reads.indy =. indices (~(put by indices) index new) cor %post =/ index [%channel channel.event group.event] - =/ indy (~(get by indices) index) - ?~ indy cor + =? indices !(~(has by indices) index) + (~(put by indices) index *[stream:a reads:a]) + =/ indy (~(got by indices) index) =/ new - :* (put:eon:a stream.u.indy now.bowl event) - floor.reads.u.indy - %^ put:mep:a event-parents.reads.u.indy + :* (put:eon:a stream.indy now.bowl event) + floor.reads.indy + %^ put:mep:a event-parents.reads.indy now.bowl [| now.bowl] == @@ -190,11 +193,12 @@ cor %reply =/ index [%channel channel.event group.event] - =/ indy (~(get by indices) index) - ?~ indy cor + =? indices !(~(has by indices) index) + (~(put by indices) index *[stream:a reads:a]) + =/ indy (~(got by indices) index) =/ new - :- (put:eon:a stream.u.indy now.bowl event) - reads.u.indy + :- (put:eon:a stream.indy now.bowl event) + reads.indy =. indices (~(put by indices) index new) cor @@ -207,18 +211,18 @@ |= =event:a ^- flavor:a ?- -.event - %dm-invite %dm-invite - %kick %kick - %join %join - %flag %flag + %dm-invite %dm-invite + %kick %kick + %join %join + %flag %flag %post - ?. mention.event %post %post-mention + ?: mention.event %post-mention %post %reply - ?. mention.event %reply %reply-mention + ?: mention.event %reply-mention %reply %dm-post - ?. mention.event %dm-post %dm-post-mention + ?: mention.event %dm-post-mention %dm-post %dm-reply - ?. mention.event %dm-reply %dm-reply-mention + ?: mention.event %dm-reply-mention %dm-reply == :: ++ find-floor From 7d7dbac90404bf87df3ec9f09fb60d7362d93c9a Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 22 Feb 2024 16:43:38 -0600 Subject: [PATCH 044/182] channels: make firehose first class --- apps/tlon-web/src/app.tsx | 2 + apps/tlon-web/src/state/channel/channel.ts | 799 +++++++++++---------- apps/tlon-web/src/state/channel/keys.ts | 7 + apps/tlon-web/src/types/channel.ts | 14 +- 4 files changed, 428 insertions(+), 394 deletions(-) diff --git a/apps/tlon-web/src/app.tsx b/apps/tlon-web/src/app.tsx index faf7be079a..a24d8f2d7b 100644 --- a/apps/tlon-web/src/app.tsx +++ b/apps/tlon-web/src/app.tsx @@ -106,6 +106,7 @@ import NewGroupDialog from './groups/NewGroup/NewGroupDialog'; import NewGroupView from './groups/NewGroup/NewGroupView'; import { ChatInputFocusProvider } from './logic/ChatInputFocusContext'; import useAppUpdates, { AppUpdateContext } from './logic/useAppUpdates'; +import { useChannelsFirehose } from './state/channel/channel'; const ReactQueryDevtoolsProduction = React.lazy(() => import('@tanstack/react-query-devtools/build/lib/index.prod.js').then( @@ -575,6 +576,7 @@ function App() { const isMobile = useIsMobile(); const isSmall = useMedia('(max-width: 1023px)'); + useChannelsFirehose(); useEffect(() => { if (isNativeApp()) { postActionToNativeApp('appLoaded'); diff --git a/apps/tlon-web/src/state/channel/channel.ts b/apps/tlon-web/src/state/channel/channel.ts index 8965f03e5e..0966b95653 100644 --- a/apps/tlon-web/src/state/channel/channel.ts +++ b/apps/tlon-web/src/state/channel/channel.ts @@ -3,7 +3,8 @@ import { decToUd, udToDec, unixToDa } from '@urbit/api'; import { Poke } from '@urbit/http-api'; import bigInt from 'big-integer'; import _ from 'lodash'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { e } from 'vite-node/index-6fb787b2'; import create from 'zustand'; import api from '@/api'; @@ -42,6 +43,7 @@ import { PostEssay, Posts, Reply, + ReplyMeta, ReplyTuple, Said, SortMode, @@ -52,7 +54,7 @@ import { } from '@/types/channel'; import { Flag } from '@/types/hark'; -import { channelKey } from './keys'; +import { channelKey, infinitePostsKey, postKey } from './keys'; import shouldAddPostToCache from './util'; const POST_PAGE_SIZE = isNativeApp() @@ -224,11 +226,22 @@ export function usePostsOnHost( return data as Posts; } -const infinitePostUpdater = (queryKey: QueryKey, data: ChannelsResponse) => { +const infinitePostUpdater = ( + prev: PostsInCachePrev | undefined, + data: ChannelsResponse +): PostsInCachePrev | undefined => { const { nest, response } = data; if (!('post' in response)) { - return; + return prev; + } + + const infinitePostQueryKey = infinitePostsKey(nest); + const existingQueryData = queryClient.getQueryData< + PostsInCachePrev | undefined + >(infinitePostQueryKey); + if (!shouldAddPostToCache(existingQueryData)) { + return prev; } const postResponse = response.post['r-post']; @@ -239,347 +252,325 @@ const infinitePostUpdater = (queryKey: QueryKey, data: ChannelsResponse) => { const post = postResponse.set; if (post === null) { - queryClient.setQueryData<{ - pages: PagedPosts[]; - pageParams: PageParam[]; - }>(queryKey, (d: PostsInCachePrev | undefined) => { - if (d === undefined) { - return undefined; - } - - const newPages = d.pages.map((page) => { - const newPage = { - ...page, - }; + if (prev === undefined) { + return prev; + } - const inPage = - Object.keys(newPage.posts).some((k) => k === time) ?? false; + const newPages = prev.pages.map((page) => { + const newPage = { + ...page, + }; - if (inPage) { - const pagePosts = { ...newPage.posts }; + const inPage = + Object.keys(newPage.posts).some((k) => k === time) ?? false; - pagePosts[time] = null; + if (inPage) { + const pagePosts = { ...newPage.posts }; - newPage.posts = pagePosts; - } + pagePosts[time] = null; - return newPage; - }); + newPage.posts = pagePosts; + } - return { - pages: newPages, - pageParams: d.pageParams, - }; + return newPage; }); - } else { - queryClient.setQueryData<{ - pages: PagedPosts[]; - pageParams: PageParam[]; - }>(queryKey, (d: PostsInCachePrev | undefined) => { - if (d === undefined) { - return { - pages: [ - { - posts: { - [time]: post, - }, - newer: null, - older: null, - total: 1, - }, - ], - pageParams: [], - }; - } - const firstPage = _.first(d.pages); + return { + pages: newPages, + pageParams: prev.pageParams, + }; + } + if (prev === undefined) { + return { + pages: [ + { + posts: { + [time]: post, + }, + newer: null, + older: null, + total: 1, + }, + ], + pageParams: [], + }; + } - if (firstPage === undefined) { - return undefined; - } + const firstPage = _.first(prev.pages); - const newPosts = { - ...firstPage.posts, - [time]: post, - }; + if (firstPage === undefined) { + return undefined; + } - const newFirstpage: PagedPosts = { - ...firstPage, - posts: newPosts, - total: firstPage.total + 1, - }; + const newPosts = { + ...firstPage.posts, + [time]: post, + }; - const cachedPost = - firstPage.posts[decToUd(unixToDa(post.essay.sent).toString())]; - - if ( - cachedPost && - id !== udToDec(unixToDa(post.essay.sent).toString()) - ) { - // remove cached post if it exists - delete newFirstpage.posts[ - decToUd(unixToDa(post.essay.sent).toString()) - ]; - - // set delivered now that we have the real post - usePostsStore - .getState() - .updateStatus( - { author: post.essay.author, sent: post.essay.sent }, - 'delivered' - ); - } + const newFirstpage: PagedPosts = { + ...firstPage, + posts: newPosts, + total: firstPage.total + 1, + }; - return { - pages: [newFirstpage, ...d.pages.slice(1, d.pages.length)], - pageParams: d.pageParams, - }; - }); + const cachedPost = + firstPage.posts[decToUd(unixToDa(post.essay.sent).toString())]; + + if (cachedPost && id !== udToDec(unixToDa(post.essay.sent).toString())) { + // remove cached post if it exists + delete newFirstpage.posts[decToUd(unixToDa(post.essay.sent).toString())]; + + // set delivered now that we have the real post + usePostsStore + .getState() + .updateStatus( + { author: post.essay.author, sent: post.essay.sent }, + 'delivered' + ); } - } else if ('reacts' in postResponse) { - queryClient.setQueryData<{ - pages: PagedPosts[]; - pageParams: PageParam[]; - }>( - queryKey, - (d: { pages: PagedPosts[]; pageParams: PageParam[] } | undefined) => { - if (d === undefined) { - return undefined; - } - const { reacts } = postResponse; + return { + pages: [newFirstpage, ...prev.pages.slice(1, prev.pages.length)], + pageParams: prev.pageParams, + }; + } - const newPages = d.pages.map((page) => { - const newPage = { - ...page, - }; + if ('reacts' in postResponse) { + if (prev === undefined) { + return undefined; + } - const inPage = - Object.keys(newPage.posts).some((k) => k === time) ?? false; + const { reacts } = postResponse; - if (inPage) { - const post = newPage.posts[time]; - if (!post) { - return newPage; - } - newPage.posts[time] = { - ...post, - seal: { - ...post.seal, - reacts, - }, - }; + const newPages = prev.pages.map((page) => { + const newPage = { + ...page, + }; - return newPage; - } + const inPage = + Object.keys(newPage.posts).some((k) => k === time) ?? false; + if (inPage) { + const post = newPage.posts[time]; + if (!post) { return newPage; - }); - - return { - pages: newPages, - pageParams: d.pageParams, + } + newPage.posts[time] = { + ...post, + seal: { + ...post.seal, + reacts, + }, }; + + return newPage; } - ); - } else if ('essay' in postResponse) { - queryClient.setQueryData<{ - pages: PagedPosts[]; - pageParams: PageParam[]; - }>( - queryKey, - (d: { pages: PagedPosts[]; pageParams: PageParam[] } | undefined) => { - if (d === undefined) { - return undefined; - } - const { essay } = postResponse; + return newPage; + }); - const newPages = d.pages.map((page) => { - const newPage = { - ...page, - }; + return { + pages: newPages, + pageParams: prev.pageParams, + }; + } - const inPage = - Object.keys(newPage.posts).some((k) => k === time) ?? false; - - if (inPage) { - const post = newPage.posts[time]; - if (!post) { - return page; - } - newPage.posts[time] = { - ...post, - essay, - }; + if ('essay' in postResponse) { + if (prev === undefined) { + return undefined; + } - return newPage; - } + const { essay } = postResponse; - return newPage; - }); + const newPages = prev.pages.map((page) => { + const newPage = { + ...page, + }; - return { - pages: newPages, - pageParams: d.pageParams, + const inPage = + Object.keys(newPage.posts).some((k) => k === time) ?? false; + + if (inPage) { + const post = newPage.posts[time]; + if (!post) { + return page; + } + newPage.posts[time] = { + ...post, + essay, }; + + return newPage; } - ); - } else if ('reply' in postResponse) { - const { - reply: { - meta: { replyCount, lastReply, lastRepliers }, - 'r-reply': reply, - }, - } = postResponse; - - const [han, flag] = nestToFlag(nest); - - const replyQueryKey = [han, 'posts', flag, udToDec(time.toString())]; - - if (reply && 'set' in reply) { - if (reply.set === null) { - queryClient.setQueryData( - replyQueryKey, - (post: PostDataResponse | undefined) => { - if (post === undefined) { - return undefined; - } - - const existingReplies = post.seal.replies ?? {}; - - const newReplies = Object.keys(existingReplies) - .filter((k) => k !== reply.set?.seal.id) - .reduce( - (acc, k) => { - // eslint-disable-next-line no-param-reassign - acc[k] = existingReplies[k]; - return acc; - }, - {} as { [key: string]: Reply } - ); - const newPost = { - ...post, - seal: { - ...post.seal, - replies: newReplies, - meta: { - ...post.seal.meta, - replyCount, - lastReply, - lastRepliers, - }, - }, - }; + return newPage; + }); - return newPost; - } - ); - } else if ('memo' in reply.set) { - const newReply = reply.set; - - queryClient.setQueryData( - replyQueryKey, - (post: PostDataResponse | undefined) => { - if (post === undefined) { - return undefined; - } - - const existingReplies = post.seal.replies ?? {}; - - const existingCachedReply = - existingReplies[decToUd(unixToDa(newReply.memo.sent).toString())]; - - if (existingCachedReply) { - // remove cached reply if it exists - delete existingReplies[ - decToUd(unixToDa(newReply.memo.sent).toString()) - ]; - } - - const newReplies = { - ...existingReplies, - [decToUd(newReply.seal.id)]: newReply, - }; + return { + pages: newPages, + pageParams: prev.pageParams, + }; + } - const newPost = { - ...post, - seal: { - ...post.seal, - replies: newReplies, - meta: { - ...post.seal.meta, - replyCount, - lastReply, - lastRepliers, - }, - }, - }; + return prev; +}; - return newPost; - } - ); +function updateReplyMetaData( + cache: PostsInCachePrev | undefined, + cacheId: string, + meta: ReplyMeta +): PostsInCachePrev | undefined { + if (!cache) { + return undefined; + } - usePostsStore.getState().updateStatus( - { - author: newReply.memo.author, - sent: newReply.memo.sent, - }, - 'delivered' - ); + const newPages = cache.pages.map((page) => { + const newPage = { + ...page, + }; + + const inPage = + Object.keys(newPage.posts).some((k) => k === cacheId) ?? false; + + if (inPage) { + const post = newPage.posts[cacheId]; + if (!post) { + return newPage; } + newPage.posts[cacheId] = { + ...post, + seal: { + ...post.seal, + meta: { + ...post.seal.meta, + ...meta, + }, + }, + }; + + return newPage; } - queryClient.setQueryData<{ - pages: PagedPosts[]; - pageParams: PageParam[]; - }>( - queryKey, - (d: { pages: PagedPosts[]; pageParams: PageParam[] } | undefined) => { - if (d === undefined) { - return undefined; - } + return newPage; + }); - const newPages = d.pages.map((page) => { - const newPage = { - ...page, - }; + return { + pages: newPages, + pageParams: cache.pageParams, + }; +} - const inPage = - Object.keys(newPage.posts).some((k) => k === time.toString()) ?? - false; - - if (inPage) { - const post = newPage.posts[time.toString()]; - if (!post) { - return newPage; - } - newPage.posts[time.toString()] = { - ...post, - seal: { - ...post.seal, - meta: { - ...post.seal.meta, - replyCount, - lastReply, - lastRepliers, - }, - }, - }; +const replyUpdater = ( + prev: PostDataResponse | undefined, + data: ChannelsResponse +): PostDataResponse | undefined => { + const { nest, response } = data; - return newPage; - } + if (!('post' in response)) { + return prev; + } - return newPage; - }); + const postResponse = response.post['r-post']; + const { id } = response.post; + const time = decToUd(id); - return { - pages: newPages, - pageParams: d.pageParams, - }; - } + if (!('reply' in postResponse)) { + return prev; + } + + const { + reply: { + meta: { replyCount, lastReply, lastRepliers }, + 'r-reply': reply, + }, + } = postResponse; + + // const [han, flag] = nestToFlag(nest); + + // const replyQueryKey = [han, 'posts', flag, udToDec(time.toString())]; + if (reply && !('set' in reply)) { + return prev; + } + + if (reply.set === null) { + if (prev === undefined) { + return undefined; + } + + const existingReplies = prev.seal.replies ?? {}; + + const newReplies = Object.keys(existingReplies) + .filter((k) => k !== reply.set?.seal.id) + .reduce( + (acc, k) => { + // eslint-disable-next-line no-param-reassign + acc[k] = existingReplies[k]; + return acc; + }, + {} as { [key: string]: Reply } + ); + + const newPost = { + ...prev, + seal: { + ...prev.seal, + replies: newReplies, + meta: { + ...prev.seal.meta, + replyCount, + lastReply, + lastRepliers, + }, + }, + }; + + return newPost; + } + + if ('memo' in reply.set) { + const newReply = reply.set; + if (prev === undefined) { + return undefined; + } + + const existingReplies = prev.seal.replies ?? {}; + + const existingCachedReply = + existingReplies[decToUd(unixToDa(newReply.memo.sent).toString())]; + + if (existingCachedReply) { + // remove cached reply if it exists + delete existingReplies[decToUd(unixToDa(newReply.memo.sent).toString())]; + } + + const newReplies = { + ...existingReplies, + [decToUd(newReply.seal.id)]: newReply, + }; + + const newPost = { + ...prev, + seal: { + ...prev.seal, + replies: newReplies, + meta: { + ...prev.seal.meta, + replyCount, + lastReply, + lastRepliers, + }, + }, + }; + + usePostsStore.getState().updateStatus( + { + author: newReply.memo.author, + sent: newReply.memo.sent, + }, + 'delivered' ); + + return newPost; } }; @@ -619,34 +610,6 @@ export function useInfinitePosts(nest: Nest, initialTime?: string) { const [han, flag] = nestToFlag(nest); const queryKey = useMemo(() => [han, 'posts', flag, 'infinite'], [han, flag]); - const invalidate = useRef( - _.debounce( - (event: ChannelsResponse) => { - queryClient.invalidateQueries({ - queryKey, - refetchType: - event.response && 'posts' in event.response ? 'active' : 'none', - }); - }, - 300, - { - leading: true, - trailing: true, - } - ) - ); - - useEffect(() => { - api.subscribe({ - app: 'channels', - path: `/${nest}`, - event: (data: ChannelsResponse) => { - infinitePostUpdater(queryKey, data); - invalidate.current(data); - }, - }); - }, [nest, invalidate, queryKey, initialTime]); - const { data, ...rest } = useInfiniteQuery({ queryKey, queryFn: infinitePostQueryFn(nest, initialTime), @@ -802,87 +765,143 @@ export function useOrderedPosts( }; } -const emptyChannels: Channels = {}; -export function useChannels(): Channels { - const invalidate = useRef( - _.debounce( - (event: ChannelsResponse) => { - const postEvent = - event.response && - ('post' in event.response || 'posts' in event.response); - queryClient.invalidateQueries({ - queryKey: channelKey(), - refetchType: postEvent ? 'none' : 'active', - }); - }, - 300, - { leading: true, trailing: true } - ) - ); - - const eventHandler = useCallback((event: ChannelsSubscribeResponse) => { - if ('hide' in event) { +export function useChannelsFirehose() { + const [eventQueue, setEventQueue] = useState([]); + const eventProcessor = useCallback((events: ChannelsSubscribeResponse[]) => { + const hideEvents = events.filter((e) => 'hide' in e).map((e) => e.hide); + if (hideEvents.length > 0) { queryClient.setQueryData( ['channels', 'hidden'], - (d: HiddenPosts | undefined) => { - if (d === undefined) { - return [event.hide]; - } - - const newHidden = [...d, event.hide]; - - return newHidden; - } + (d: HiddenPosts = []) => [...d, ...hideEvents] ); } - if ('show' in event) { + const showEvents = events.filter((e) => 'show' in e).map((e) => e.show); + if (showEvents.length > 0) { queryClient.setQueryData( ['channels', 'hidden'], - (d: HiddenPosts | undefined) => { - if (d === undefined) { + (d: HiddenPosts = []) => [...d, ...showEvents] + ); + } + + const postEvents = events.filter( + (e) => + 'response' in e && + 'post' in e.response && + !('reply' in e.response.post['r-post']) + ); + if (postEvents.length > 0) { + const channelPostEvents = Object.entries( + _.groupBy(postEvents, (event: ChannelsSubscribeResponse) => event.nest) + ); + channelPostEvents.forEach(([nest, postEvents]) => { + const key = infinitePostsKey(nest); + const existingQueryData = queryClient.getQueryData< + PostsInCachePrev | undefined + >(key); + const newData = postEvents.reduce( + (prev, event) => infinitePostUpdater(prev, event), + existingQueryData + ); + queryClient.setQueryData(key, newData); + }); + } + + const replyEvents = events.filter( + (e) => + 'response' in e && + 'post' in e.response && + 'reply' in e.response.post['r-post'] + ); + if (replyEvents.length > 0) { + const channelReplyEvents = Object.entries( + _.groupBy(replyEvents, (event: ChannelsSubscribeResponse) => { + if (!('post' in event.response)) { return undefined; } - const newHidden = d.filter((h) => h !== event.show); + const { nest } = event; + const { id } = event.response.post; + const time = decToUd(id); + return postKey(nest, time).join(); + }) + ); - return newHidden; + channelReplyEvents.forEach(([, replyEvents]) => { + const event = replyEvents[0]; + if (!event || !('response' in event) || !('post' in event.response)) { + return; } - ); - } - if ('response' in event && 'post' in event.response) { - // We call infinitePostUpdater here because there are situations where we - // are only listening to useChannels and not useInfinitePosts. This is - // the case in threads on mobile in particular. - const { nest } = event; - const [han, flag] = nestToFlag(nest); - const infinitePostQueryKey = [han, 'posts', flag, 'infinite']; - const existingQueryData = queryClient.getQueryData(infinitePostQueryKey); - if ( - shouldAddPostToCache( - existingQueryData as { pages: PagedPosts[] } | undefined - ) - ) { - infinitePostUpdater(infinitePostQueryKey, event); - } + const postResponse = event.response.post['r-post']; + const { id } = event.response.post; + const { nest } = event; + const time = decToUd(id); + const key = postKey(nest, time); + const existingQueryData = queryClient.getQueryData< + PostDataResponse | undefined + >(key); + const newData = replyEvents.reduce( + (prev, event) => replyUpdater(prev, event), + existingQueryData + ); + queryClient.setQueryData(key, newData); + + if (!('reply' in postResponse)) { + return; + } + + const { + reply: { meta }, + } = postResponse; + queryClient.setQueryData( + infinitePostsKey(nest), + (d) => updateReplyMetaData(d, time, meta) + ); + }); } + }, []); - invalidate.current(event); + const eventHandler = useCallback((event: ChannelsSubscribeResponse) => { + setEventQueue((prev) => [...prev, event]); }, []); - const { data, ...rest } = useReactQuerySubscription< - Channels, - ChannelsSubscribeResponse - >({ + useEffect(() => { + api.subscribe({ + app: 'channels', + path: '/', + event: eventHandler, + }); + }, [eventHandler]); + + const processQueue = useRef( + _.debounce( + (events: ChannelsSubscribeResponse[]) => { + eventProcessor(events); + }, + 300, + { leading: true, trailing: true } + ) + ); + + useEffect(() => { + if (eventQueue.length === 0) { + return; + } + + processQueue.current(eventQueue); + }, [eventQueue]); +} + +const emptyChannels: Channels = {}; +export function useChannels(): Channels { + const { data, ...rest } = useReactQueryScry({ queryKey: channelKey(), app: 'channels', - path: '/', - scry: '/channels', + path: '/channels', options: { refetchOnMount: false, }, - onEvent: eventHandler, }); if (rest.isLoading || rest.isError || data === undefined) { @@ -1434,7 +1453,10 @@ export function useAddPostMutation(nest: string) { post ); - infinitePostUpdater(queryKey('infinite'), { + const existingQueryData = queryClient.getQueryData< + PostsInCachePrev | undefined + >(queryKey('infinite')); + const newData = infinitePostUpdater(existingQueryData, { nest, response: { post: { @@ -1451,6 +1473,7 @@ export function useAddPostMutation(nest: string) { }, }, }); + queryClient.setQueryData(queryKey('infinite'), newData); }, onSuccess: async (_data, variables) => { const status = usePostsStore.getState().getStatus(variables.cacheId); diff --git a/apps/tlon-web/src/state/channel/keys.ts b/apps/tlon-web/src/state/channel/keys.ts index fe5dc6055b..d03d0773c2 100644 --- a/apps/tlon-web/src/state/channel/keys.ts +++ b/apps/tlon-web/src/state/channel/keys.ts @@ -1,3 +1,5 @@ +import { udToDec } from '@urbit/api'; + import { nestToFlag } from '@/logic/utils'; export const channelKey = (...parts: string[]) => ['channels', ...parts]; @@ -7,6 +9,11 @@ export const infinitePostsKey = (nest: string) => { return [han, 'posts', flag, 'infinite']; }; +export const postKey = (nest: string, id: string) => { + const [han, flag] = nestToFlag(nest); + return [han, 'posts', flag, udToDec(id)]; +}; + export const ChannnelKeys = { channel: channelKey, infinitePostsKey, diff --git a/apps/tlon-web/src/types/channel.ts b/apps/tlon-web/src/types/channel.ts index 43094fd913..4d2f02066c 100644 --- a/apps/tlon-web/src/types/channel.ts +++ b/apps/tlon-web/src/types/channel.ts @@ -409,14 +409,16 @@ export type PostResponse = export type ReplyResponse = { set: Reply } | { reacts: Record }; +export interface ChannelPostResponse { + post: { + id: string; + 'r-post': PostResponse; + }; +} + export type Response = | { posts: Posts } - | { - post: { - id: string; - 'r-post': PostResponse; - }; - } + | ChannelPostResponse | { order: string[] } | { view: DisplayMode } | { sort: SortMode } From 5df264a84df4ce2d23aff25aa2ce143de43298a8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 22 Feb 2024 23:04:06 +0000 Subject: [PATCH 045/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index b304d50f44..60e1e8388f 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.ahc2a.qqtl8.6q39i.o4dca.i65jo.glob' 0v7.ahc2a.qqtl8.6q39i.o4dca.i65jo] + glob-http+['https://bootstrap.urbit.org/glob-0v6.u2o33.8vku1.n45v9.q58t2.ikovc.glob' 0v6.u2o33.8vku1.n45v9.q58t2.ikovc] base+'groups' version+[5 5 1] website+'https://tlon.io' From d81c8483d316b3f90032aa32ee63788a7ef3353b Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Thu, 22 Feb 2024 18:23:46 -0500 Subject: [PATCH 046/182] global: move urbit type to shared package --- apps/tlon-web/src/channels/ChannelActions.tsx | 2 +- .../src/channels/ChannelSortSelector.tsx | 3 +- .../src/channels/ChannelTypeSelector.tsx | 5 +- .../src/channels/ChannelViewSelector.tsx | 3 +- .../src/channels/ChannelVolumeDialog.tsx | 2 +- .../tlon-web/src/channels/DisplayDropdown.tsx | 2 +- .../tlon-web/src/channels/EditChannelForm.tsx | 7 ++- .../channels/NewChannel/NewChannelForm.tsx | 2 +- apps/tlon-web/src/chat/ChatChannel.tsx | 2 +- .../src/chat/ChatContent/ChatContent.tsx | 32 ++++++------ .../tlon-web/src/chat/ChatInput/ChatInput.tsx | 18 +++---- .../src/chat/ChatMessage/ChatMessage.tsx | 4 +- .../chat/ChatMessage/ChatMessageOptions.tsx | 2 +- apps/tlon-web/src/chat/ChatNotice.tsx | 2 +- .../src/chat/ChatReactions/ChatReaction.tsx | 2 +- .../src/chat/ChatReactions/ChatReactions.tsx | 2 +- .../src/chat/ChatScroller/ChatScroller.tsx | 4 +- .../src/chat/ChatSearch/ChatSearch.tsx | 2 +- .../src/chat/ChatSearch/ChatSearchResult.tsx | 4 +- .../src/chat/ChatSearch/ChatSearchResults.tsx | 4 +- .../chat/ChatSearch/useChatSearchInput.tsx | 2 +- .../src/chat/ChatThread/ChatThread.tsx | 2 +- apps/tlon-web/src/chat/ChatUnreadAlerts.tsx | 2 +- apps/tlon-web/src/chat/DMUnreadAlerts.tsx | 2 +- apps/tlon-web/src/chat/unreadUtils.ts | 5 +- apps/tlon-web/src/chat/useChatStore.ts | 4 +- .../src/components/About/AboutView.tsx | 2 +- apps/tlon-web/src/components/Avatar.tsx | 2 +- apps/tlon-web/src/components/ClubName.tsx | 2 +- apps/tlon-web/src/components/Leap/useLeap.tsx | 6 +-- .../tlon-web/src/components/MessageEditor.tsx | 2 +- .../References/ContentReference.tsx | 2 +- .../components/References/CurioReference.tsx | 2 +- .../References/UnavailableReference.tsx | 3 +- .../References/WritBaseReference.tsx | 2 +- .../components/Settings/BlockedUsersView.tsx | 3 +- .../src/components/Settings/SettingsView.tsx | 2 +- .../src/components/Sidebar/GroupList.tsx | 2 +- .../src/components/Sidebar/Sidebar.test.tsx | 2 +- .../src/components/Sidebar/useSearchFilter.ts | 2 +- .../tlon-web/src/components/VolumeSetting.tsx | 6 ++- apps/tlon-web/src/diary/DiaryChannel.tsx | 4 +- apps/tlon-web/src/diary/DiaryCommentField.tsx | 4 +- .../diary/DiaryContent/DiaryContent.test.tsx | 3 +- .../src/diary/DiaryContent/DiaryContent.tsx | 31 +++++++----- apps/tlon-web/src/diary/DiaryHeader.tsx | 2 +- .../src/diary/DiaryList/DiaryGridItem.tsx | 2 +- .../src/diary/DiaryList/DiaryGridView.tsx | 2 +- .../src/diary/DiaryList/DiaryListItem.tsx | 2 +- .../src/diary/DiaryMarkdownEditor.tsx | 2 +- apps/tlon-web/src/diary/DiaryNote.tsx | 4 +- apps/tlon-web/src/diary/DiaryNoteHeadline.tsx | 2 +- apps/tlon-web/src/diary/DiaryVerse.tsx | 3 +- .../src/diary/NoteReactions/NoteReactions.tsx | 2 +- apps/tlon-web/src/diary/diary-add-note.tsx | 4 +- apps/tlon-web/src/dms/DMHero.tsx | 2 +- apps/tlon-web/src/dms/DMThread.tsx | 2 +- apps/tlon-web/src/dms/Dm.tsx | 2 +- apps/tlon-web/src/dms/DmWindow.tsx | 2 +- apps/tlon-web/src/dms/MessagesList.tsx | 4 +- apps/tlon-web/src/dms/MultiDMInfoForm.tsx | 2 +- apps/tlon-web/src/dms/MultiDm.tsx | 2 +- apps/tlon-web/src/dms/MultiDmHero.tsx | 2 +- .../src/groups/AddGroup/SearchResults.tsx | 2 +- .../ChannelsList/ChannelJoinSelector.tsx | 2 +- .../ChannelsList/ChannelPermsSelector.tsx | 5 +- .../groups/ChannelsList/ChannelsListItem.tsx | 2 +- .../ChannelsList/DeleteChannelModal.tsx | 2 +- .../groups/ChannelsList/EditChannelModal.tsx | 2 +- .../ChannelsList/GroupChannelManager.tsx | 2 +- .../ChannelsList/SectionNameEditInput.tsx | 2 +- .../tlon-web/src/groups/ChannelsList/types.ts | 2 +- apps/tlon-web/src/groups/FindGroups.tsx | 2 +- .../src/groups/GangPreview/GangPreview.tsx | 2 +- .../src/groups/GroupAdmin/GroupInfo.tsx | 2 +- .../src/groups/GroupAdmin/GroupInfoEditor.tsx | 12 ++--- .../src/groups/GroupAdmin/GroupInfoFields.tsx | 2 +- .../groups/GroupAdmin/GroupInvitesPrivacy.tsx | 2 +- .../src/groups/GroupAdmin/GroupMemberItem.tsx | 2 +- .../src/groups/GroupAdmin/GroupMembers.tsx | 2 +- .../src/groups/GroupAdmin/PrivacySelector.tsx | 6 ++- apps/tlon-web/src/groups/GroupJoinList.tsx | 2 +- .../groups/GroupSidebar/ChannelList.test.tsx | 2 +- .../src/groups/GroupSidebar/ChannelList.tsx | 2 +- apps/tlon-web/src/groups/GroupSummary.tsx | 3 +- .../tlon-web/src/groups/GroupVolumeDialog.tsx | 2 +- apps/tlon-web/src/groups/LureAutojoiner.tsx | 2 +- apps/tlon-web/src/groups/LureInviteBlock.tsx | 2 +- .../tlon-web/src/groups/NewGroup/NewGroup.tsx | 2 +- .../src/groups/NewGroup/NewGroupInvite.tsx | 2 +- .../src/groups/RoleInput/RoleSelector.tsx | 2 +- apps/tlon-web/src/groups/useGroupJoin.ts | 2 +- apps/tlon-web/src/heap/EditCurioForm.tsx | 7 ++- apps/tlon-web/src/heap/HeapBlock.tsx | 7 ++- apps/tlon-web/src/heap/HeapChannel.tsx | 4 +- apps/tlon-web/src/heap/HeapContent.tsx | 11 ++-- apps/tlon-web/src/heap/HeapDetail.tsx | 4 +- .../src/heap/HeapDetail/HeapDetailBody.tsx | 12 ++--- .../src/heap/HeapDetail/HeapDetailHeader.tsx | 7 ++- .../HeapDetailSidebar/HeapDetailComments.tsx | 2 +- .../HeapDetailSidebarInfo.tsx | 3 +- apps/tlon-web/src/heap/HeapHeader.tsx | 2 +- apps/tlon-web/src/heap/HeapRow.tsx | 7 ++- apps/tlon-web/src/heap/HeapTextInput.tsx | 4 +- apps/tlon-web/src/logic/analytics.ts | 3 +- apps/tlon-web/src/logic/channel.ts | 12 +++-- apps/tlon-web/src/logic/getKindData.ts | 2 +- apps/tlon-web/src/logic/heap.ts | 9 ++-- apps/tlon-web/src/logic/messageSender.ts | 12 ++--- apps/tlon-web/src/logic/tiptap.test.ts | 5 +- apps/tlon-web/src/logic/tiptap.ts | 15 ++++-- apps/tlon-web/src/logic/useGroupSort.ts | 2 +- apps/tlon-web/src/logic/useMessageSort.ts | 5 +- .../tlon-web/src/logic/useScrollerMessages.ts | 5 +- apps/tlon-web/src/logic/utils.ts | 50 ++++++++++--------- apps/tlon-web/src/mocks/chat.ts | 10 ++-- apps/tlon-web/src/mocks/groups.ts | 9 ++-- apps/tlon-web/src/mocks/handlers.ts | 20 ++++---- apps/tlon-web/src/mocks/heaps.ts | 3 +- .../src/notifications/DMNotification.tsx | 2 +- .../src/notifications/GroupNotification.tsx | 3 +- .../src/notifications/Notification.tsx | 2 +- .../src/notifications/Notifications.tsx | 4 +- .../src/notifications/useNotifications.tsx | 2 +- .../src/profiles/EditProfile/EditProfile.tsx | 14 +++--- apps/tlon-web/src/profiles/Profile.tsx | 2 +- apps/tlon-web/src/replies/ReplyMessage.tsx | 9 +++- .../src/replies/ReplyMessageOptions.tsx | 2 +- .../replies/ReplyReactions/ReplyReactions.tsx | 2 +- apps/tlon-web/src/replies/replies.ts | 8 ++- apps/tlon-web/src/state/bootstrap.ts | 2 +- apps/tlon-web/src/state/channel/channel.ts | 44 ++++++++-------- apps/tlon-web/src/state/channel/util.ts | 2 +- apps/tlon-web/src/state/chat/chat.ts | 47 +++++++++-------- apps/tlon-web/src/state/chat/search.ts | 20 +++++--- apps/tlon-web/src/state/chat/utils.ts | 13 +++-- apps/tlon-web/src/state/contact.ts | 16 +++--- apps/tlon-web/src/state/groups/groups.ts | 42 ++++++++-------- .../src/state/groups/groupsReducer.ts | 3 +- apps/tlon-web/src/state/groups/type.ts | 10 ++-- apps/tlon-web/src/state/hark.ts | 10 ++-- apps/tlon-web/src/state/lure/lure.ts | 2 +- apps/tlon-web/src/state/negotiation.ts | 5 +- apps/tlon-web/src/state/pins.ts | 2 +- apps/tlon-web/src/state/settings.ts | 2 +- apps/tlon-web/src/window.ts | 3 +- packages/shared/package.json | 4 +- .../shared/src/urbit}/channel.ts | 0 .../shared/src/urbit}/contact.ts | 0 .../shared/src/urbit}/content.ts | 0 .../shared/src/urbit}/dms.ts | 0 .../shared/src/urbit}/groups.ts | 0 .../shared/src/urbit}/hark.ts | 0 .../shared/src/urbit}/negotiation.ts | 0 .../shared/src/urbit}/sigil.ts | 0 .../types => packages/shared/src/urbit}/ui.ts | 0 .../shared/src/urbit}/volume.ts | 0 157 files changed, 455 insertions(+), 376 deletions(-) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/channel.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/contact.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/content.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/dms.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/groups.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/hark.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/negotiation.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/sigil.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/ui.ts (100%) rename {apps/tlon-web/src/types => packages/shared/src/urbit}/volume.ts (100%) diff --git a/apps/tlon-web/src/channels/ChannelActions.tsx b/apps/tlon-web/src/channels/ChannelActions.tsx index 0e7cec1684..f57d48da6a 100644 --- a/apps/tlon-web/src/channels/ChannelActions.tsx +++ b/apps/tlon-web/src/channels/ChannelActions.tsx @@ -1,3 +1,4 @@ +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { PropsWithChildren, useCallback, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; @@ -13,7 +14,6 @@ import { Status } from '@/logic/status'; import { useIsMobile } from '@/logic/useMedia'; import { getFlagParts, nestToFlag } from '@/logic/utils'; import { useDeleteChannelMutation, useRouteGroup } from '@/state/groups'; -import { GroupChannel } from '@/types/groups'; import ChannelHostConnection from './ChannelHostConnection'; diff --git a/apps/tlon-web/src/channels/ChannelSortSelector.tsx b/apps/tlon-web/src/channels/ChannelSortSelector.tsx index 88977326df..147df00706 100644 --- a/apps/tlon-web/src/channels/ChannelSortSelector.tsx +++ b/apps/tlon-web/src/channels/ChannelSortSelector.tsx @@ -1,7 +1,6 @@ +import { ChannelFormSchema } from '@tloncorp/shared/dist/urbit/groups'; import { useFormContext } from 'react-hook-form'; -import { ChannelFormSchema } from '@/types/groups'; - interface SortSettingRowProps { type: 'time' | 'arranged'; } diff --git a/apps/tlon-web/src/channels/ChannelTypeSelector.tsx b/apps/tlon-web/src/channels/ChannelTypeSelector.tsx index 2f779a3664..9b0d76e89d 100644 --- a/apps/tlon-web/src/channels/ChannelTypeSelector.tsx +++ b/apps/tlon-web/src/channels/ChannelTypeSelector.tsx @@ -1,3 +1,7 @@ +import { + ChannelType, + NewChannelFormSchema, +} from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React from 'react'; import { useFormContext } from 'react-hook-form'; @@ -5,7 +9,6 @@ import { useFormContext } from 'react-hook-form'; import BubbleIcon from '@/components/icons/BubbleIcon'; import NotebookIcon from '@/components/icons/NotebookIcon'; import ShapesIcon from '@/components/icons/ShapesIcon'; -import { ChannelType, NewChannelFormSchema } from '@/types/groups'; interface ChannelTypeMetadata { title: string; diff --git a/apps/tlon-web/src/channels/ChannelViewSelector.tsx b/apps/tlon-web/src/channels/ChannelViewSelector.tsx index 06d2b797df..6166a91986 100644 --- a/apps/tlon-web/src/channels/ChannelViewSelector.tsx +++ b/apps/tlon-web/src/channels/ChannelViewSelector.tsx @@ -1,7 +1,6 @@ +import { ChannelFormSchema } from '@tloncorp/shared/dist/urbit/groups'; import { useFormContext } from 'react-hook-form'; -import { ChannelFormSchema } from '@/types/groups'; - interface ViewSettingRowProps { type: 'grid' | 'list'; } diff --git a/apps/tlon-web/src/channels/ChannelVolumeDialog.tsx b/apps/tlon-web/src/channels/ChannelVolumeDialog.tsx index 558f15458b..bc574c3c2a 100644 --- a/apps/tlon-web/src/channels/ChannelVolumeDialog.tsx +++ b/apps/tlon-web/src/channels/ChannelVolumeDialog.tsx @@ -1,3 +1,4 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { Helmet } from 'react-helmet'; import { useParams } from 'react-router'; @@ -5,7 +6,6 @@ import Dialog from '@/components/Dialog'; import VolumeSetting from '@/components/VolumeSetting'; import { useDismissNavigate } from '@/logic/routing'; import { useGroupChannel, useRouteGroup } from '@/state/groups'; -import { ViewProps } from '@/types/groups'; export default function ChannelVolumeDialog({ title }: ViewProps) { const { chType, chShip, chName } = useParams<{ diff --git a/apps/tlon-web/src/channels/DisplayDropdown.tsx b/apps/tlon-web/src/channels/DisplayDropdown.tsx index 1b924bf0dc..88652e5d11 100644 --- a/apps/tlon-web/src/channels/DisplayDropdown.tsx +++ b/apps/tlon-web/src/channels/DisplayDropdown.tsx @@ -1,9 +1,9 @@ import * as Dropdown from '@radix-ui/react-dropdown-menu'; +import { DisplayMode } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import GridIcon from '@/components/icons/GridIcon'; import ListIcon from '@/components/icons/ListIcon'; -import { DisplayMode } from '@/types/channel'; interface DisplayDropdownProps { displayMode: DisplayMode; diff --git a/apps/tlon-web/src/channels/EditChannelForm.tsx b/apps/tlon-web/src/channels/EditChannelForm.tsx index 9c2a9839c1..2d5f8c1856 100644 --- a/apps/tlon-web/src/channels/EditChannelForm.tsx +++ b/apps/tlon-web/src/channels/EditChannelForm.tsx @@ -1,4 +1,9 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { SortMode } from '@tloncorp/shared/dist/urbit/channel'; +import { + ChannelFormSchema, + GroupChannel, +} from '@tloncorp/shared/dist/urbit/groups'; import _ from 'lodash'; import React, { useCallback } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; @@ -21,8 +26,6 @@ import { useGroup, useRouteGroup, } from '@/state/groups'; -import { SortMode } from '@/types/channel'; -import { ChannelFormSchema, GroupChannel } from '@/types/groups'; import ChannelSortSelector from './ChannelSortSelector'; import ChannelViewSelector from './ChannelViewSelector'; diff --git a/apps/tlon-web/src/channels/NewChannel/NewChannelForm.tsx b/apps/tlon-web/src/channels/NewChannel/NewChannelForm.tsx index 03ef393de7..816b253310 100644 --- a/apps/tlon-web/src/channels/NewChannel/NewChannelForm.tsx +++ b/apps/tlon-web/src/channels/NewChannel/NewChannelForm.tsx @@ -1,4 +1,5 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { NewChannelFormSchema } from '@tloncorp/shared/dist/urbit/groups'; import { useCallback } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router'; @@ -15,7 +16,6 @@ import { useGroupCompatibility, useRouteGroup, } from '@/state/groups'; -import { NewChannelFormSchema } from '@/types/groups'; export default function NewChannelForm() { const { section } = useParams<{ section: string }>(); diff --git a/apps/tlon-web/src/chat/ChatChannel.tsx b/apps/tlon-web/src/chat/ChatChannel.tsx index c7dc8203b2..3344e2e352 100644 --- a/apps/tlon-web/src/chat/ChatChannel.tsx +++ b/apps/tlon-web/src/chat/ChatChannel.tsx @@ -1,3 +1,4 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useMemo, useRef } from 'react'; import { Helmet } from 'react-helmet'; @@ -25,7 +26,6 @@ import { useReplyPost, } from '@/state/channel/channel'; import { useRouteGroup } from '@/state/groups/groups'; -import { ViewProps } from '@/types/groups'; import ChatThread from './ChatThread/ChatThread'; diff --git a/apps/tlon-web/src/chat/ChatContent/ChatContent.tsx b/apps/tlon-web/src/chat/ChatContent/ChatContent.tsx index 82751cd55e..7e10a6aad3 100644 --- a/apps/tlon-web/src/chat/ChatContent/ChatContent.tsx +++ b/apps/tlon-web/src/chat/ChatContent/ChatContent.tsx @@ -1,24 +1,10 @@ -import cn from 'classnames'; -import { findLastIndex } from 'lodash'; -import React, { useEffect } from 'react'; -import { useLocation } from 'react-router'; -import { Link } from 'react-router-dom'; - -import ChatContentImage from '@/chat/ChatContent/ChatContentImage'; -import ChatEmbedContent from '@/chat/ChatEmbedContent/ChatEmbedContent'; -// eslint-disable-next-line import/no-cycle -import ContentReference from '@/components/References/ContentReference'; -import ShipName from '@/components/ShipName'; -import { isSingleEmoji } from '@/logic/utils'; -// eslint-disable-next-line import/no-cycle -import { VIDEO_REGEX } from '@/logic/utils'; import { Block, Story, VerseBlock, VerseInline, isImage, -} from '@/types/channel'; +} from '@tloncorp/shared/dist/urbit/channel'; import { Inline, isBlockCode, @@ -30,7 +16,21 @@ import { isLink, isShip, isStrikethrough, -} from '@/types/content'; +} from '@tloncorp/shared/dist/urbit/content'; +import cn from 'classnames'; +import { findLastIndex } from 'lodash'; +import React, { useEffect } from 'react'; +import { useLocation } from 'react-router'; +import { Link } from 'react-router-dom'; + +import ChatContentImage from '@/chat/ChatContent/ChatContentImage'; +import ChatEmbedContent from '@/chat/ChatEmbedContent/ChatEmbedContent'; +// eslint-disable-next-line import/no-cycle +import ContentReference from '@/components/References/ContentReference'; +import ShipName from '@/components/ShipName'; +import { isSingleEmoji } from '@/logic/utils'; +// eslint-disable-next-line import/no-cycle +import { VIDEO_REGEX } from '@/logic/utils'; interface ChatContentProps { story: Story; diff --git a/apps/tlon-web/src/chat/ChatInput/ChatInput.tsx b/apps/tlon-web/src/chat/ChatInput/ChatInput.tsx index 8db4db0a4c..7235a9d685 100644 --- a/apps/tlon-web/src/chat/ChatInput/ChatInput.tsx +++ b/apps/tlon-web/src/chat/ChatInput/ChatInput.tsx @@ -1,5 +1,14 @@ import * as Popover from '@radix-ui/react-popover'; import { Editor } from '@tiptap/react'; +import { + Cite, + Memo, + Nest, + PageTuple, + PostEssay, + ReplyTuple, +} from '@tloncorp/shared/dist/urbit/channel'; +import { WritTuple } from '@tloncorp/shared/dist/urbit/dms'; import cn from 'classnames'; import _, { debounce } from 'lodash'; import React, { @@ -55,15 +64,6 @@ import { } from '@/state/chat'; import { useGroupFlag } from '@/state/groups'; import { useFileStore, useUploader } from '@/state/storage'; -import { - Cite, - Memo, - Nest, - PageTuple, - PostEssay, - ReplyTuple, -} from '@/types/channel'; -import { WritTuple } from '@/types/dms'; interface ChatInputProps { whom: string; diff --git a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx index dab68558bb..165a1bff00 100644 --- a/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx +++ b/apps/tlon-web/src/chat/ChatMessage/ChatMessage.tsx @@ -1,5 +1,7 @@ /* eslint-disable react/no-unused-prop-types */ // eslint-disable-next-line import/no-cycle +import { Post, Story, Unread } from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import { BigInteger } from 'big-integer'; import cn from 'classnames'; @@ -36,8 +38,6 @@ import { useMessageToggler, useTrackedMessageStatus, } from '@/state/chat'; -import { Post, Story, Unread } from '@/types/channel'; -import { DMUnread } from '@/types/dms'; import ReactionDetails from '../ChatReactions/ReactionDetails'; import { getUnreadStatus, threadIsOlderThanLastRead } from '../unreadUtils'; diff --git a/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx b/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx index ae8fd9585d..bd58f7e41e 100644 --- a/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx +++ b/apps/tlon-web/src/chat/ChatMessage/ChatMessageOptions.tsx @@ -1,3 +1,4 @@ +import { Post, emptyPost } from '@tloncorp/shared/dist/urbit/channel'; import { decToUd } from '@urbit/api'; import cn from 'classnames'; import React, { @@ -50,7 +51,6 @@ import { useRouteGroup, useVessel, } from '@/state/groups'; -import { Post, emptyPost } from '@/types/channel'; function ChatMessageOptions(props: { open: boolean; diff --git a/apps/tlon-web/src/chat/ChatNotice.tsx b/apps/tlon-web/src/chat/ChatNotice.tsx index 91dd42a11f..42d78b05bc 100644 --- a/apps/tlon-web/src/chat/ChatNotice.tsx +++ b/apps/tlon-web/src/chat/ChatNotice.tsx @@ -1,7 +1,7 @@ +import { Post } from '@tloncorp/shared/dist/urbit/channel'; import React from 'react'; import AddPersonIcon from '@/components/icons/AddPersonIcon'; -import { Post } from '@/types/channel'; import ChatContent from './ChatContent/ChatContent'; import DateDivider from './ChatMessage/DateDivider'; diff --git a/apps/tlon-web/src/chat/ChatReactions/ChatReaction.tsx b/apps/tlon-web/src/chat/ChatReactions/ChatReaction.tsx index dac1b4eb70..b3c8b67f6b 100644 --- a/apps/tlon-web/src/chat/ChatReactions/ChatReaction.tsx +++ b/apps/tlon-web/src/chat/ChatReactions/ChatReaction.tsx @@ -1,4 +1,5 @@ import * as Tooltip from '@radix-ui/react-tooltip'; +import { PostSeal, ReplySeal } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import React, { useCallback, useEffect } from 'react'; @@ -14,7 +15,6 @@ import { import { useAddDmReactMutation, useDelDmReactMutation } from '@/state/chat'; import useEmoji from '@/state/emoji'; import { useRouteGroup } from '@/state/groups'; -import { PostSeal, ReplySeal } from '@/types/channel'; interface ChatReactionProps { whom: string; diff --git a/apps/tlon-web/src/chat/ChatReactions/ChatReactions.tsx b/apps/tlon-web/src/chat/ChatReactions/ChatReactions.tsx index 53288ea70f..e7eda1561e 100644 --- a/apps/tlon-web/src/chat/ChatReactions/ChatReactions.tsx +++ b/apps/tlon-web/src/chat/ChatReactions/ChatReactions.tsx @@ -1,3 +1,4 @@ +import { PostSeal } from '@tloncorp/shared/dist/urbit/channel'; import _ from 'lodash'; import { useCallback, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; @@ -11,7 +12,6 @@ import { useIsDmOrMultiDm } from '@/logic/utils'; import { useAddPostReactMutation } from '@/state/channel/channel'; import { useAddDmReactMutation } from '@/state/chat'; import { useRouteGroup } from '@/state/groups'; -import { PostSeal } from '@/types/channel'; import ChatReaction from './ChatReaction'; diff --git a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx index 7c121fcefe..2ccde43646 100644 --- a/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx +++ b/apps/tlon-web/src/chat/ChatScroller/ChatScroller.tsx @@ -1,4 +1,6 @@ import { Virtualizer, useVirtualizer } from '@tanstack/react-virtual'; +import { PageTuple, ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { WritTuple } from '@tloncorp/shared/dist/urbit/dms'; import { BigInteger } from 'big-integer'; import React, { PropsWithChildren, @@ -33,8 +35,6 @@ import { import { createDevLogger, useObjectChangeLogging } from '@/logic/utils'; import ReplyMessage from '@/replies/ReplyMessage'; import { useShowDevTools } from '@/state/local'; -import { PageTuple, ReplyTuple } from '@/types/channel'; -import { WritTuple } from '@/types/dms'; import ChatScrollerDebugOverlay from './ChatScrollerDebugOverlay'; diff --git a/apps/tlon-web/src/chat/ChatSearch/ChatSearch.tsx b/apps/tlon-web/src/chat/ChatSearch/ChatSearch.tsx index 6c59e90253..49e26f7f96 100644 --- a/apps/tlon-web/src/chat/ChatSearch/ChatSearch.tsx +++ b/apps/tlon-web/src/chat/ChatSearch/ChatSearch.tsx @@ -1,4 +1,5 @@ import * as Dialog from '@radix-ui/react-dialog'; +import { ChatMap } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import React, { PropsWithChildren, useCallback } from 'react'; import { useNavigate } from 'react-router'; @@ -9,7 +10,6 @@ import useActiveTab from '@/components/Sidebar/util'; import { useSafeAreaInsets } from '@/logic/native'; import useMedia, { useIsMobile } from '@/logic/useMedia'; import { disableDefault } from '@/logic/utils'; -import { ChatMap } from '@/types/channel'; import ChatSearchResults from './ChatSearchResults'; import SearchBar from './SearchBar'; diff --git a/apps/tlon-web/src/chat/ChatSearch/ChatSearchResult.tsx b/apps/tlon-web/src/chat/ChatSearch/ChatSearchResult.tsx index 2ad30862f1..5e558dc0fe 100644 --- a/apps/tlon-web/src/chat/ChatSearch/ChatSearchResult.tsx +++ b/apps/tlon-web/src/chat/ChatSearch/ChatSearchResult.tsx @@ -1,3 +1,5 @@ +import { Post, Reply } from '@tloncorp/shared/dist/urbit/channel'; +import { Writ } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import { BigInteger } from 'big-integer'; import cn from 'classnames'; @@ -5,8 +7,6 @@ import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; import ReplyReactions from '@/replies/ReplyReactions/ReplyReactions'; -import { Post, Reply } from '@/types/channel'; -import { Writ } from '@/types/dms'; import ChatContent from '../ChatContent/ChatContent'; import Author from '../ChatMessage/Author'; diff --git a/apps/tlon-web/src/chat/ChatSearch/ChatSearchResults.tsx b/apps/tlon-web/src/chat/ChatSearch/ChatSearchResults.tsx index e62f8cb05f..a55041f84a 100644 --- a/apps/tlon-web/src/chat/ChatSearch/ChatSearchResults.tsx +++ b/apps/tlon-web/src/chat/ChatSearch/ChatSearchResults.tsx @@ -1,10 +1,10 @@ +import { ChatMap, Post, Reply } from '@tloncorp/shared/dist/urbit/channel'; +import { Writ } from '@tloncorp/shared/dist/urbit/dms'; import { BigInteger } from 'big-integer'; import React, { useEffect, useMemo, useState } from 'react'; import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import { useIsMobile } from '@/logic/useMedia'; -import { ChatMap, Post, Reply } from '@/types/channel'; -import { Writ } from '@/types/dms'; import ChatScrollerPlaceholder from '../ChatScroller/ChatScrollerPlaceholder'; import ChatSearchResult from './ChatSearchResult'; diff --git a/apps/tlon-web/src/chat/ChatSearch/useChatSearchInput.tsx b/apps/tlon-web/src/chat/ChatSearch/useChatSearchInput.tsx index b267553dd4..05b2a1a0cb 100644 --- a/apps/tlon-web/src/chat/ChatSearch/useChatSearchInput.tsx +++ b/apps/tlon-web/src/chat/ChatSearch/useChatSearchInput.tsx @@ -1,9 +1,9 @@ +import { ChatMap } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import { ChangeEvent, KeyboardEvent, useCallback, useState } from 'react'; import { useNavigate } from 'react-router'; import useDebounce from '@/logic/useDebounce'; -import { ChatMap } from '@/types/channel'; export interface Selection { index: number; diff --git a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx index 0f6f0ca983..176b412374 100644 --- a/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx +++ b/apps/tlon-web/src/chat/ChatThread/ChatThread.tsx @@ -1,3 +1,4 @@ +import { ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import cn from 'classnames'; import _ from 'lodash'; @@ -32,7 +33,6 @@ import { useRouteGroup, useVessel, } from '@/state/groups/groups'; -import { ReplyTuple } from '@/types/channel'; import ChatScrollerPlaceholder from '../ChatScroller/ChatScrollerPlaceholder'; import { chatStoreLogger, useChatInfo, useChatStore } from '../useChatStore'; diff --git a/apps/tlon-web/src/chat/ChatUnreadAlerts.tsx b/apps/tlon-web/src/chat/ChatUnreadAlerts.tsx index 32c2ac2889..3383c588fb 100644 --- a/apps/tlon-web/src/chat/ChatUnreadAlerts.tsx +++ b/apps/tlon-web/src/chat/ChatUnreadAlerts.tsx @@ -1,3 +1,4 @@ +import { Unread, UnreadPoint } from '@tloncorp/shared/dist/urbit/channel'; import { daToUnix } from '@urbit/api'; import bigInt from 'big-integer'; import { format, isToday } from 'date-fns'; @@ -7,7 +8,6 @@ import { Link } from 'react-router-dom'; import XIcon from '@/components/icons/XIcon'; import { nestToFlag, pluralize } from '@/logic/utils'; import { useMarkReadMutation } from '@/state/channel/channel'; -import { Unread, UnreadPoint } from '@/types/channel'; import { getUnreadStatus, threadIsOlderThanLastRead } from './unreadUtils'; import { useChatInfo, useChatStore } from './useChatStore'; diff --git a/apps/tlon-web/src/chat/DMUnreadAlerts.tsx b/apps/tlon-web/src/chat/DMUnreadAlerts.tsx index 500fb0d403..983ab7ec77 100644 --- a/apps/tlon-web/src/chat/DMUnreadAlerts.tsx +++ b/apps/tlon-web/src/chat/DMUnreadAlerts.tsx @@ -1,3 +1,4 @@ +import { DMUnread, UnreadThread } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import bigInt from 'big-integer'; import { format, isToday } from 'date-fns'; @@ -7,7 +8,6 @@ import { Link } from 'react-router-dom'; import XIcon from '@/components/icons/XIcon'; import { pluralize } from '@/logic/utils'; import { useMarkDmReadMutation } from '@/state/chat'; -import { DMUnread, UnreadThread } from '@/types/dms'; import { getUnreadStatus, threadIsOlderThanLastRead } from './unreadUtils'; import { useChatInfo, useChatStore } from './useChatStore'; diff --git a/apps/tlon-web/src/chat/unreadUtils.ts b/apps/tlon-web/src/chat/unreadUtils.ts index 7145b49f26..d936386817 100644 --- a/apps/tlon-web/src/chat/unreadUtils.ts +++ b/apps/tlon-web/src/chat/unreadUtils.ts @@ -1,8 +1,7 @@ +import { Unread } from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; import bigInt from 'big-integer'; -import { Unread } from '@/types/channel'; -import { DMUnread } from '@/types/dms'; - export function threadIsOlderThanLastRead( unread: DMUnread | Unread, threadId: string | null diff --git a/apps/tlon-web/src/chat/useChatStore.ts b/apps/tlon-web/src/chat/useChatStore.ts index b59438c5df..b4014428ee 100644 --- a/apps/tlon-web/src/chat/useChatStore.ts +++ b/apps/tlon-web/src/chat/useChatStore.ts @@ -1,10 +1,10 @@ +import { Block, Unread, Unreads } from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread, DMUnreads } from '@tloncorp/shared/dist/urbit/dms'; import produce from 'immer'; import { useCallback } from 'react'; import create from 'zustand'; import { createDevLogger } from '@/logic/utils'; -import { Block, Unread, Unreads } from '@/types/channel'; -import { DMUnread, DMUnreads } from '@/types/dms'; export interface ChatInfo { replying: string | null; diff --git a/apps/tlon-web/src/components/About/AboutView.tsx b/apps/tlon-web/src/components/About/AboutView.tsx index 60bb54ed25..5ef1d7b883 100644 --- a/apps/tlon-web/src/components/About/AboutView.tsx +++ b/apps/tlon-web/src/components/About/AboutView.tsx @@ -1,7 +1,7 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { Helmet } from 'react-helmet'; import { useIsMobile } from '@/logic/useMedia'; -import { ViewProps } from '@/types/groups'; import Layout from '../Layout/Layout'; import MobileHeader from '../MobileHeader'; diff --git a/apps/tlon-web/src/components/Avatar.tsx b/apps/tlon-web/src/components/Avatar.tsx index 6459dabf19..91cece39f5 100644 --- a/apps/tlon-web/src/components/Avatar.tsx +++ b/apps/tlon-web/src/components/Avatar.tsx @@ -1,3 +1,4 @@ +import { SigilProps } from '@tloncorp/shared/dist/urbit/sigil'; import { Contact, cite } from '@urbit/api'; import '@urbit/sigil-js'; import classNames from 'classnames'; @@ -11,7 +12,6 @@ import { useAvatar } from '@/state/avatar'; import { useContact } from '@/state/contact'; import { useCurrentTheme } from '@/state/local'; import { useCalm } from '@/state/settings'; -import { SigilProps } from '@/types/sigil'; export type AvatarSizes = | 'sidebar' diff --git a/apps/tlon-web/src/components/ClubName.tsx b/apps/tlon-web/src/components/ClubName.tsx index 6937d45416..5e5ca2f43f 100644 --- a/apps/tlon-web/src/components/ClubName.tsx +++ b/apps/tlon-web/src/components/ClubName.tsx @@ -1,4 +1,4 @@ -import type { Club } from '@/types/dms'; +import type { Club } from '@tloncorp/shared/dist/urbit/dms'; import ShipName from './ShipName'; diff --git a/apps/tlon-web/src/components/Leap/useLeap.tsx b/apps/tlon-web/src/components/Leap/useLeap.tsx index de3ba35005..d392b4211d 100644 --- a/apps/tlon-web/src/components/Leap/useLeap.tsx +++ b/apps/tlon-web/src/components/Leap/useLeap.tsx @@ -1,3 +1,6 @@ +import { Contact } from '@tloncorp/shared/dist/urbit/contact'; +import { Club } from '@tloncorp/shared/dist/urbit/dms'; +import { Group, GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import { cite, deSig, preSig } from '@urbit/api'; import fuzzy from 'fuzzy'; import { uniqBy } from 'lodash'; @@ -18,9 +21,6 @@ import { ChargeWithDesk, useCharges } from '@/state/docket'; import { useGroupFlag, useGroups, usePinnedGroups } from '@/state/groups'; import { useMutuals } from '@/state/pals'; import { usePinnedChannels, usePinnedClubs } from '@/state/pins'; -import { Contact } from '@/types/contact'; -import { Club } from '@/types/dms'; -import { Group, GroupChannel } from '@/types/groups'; import useActiveTab from '../Sidebar/util'; import BubbleIcon from '../icons/BubbleIcon'; diff --git a/apps/tlon-web/src/components/MessageEditor.tsx b/apps/tlon-web/src/components/MessageEditor.tsx index 2f92266d4a..c6bfc2cc4b 100644 --- a/apps/tlon-web/src/components/MessageEditor.tsx +++ b/apps/tlon-web/src/components/MessageEditor.tsx @@ -15,6 +15,7 @@ import Text from '@tiptap/extension-text'; import { Slice } from '@tiptap/pm/model'; import { EditorView } from '@tiptap/pm/view'; import { Editor, EditorContent, JSONContent, useEditor } from '@tiptap/react'; +import { Cite } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import React, { useCallback, useMemo } from 'react'; @@ -29,7 +30,6 @@ import { Shortcuts, refPasteRule } from '@/logic/tiptap'; import { useIsMobile } from '@/logic/useMedia'; import { useCalm } from '@/state/settings'; import { useFileStore } from '@/state/storage'; -import { Cite } from '@/types/channel'; import getMentionPopup from './Mention/MentionPopup'; diff --git a/apps/tlon-web/src/components/References/ContentReference.tsx b/apps/tlon-web/src/components/References/ContentReference.tsx index c3d7973a30..a9775f316e 100644 --- a/apps/tlon-web/src/components/References/ContentReference.tsx +++ b/apps/tlon-web/src/components/References/ContentReference.tsx @@ -1,8 +1,8 @@ +import { Cite } from '@tloncorp/shared/dist/urbit/channel'; import { udToDec } from '@urbit/api'; import React from 'react'; import { nestToFlag } from '@/logic/utils'; -import { Cite } from '@/types/channel'; // eslint-disable-next-line import/no-cycle import AppReference from './AppReference'; diff --git a/apps/tlon-web/src/components/References/CurioReference.tsx b/apps/tlon-web/src/components/References/CurioReference.tsx index cd14b26d80..20ff8dbe80 100644 --- a/apps/tlon-web/src/components/References/CurioReference.tsx +++ b/apps/tlon-web/src/components/References/CurioReference.tsx @@ -1,3 +1,4 @@ +import { imageUrlFromContent } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import React, { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router'; @@ -15,7 +16,6 @@ import { firstInlineSummary } from '@/logic/tiptap'; import getHeapContentType from '@/logic/useHeapContentType'; import { useRemotePost } from '@/state/channel/channel'; import { useChannelPreview, useGang } from '@/state/groups'; -import { imageUrlFromContent } from '@/types/channel'; import ReferenceBar from './ReferenceBar'; import ReferenceInHeap from './ReferenceInHeap'; diff --git a/apps/tlon-web/src/components/References/UnavailableReference.tsx b/apps/tlon-web/src/components/References/UnavailableReference.tsx index ed5b5b95f0..315e2ded47 100644 --- a/apps/tlon-web/src/components/References/UnavailableReference.tsx +++ b/apps/tlon-web/src/components/References/UnavailableReference.tsx @@ -1,8 +1,7 @@ +import { ChannelPreview } from '@tloncorp/shared/dist/urbit/groups'; import bigInt from 'big-integer'; import React from 'react'; -import { ChannelPreview } from '@/types/groups'; - import ExclamationPoint from '../icons/ExclamationPoint'; import ReferenceBar from './ReferenceBar'; diff --git a/apps/tlon-web/src/components/References/WritBaseReference.tsx b/apps/tlon-web/src/components/References/WritBaseReference.tsx index 265e44a315..5f74e95386 100644 --- a/apps/tlon-web/src/components/References/WritBaseReference.tsx +++ b/apps/tlon-web/src/components/References/WritBaseReference.tsx @@ -1,3 +1,4 @@ +import { ReferenceResponse } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import cn from 'classnames'; import React, { useMemo } from 'react'; @@ -10,7 +11,6 @@ import HeapLoadingBlock from '@/heap/HeapLoadingBlock'; import { useChannelFlag } from '@/logic/channel'; import { isImageUrl, nestToFlag } from '@/logic/utils'; import { useChannelPreview, useGang } from '@/state/groups'; -import { ReferenceResponse } from '@/types/channel'; import ShipName from '../ShipName'; import ReferenceBar from './ReferenceBar'; diff --git a/apps/tlon-web/src/components/Settings/BlockedUsersView.tsx b/apps/tlon-web/src/components/Settings/BlockedUsersView.tsx index d830a0c961..ebf7b8cfee 100644 --- a/apps/tlon-web/src/components/Settings/BlockedUsersView.tsx +++ b/apps/tlon-web/src/components/Settings/BlockedUsersView.tsx @@ -1,8 +1,7 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { Helmet } from 'react-helmet'; -import { ViewProps } from '@/types/groups'; - import MobileHeader from '../MobileHeader'; import BlockedUsers from './BlockedUsers'; diff --git a/apps/tlon-web/src/components/Settings/SettingsView.tsx b/apps/tlon-web/src/components/Settings/SettingsView.tsx index 07e5d3667e..78ac805acf 100644 --- a/apps/tlon-web/src/components/Settings/SettingsView.tsx +++ b/apps/tlon-web/src/components/Settings/SettingsView.tsx @@ -1,7 +1,7 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { Helmet } from 'react-helmet'; import { useIsMobile } from '@/logic/useMedia'; -import { ViewProps } from '@/types/groups'; import Layout from '../Layout/Layout'; import MobileHeader from '../MobileHeader'; diff --git a/apps/tlon-web/src/components/Sidebar/GroupList.tsx b/apps/tlon-web/src/components/Sidebar/GroupList.tsx index 6f596f85a3..b3a92d2d44 100644 --- a/apps/tlon-web/src/components/Sidebar/GroupList.tsx +++ b/apps/tlon-web/src/components/Sidebar/GroupList.tsx @@ -1,8 +1,8 @@ +import { Group } from '@tloncorp/shared/dist/urbit/groups'; import React, { ReactNode, useEffect, useMemo, useRef } from 'react'; import { StateSnapshot, Virtuoso, VirtuosoHandle } from 'react-virtuoso'; import { useIsMobile } from '@/logic/useMedia'; -import { Group } from '@/types/groups'; import GangItem from './GangItem'; import GroupListPlaceholder from './GroupListPlaceholder'; diff --git a/apps/tlon-web/src/components/Sidebar/Sidebar.test.tsx b/apps/tlon-web/src/components/Sidebar/Sidebar.test.tsx index a381cb6796..a3104c9177 100644 --- a/apps/tlon-web/src/components/Sidebar/Sidebar.test.tsx +++ b/apps/tlon-web/src/components/Sidebar/Sidebar.test.tsx @@ -1,9 +1,9 @@ /* eslint-disable import/no-extraneous-dependencies */ +import { Group } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { describe, expect, it } from 'vitest'; import { createMockGroup } from '@/mocks/groups'; -import { Group } from '@/types/groups'; import { render } from '../../../test/utils'; import Sidebar from './Sidebar'; diff --git a/apps/tlon-web/src/components/Sidebar/useSearchFilter.ts b/apps/tlon-web/src/components/Sidebar/useSearchFilter.ts index d56553d379..65ee0b2d9f 100644 --- a/apps/tlon-web/src/components/Sidebar/useSearchFilter.ts +++ b/apps/tlon-web/src/components/Sidebar/useSearchFilter.ts @@ -1,10 +1,10 @@ +import { Gangs, Groups } from '@tloncorp/shared/dist/urbit/groups'; import { deSig } from '@urbit/api'; import fuzzy from 'fuzzy'; import _ from 'lodash'; import { useMemo } from 'react'; import { useGangs, useGroups } from '@/state/groups'; -import { Gangs, Groups } from '@/types/groups'; export interface GroupSearchRecord { flag: string; diff --git a/apps/tlon-web/src/components/VolumeSetting.tsx b/apps/tlon-web/src/components/VolumeSetting.tsx index 295e07062c..9dc6b8219f 100644 --- a/apps/tlon-web/src/components/VolumeSetting.tsx +++ b/apps/tlon-web/src/components/VolumeSetting.tsx @@ -1,3 +1,8 @@ +import { + LevelNames, + Scope, + VolumeValue, +} from '@tloncorp/shared/dist/urbit/volume'; import React, { useEffect, useState } from 'react'; import { @@ -7,7 +12,6 @@ import { useRouteGroup, useVolume, } from '@/state/groups'; -import { LevelNames, Scope, VolumeValue } from '@/types/volume'; import RadioGroup, { RadioGroupOption } from './RadioGroup'; diff --git a/apps/tlon-web/src/diary/DiaryChannel.tsx b/apps/tlon-web/src/diary/DiaryChannel.tsx index fb28c92752..b8303160d7 100644 --- a/apps/tlon-web/src/diary/DiaryChannel.tsx +++ b/apps/tlon-web/src/diary/DiaryChannel.tsx @@ -1,4 +1,6 @@ import * as Toast from '@radix-ui/react-toast'; +import { PageTuple } from '@tloncorp/shared/dist/urbit/channel'; +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Helmet } from 'react-helmet'; import { Outlet, useLocation, useNavigate, useParams } from 'react-router'; @@ -21,8 +23,6 @@ import { useUserDiaryDisplayMode, useUserDiarySortMode, } from '@/state/settings'; -import { PageTuple } from '@/types/channel'; -import { ViewProps } from '@/types/groups'; import DiaryChannelListPlaceholder from './DiaryChannelListPlaceholder'; import DiaryHeader from './DiaryHeader'; diff --git a/apps/tlon-web/src/diary/DiaryCommentField.tsx b/apps/tlon-web/src/diary/DiaryCommentField.tsx index d1ab03d37d..dece26125b 100644 --- a/apps/tlon-web/src/diary/DiaryCommentField.tsx +++ b/apps/tlon-web/src/diary/DiaryCommentField.tsx @@ -1,4 +1,6 @@ import { Editor } from '@tiptap/react'; +import { Cite, Kind, Story } from '@tloncorp/shared/dist/urbit/channel'; +import { Inline } from '@tloncorp/shared/dist/urbit/content'; import cn from 'classnames'; import { useCallback, useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; @@ -24,8 +26,6 @@ import { useIsMobile } from '@/logic/useMedia'; import useRequestState from '@/logic/useRequestState'; import { pathToCite } from '@/logic/utils'; import { useAddReplyMutation, useReply } from '@/state/channel/channel'; -import { Cite, Kind, Story } from '@/types/channel'; -import { Inline } from '@/types/content'; interface DiaryCommentFieldProps { flag: string; diff --git a/apps/tlon-web/src/diary/DiaryContent/DiaryContent.test.tsx b/apps/tlon-web/src/diary/DiaryContent/DiaryContent.test.tsx index e49e92b7ac..9006ac44f5 100644 --- a/apps/tlon-web/src/diary/DiaryContent/DiaryContent.test.tsx +++ b/apps/tlon-web/src/diary/DiaryContent/DiaryContent.test.tsx @@ -1,7 +1,6 @@ +import { Inline } from '@tloncorp/shared/dist/urbit/content'; import { describe, expect, it } from 'vitest'; -import { Inline } from '@/types/content'; - import { groupByParagraph } from './DiaryContent'; const br = { break: null }; diff --git a/apps/tlon-web/src/diary/DiaryContent/DiaryContent.tsx b/apps/tlon-web/src/diary/DiaryContent/DiaryContent.tsx index 26f4c28608..aed6ca7cca 100644 --- a/apps/tlon-web/src/diary/DiaryContent/DiaryContent.tsx +++ b/apps/tlon-web/src/diary/DiaryContent/DiaryContent.tsx @@ -1,14 +1,11 @@ -import cn from 'classnames'; -import { toH } from 'hast-to-hyperscript'; -import _ from 'lodash'; -import React from 'react'; -import hoon from 'refractor/lang/hoon.js'; -import { refractor } from 'refractor/lib/common.js'; - -// eslint-disable-next-line import/no-cycle -import ContentReference from '@/components/References/ContentReference'; -import { useIsDark } from '@/logic/useMedia'; -import { Block, Cite, Listing, Story, isCite, isImage } from '@/types/channel'; +import { + Block, + Cite, + Listing, + Story, + isCite, + isImage, +} from '@tloncorp/shared/dist/urbit/channel'; import { Inline, isBlockCode, @@ -19,7 +16,17 @@ import { isItalics, isLink, isStrikethrough, -} from '@/types/content'; +} from '@tloncorp/shared/dist/urbit/content'; +import cn from 'classnames'; +import { toH } from 'hast-to-hyperscript'; +import _ from 'lodash'; +import React from 'react'; +import hoon from 'refractor/lang/hoon.js'; +import { refractor } from 'refractor/lib/common.js'; + +// eslint-disable-next-line import/no-cycle +import ContentReference from '@/components/References/ContentReference'; +import { useIsDark } from '@/logic/useMedia'; import DiaryContentImage from './DiaryContentImage'; diff --git a/apps/tlon-web/src/diary/DiaryHeader.tsx b/apps/tlon-web/src/diary/DiaryHeader.tsx index 33b62df76d..d66fd3c2d0 100644 --- a/apps/tlon-web/src/diary/DiaryHeader.tsx +++ b/apps/tlon-web/src/diary/DiaryHeader.tsx @@ -1,3 +1,4 @@ +import { DisplayMode } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import React, { useState } from 'react'; import { Link } from 'react-router-dom'; @@ -17,7 +18,6 @@ import { useDiarySettings, usePutEntryMutation, } from '@/state/settings'; -import { DisplayMode } from '@/types/channel'; interface DiaryHeaderProps { groupFlag: string; diff --git a/apps/tlon-web/src/diary/DiaryList/DiaryGridItem.tsx b/apps/tlon-web/src/diary/DiaryList/DiaryGridItem.tsx index a2c319748c..ce55a83d10 100644 --- a/apps/tlon-web/src/diary/DiaryList/DiaryGridItem.tsx +++ b/apps/tlon-web/src/diary/DiaryList/DiaryGridItem.tsx @@ -1,10 +1,10 @@ +import { Post } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import { useNavigate } from 'react-router'; import getKindDataFromEssay from '@/logic/getKindData'; import { useIsPostUndelivered, usePostToggler } from '@/state/channel/channel'; import { useCalm } from '@/state/settings'; -import { Post } from '@/types/channel'; import DiaryNoteHeadline from '../DiaryNoteHeadline'; diff --git a/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx b/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx index 9358fd6c94..34c84bb7ec 100644 --- a/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx +++ b/apps/tlon-web/src/diary/DiaryList/DiaryGridView.tsx @@ -1,3 +1,4 @@ +import { PageTuple, Post } from '@tloncorp/shared/dist/urbit/channel'; import { RenderComponentProps, useInfiniteLoader, @@ -9,7 +10,6 @@ import React, { useRef } from 'react'; import DiaryGridItem from '@/diary/DiaryList/DiaryGridItem'; import { useIsMobile } from '@/logic/useMedia'; -import { PageTuple, Post } from '@/types/channel'; interface DiaryGridProps { outlines: PageTuple[]; diff --git a/apps/tlon-web/src/diary/DiaryList/DiaryListItem.tsx b/apps/tlon-web/src/diary/DiaryList/DiaryListItem.tsx index 425d2ac38b..5bad878539 100644 --- a/apps/tlon-web/src/diary/DiaryList/DiaryListItem.tsx +++ b/apps/tlon-web/src/diary/DiaryList/DiaryListItem.tsx @@ -1,3 +1,4 @@ +import { Post } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import { useNavigate } from 'react-router'; @@ -7,7 +8,6 @@ import { useIsPostUndelivered, usePostToggler, } from '@/state/channel/channel'; -import { Post } from '@/types/channel'; interface DiaryListItemProps { note: Post; diff --git a/apps/tlon-web/src/diary/DiaryMarkdownEditor.tsx b/apps/tlon-web/src/diary/DiaryMarkdownEditor.tsx index be91605c82..e2bdf27d17 100644 --- a/apps/tlon-web/src/diary/DiaryMarkdownEditor.tsx +++ b/apps/tlon-web/src/diary/DiaryMarkdownEditor.tsx @@ -1,6 +1,7 @@ // currently importing from tiptap, but this could be imported directly from // prosemirror when/if we ditch tiptap import { Node, DOMParser as PMDomParser, Schema } from '@tiptap/pm/model'; +import { JSONContent } from '@tloncorp/shared/dist/urbit/content'; import { deSig, preSig } from '@urbit/api'; import { marked } from 'marked'; import { @@ -12,7 +13,6 @@ import { useEffect, useState } from 'react'; import ob from 'urbit-ob'; import { PATP_REGEX, REF_REGEX } from '@/logic/utils'; -import { JSONContent } from '@/types/content'; import parserRules from './parserRules'; import schema from './schema'; diff --git a/apps/tlon-web/src/diary/DiaryNote.tsx b/apps/tlon-web/src/diary/DiaryNote.tsx index bb851f9eeb..bca9e17bc3 100644 --- a/apps/tlon-web/src/diary/DiaryNote.tsx +++ b/apps/tlon-web/src/diary/DiaryNote.tsx @@ -1,3 +1,5 @@ +import { Post, Posts } from '@tloncorp/shared/dist/urbit/channel'; +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { udToDec } from '@urbit/api'; import bigInt from 'big-integer'; import { useCallback, useEffect, useMemo } from 'react'; @@ -35,8 +37,6 @@ import { } from '@/state/groups/groups'; import { useDiaryCommentSortMode } from '@/state/settings'; import { useConnectivityCheck } from '@/state/vitals'; -import { Post, Posts } from '@/types/channel'; -import { ViewProps } from '@/types/groups'; import DiaryCommentField from './DiaryCommentField'; import DiaryContent from './DiaryContent/DiaryContent'; diff --git a/apps/tlon-web/src/diary/DiaryNoteHeadline.tsx b/apps/tlon-web/src/diary/DiaryNoteHeadline.tsx index 1acf9f976e..bcfce74f1d 100644 --- a/apps/tlon-web/src/diary/DiaryNoteHeadline.tsx +++ b/apps/tlon-web/src/diary/DiaryNoteHeadline.tsx @@ -1,3 +1,4 @@ +import { PostEssay } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import { format } from 'date-fns'; import { useNavigate } from 'react-router-dom'; @@ -14,7 +15,6 @@ import getKindDataFromEssay from '@/logic/getKindData'; import { usePostToggler } from '@/state/channel/channel'; import { useAmAdmin, useRouteGroup } from '@/state/groups/groups'; import { useCalm } from '@/state/settings'; -import { PostEssay } from '@/types/channel'; import useDiaryActions from './useDiaryActions'; diff --git a/apps/tlon-web/src/diary/DiaryVerse.tsx b/apps/tlon-web/src/diary/DiaryVerse.tsx index 37afb77f60..3ea0961058 100644 --- a/apps/tlon-web/src/diary/DiaryVerse.tsx +++ b/apps/tlon-web/src/diary/DiaryVerse.tsx @@ -1,5 +1,6 @@ +import { Verse } from '@tloncorp/shared/dist/urbit/channel'; + import { InlineContent } from '@/chat/ChatContent/ChatContent'; -import { Verse } from '@/types/channel'; export interface DiaryVerseProps { verse: Verse; diff --git a/apps/tlon-web/src/diary/NoteReactions/NoteReactions.tsx b/apps/tlon-web/src/diary/NoteReactions/NoteReactions.tsx index e4b0f3e787..171df48c19 100644 --- a/apps/tlon-web/src/diary/NoteReactions/NoteReactions.tsx +++ b/apps/tlon-web/src/diary/NoteReactions/NoteReactions.tsx @@ -1,10 +1,10 @@ +import { PostSeal } from '@tloncorp/shared/dist/urbit/channel'; import _ from 'lodash'; import { useCallback, useState } from 'react'; import EmojiPicker from '@/components/EmojiPicker'; import AddReactIcon from '@/components/icons/AddReactIcon'; import { useAddPostReactMutation } from '@/state/channel/channel'; -import { PostSeal } from '@/types/channel'; import NoteReaction from './NoteReaction'; diff --git a/apps/tlon-web/src/diary/diary-add-note.tsx b/apps/tlon-web/src/diary/diary-add-note.tsx index 4e9c1b4e4f..bf3dad762b 100644 --- a/apps/tlon-web/src/diary/diary-add-note.tsx +++ b/apps/tlon-web/src/diary/diary-add-note.tsx @@ -1,3 +1,5 @@ +import { constructStory } from '@tloncorp/shared/dist/urbit/channel'; +import { JSONContent } from '@tloncorp/shared/dist/urbit/content'; import cn from 'classnames'; import { isFirstDayOfMonth } from 'date-fns'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -29,8 +31,6 @@ import { } from '@/state/channel/channel'; import { useGroup, useGroupChannel, useRouteGroup } from '@/state/groups'; import { useMarkdownInDiaries, usePutEntryMutation } from '@/state/settings'; -import { constructStory } from '@/types/channel'; -import { JSONContent } from '@/types/content'; import DiaryInlineEditor, { useDiaryInlineEditor } from './DiaryInlineEditor'; import DiaryMarkdownEditor from './DiaryMarkdownEditor'; diff --git a/apps/tlon-web/src/dms/DMHero.tsx b/apps/tlon-web/src/dms/DMHero.tsx index 23843bbfbf..b5df7e35e4 100644 --- a/apps/tlon-web/src/dms/DMHero.tsx +++ b/apps/tlon-web/src/dms/DMHero.tsx @@ -1,8 +1,8 @@ +import { Contact } from '@tloncorp/shared/dist/urbit/contact'; import { Patp } from '@urbit/api'; import { useLocation } from 'react-router'; import { useModalNavigate } from '@/logic/routing'; -import { Contact } from '@/types/contact'; import Avatar from '../components/Avatar'; import ShipName from '../components/ShipName'; diff --git a/apps/tlon-web/src/dms/DMThread.tsx b/apps/tlon-web/src/dms/DMThread.tsx index 2a150d0b4f..2cb4259c2d 100644 --- a/apps/tlon-web/src/dms/DMThread.tsx +++ b/apps/tlon-web/src/dms/DMThread.tsx @@ -1,3 +1,4 @@ +import { ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import cn from 'classnames'; import React, { @@ -36,7 +37,6 @@ import { useSendReplyMutation, useWrit, } from '@/state/chat'; -import { ReplyTuple } from '@/types/channel'; export default function DMThread() { const { chShip, ship, chName, idTime, idShip } = useParams<{ diff --git a/apps/tlon-web/src/dms/Dm.tsx b/apps/tlon-web/src/dms/Dm.tsx index d765d3b416..91c293dd79 100644 --- a/apps/tlon-web/src/dms/Dm.tsx +++ b/apps/tlon-web/src/dms/Dm.tsx @@ -1,3 +1,4 @@ +import { Contact } from '@tloncorp/shared/dist/urbit/contact'; import cn from 'classnames'; import React, { useCallback, useMemo, useRef } from 'react'; import { @@ -35,7 +36,6 @@ import { useDmIsPending, useDmUnread, useSendMessage } from '@/state/chat'; import { useContact } from '@/state/contact'; import { useNegotiate } from '@/state/negotiation'; import { useConnectivityCheck } from '@/state/vitals'; -import { Contact } from '@/types/contact'; import DmSearch from './DmSearch'; import MessageSelector from './MessageSelector'; diff --git a/apps/tlon-web/src/dms/DmWindow.tsx b/apps/tlon-web/src/dms/DmWindow.tsx index 89ca068afc..24d08425a2 100644 --- a/apps/tlon-web/src/dms/DmWindow.tsx +++ b/apps/tlon-web/src/dms/DmWindow.tsx @@ -1,3 +1,4 @@ +import { WritTuple } from '@tloncorp/shared/dist/urbit/dms'; import { udToDec } from '@urbit/api'; import bigInt from 'big-integer'; import { ReactElement, useCallback, useEffect, useMemo, useRef } from 'react'; @@ -12,7 +13,6 @@ import ArrowS16Icon from '@/components/icons/ArrowS16Icon'; import { useIsScrolling } from '@/logic/scroll'; import { getPatdaParts, log } from '@/logic/utils'; import { useInfiniteDMs, useMarkDmReadMutation } from '@/state/chat'; -import { WritTuple } from '@/types/dms'; interface DmWindowProps { whom: string; diff --git a/apps/tlon-web/src/dms/MessagesList.tsx b/apps/tlon-web/src/dms/MessagesList.tsx index a639ed0583..d1a1d990a3 100644 --- a/apps/tlon-web/src/dms/MessagesList.tsx +++ b/apps/tlon-web/src/dms/MessagesList.tsx @@ -1,3 +1,5 @@ +import { Unread } from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; import { deSig } from '@urbit/api'; import React, { PropsWithChildren, useEffect, useMemo, useRef } from 'react'; import { StateSnapshot, Virtuoso, VirtuosoHandle } from 'react-virtuoso'; @@ -12,8 +14,6 @@ import { useContacts } from '@/state/contact'; import { useGroups } from '@/state/groups'; import { usePinnedChats } from '@/state/pins'; import { SidebarFilter, filters } from '@/state/settings'; -import { Unread } from '@/types/channel'; -import { DMUnread } from '@/types/dms'; import { useDmUnreads, diff --git a/apps/tlon-web/src/dms/MultiDMInfoForm.tsx b/apps/tlon-web/src/dms/MultiDMInfoForm.tsx index 4f404af29d..1f39422f36 100644 --- a/apps/tlon-web/src/dms/MultiDMInfoForm.tsx +++ b/apps/tlon-web/src/dms/MultiDMInfoForm.tsx @@ -1,4 +1,5 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; import React, { useCallback, useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom'; @@ -13,7 +14,6 @@ import GroupAvatar from '@/groups/GroupAvatar'; import { Status } from '@/logic/status'; import { isValidUrl } from '@/logic/utils'; import { useEditMultiDm, useMultiDm } from '@/state/chat'; -import { GroupMeta } from '@/types/groups'; interface MultiDMInfoFormProps { setEditing: (editing: boolean) => void; diff --git a/apps/tlon-web/src/dms/MultiDm.tsx b/apps/tlon-web/src/dms/MultiDm.tsx index 6ef06297e9..9f886df574 100644 --- a/apps/tlon-web/src/dms/MultiDm.tsx +++ b/apps/tlon-web/src/dms/MultiDm.tsx @@ -1,3 +1,4 @@ +import { Club } from '@tloncorp/shared/dist/urbit/dms'; import cn from 'classnames'; import React, { useCallback, useRef } from 'react'; import { @@ -27,7 +28,6 @@ import useShowTabBar from '@/logic/useShowTabBar'; import { dmListPath, pluralize } from '@/logic/utils'; import { useMultiDm, useMultiDmIsPending, useSendMessage } from '@/state/chat'; import { useNegotiateMulti } from '@/state/negotiation'; -import { Club } from '@/types/dms'; import DmOptions from './DMOptions'; import DmSearch from './DmSearch'; diff --git a/apps/tlon-web/src/dms/MultiDmHero.tsx b/apps/tlon-web/src/dms/MultiDmHero.tsx index c5a51481cb..5dc2b2490d 100644 --- a/apps/tlon-web/src/dms/MultiDmHero.tsx +++ b/apps/tlon-web/src/dms/MultiDmHero.tsx @@ -1,10 +1,10 @@ +import { Club } from '@tloncorp/shared/dist/urbit/dms'; import cn from 'classnames'; import React from 'react'; import ClubName from '@/components/ClubName'; import { pluralize } from '../logic/utils'; -import { Club } from '../types/dms'; import PendingIndicator from './MultiDMPendingIndicator'; import MultiDmAvatar from './MultiDmAvatar'; diff --git a/apps/tlon-web/src/groups/AddGroup/SearchResults.tsx b/apps/tlon-web/src/groups/AddGroup/SearchResults.tsx index fa9037bcdc..ac45373db9 100644 --- a/apps/tlon-web/src/groups/AddGroup/SearchResults.tsx +++ b/apps/tlon-web/src/groups/AddGroup/SearchResults.tsx @@ -1,3 +1,4 @@ +import { Gang } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import Avatar from '@/components/Avatar'; @@ -6,7 +7,6 @@ import ShipName from '@/components/ShipName'; import SidebarItem from '@/components/Sidebar/SidebarItem'; import useGroupPrivacy from '@/logic/useGroupPrivacy'; import { useGang, useGroup } from '@/state/groups'; -import { Gang } from '@/types/groups'; import GroupAvatar from '../GroupAvatar'; diff --git a/apps/tlon-web/src/groups/ChannelsList/ChannelJoinSelector.tsx b/apps/tlon-web/src/groups/ChannelsList/ChannelJoinSelector.tsx index 7c5ccf500a..e24ea3190d 100644 --- a/apps/tlon-web/src/groups/ChannelsList/ChannelJoinSelector.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/ChannelJoinSelector.tsx @@ -1,8 +1,8 @@ +import { ChannelFormSchema } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { useFormContext } from 'react-hook-form'; import CheckIcon from '@/components/icons/CheckIcon'; -import { ChannelFormSchema } from '@/types/groups'; export default function ChannelJoinSelector() { const { register, watch } = useFormContext(); diff --git a/apps/tlon-web/src/groups/ChannelsList/ChannelPermsSelector.tsx b/apps/tlon-web/src/groups/ChannelsList/ChannelPermsSelector.tsx index 6f3b841de8..44c46d4336 100644 --- a/apps/tlon-web/src/groups/ChannelsList/ChannelPermsSelector.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/ChannelPermsSelector.tsx @@ -1,4 +1,8 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { + ChannelFormSchema, + ChannelPrivacyType, +} from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; import _ from 'lodash'; @@ -7,7 +11,6 @@ import { useFormContext } from 'react-hook-form'; import CheckIcon from '@/components/icons/CheckIcon'; import { useGroup, useRouteGroup } from '@/state/groups'; -import { ChannelFormSchema, ChannelPrivacyType } from '@/types/groups'; interface ChannelPrivacySetting { title: string; diff --git a/apps/tlon-web/src/groups/ChannelsList/ChannelsListItem.tsx b/apps/tlon-web/src/groups/ChannelsList/ChannelsListItem.tsx index 2d70361e83..282267f0de 100644 --- a/apps/tlon-web/src/groups/ChannelsList/ChannelsListItem.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/ChannelsListItem.tsx @@ -1,3 +1,4 @@ +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useCallback, useState } from 'react'; import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd'; @@ -25,7 +26,6 @@ import { useDeleteChannelMutation, useRouteGroup, } from '@/state/groups'; -import { GroupChannel } from '@/types/groups'; interface ChannelsListItemProps { nest: string; diff --git a/apps/tlon-web/src/groups/ChannelsList/DeleteChannelModal.tsx b/apps/tlon-web/src/groups/ChannelsList/DeleteChannelModal.tsx index 3acdf41644..7a8259f26a 100644 --- a/apps/tlon-web/src/groups/ChannelsList/DeleteChannelModal.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/DeleteChannelModal.tsx @@ -1,9 +1,9 @@ +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import Dialog from '@/components/Dialog'; import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; import { Status } from '@/logic/status'; -import { GroupChannel } from '@/types/groups'; interface DeleteChannelModalProps { deleteChannelIsOpen: boolean; diff --git a/apps/tlon-web/src/groups/ChannelsList/EditChannelModal.tsx b/apps/tlon-web/src/groups/ChannelsList/EditChannelModal.tsx index 46a22797c1..d5396bc9fe 100644 --- a/apps/tlon-web/src/groups/ChannelsList/EditChannelModal.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/EditChannelModal.tsx @@ -1,9 +1,9 @@ +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import EditChannelForm from '@/channels/EditChannelForm'; import Dialog from '@/components/Dialog'; import { prettyChannelTypeName } from '@/logic/channel'; -import { GroupChannel } from '@/types/groups'; interface EditChannelModalProps { nest: string; diff --git a/apps/tlon-web/src/groups/ChannelsList/GroupChannelManager.tsx b/apps/tlon-web/src/groups/ChannelsList/GroupChannelManager.tsx index ab9da1437c..1acf840e41 100644 --- a/apps/tlon-web/src/groups/ChannelsList/GroupChannelManager.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/GroupChannelManager.tsx @@ -1,9 +1,9 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { Helmet } from 'react-helmet'; import MobileHeader from '@/components/MobileHeader'; import { useIsMobile } from '@/logic/useMedia'; import { useGroup, useRouteGroup } from '@/state/groups/groups'; -import { ViewProps } from '@/types/groups'; import GroupActions from '../GroupActions'; import GroupAvatar from '../GroupAvatar'; diff --git a/apps/tlon-web/src/groups/ChannelsList/SectionNameEditInput.tsx b/apps/tlon-web/src/groups/ChannelsList/SectionNameEditInput.tsx index 190d9ee9a6..346a5ba448 100644 --- a/apps/tlon-web/src/groups/ChannelsList/SectionNameEditInput.tsx +++ b/apps/tlon-web/src/groups/ChannelsList/SectionNameEditInput.tsx @@ -1,3 +1,4 @@ +import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { useForm } from 'react-hook-form'; @@ -8,7 +9,6 @@ import { useGroupMoveZoneMutation, useRouteGroup, } from '@/state/groups'; -import { GroupMeta } from '@/types/groups'; interface HandleSectionNameEditInputProps { handleEditingChange: () => void; diff --git a/apps/tlon-web/src/groups/ChannelsList/types.ts b/apps/tlon-web/src/groups/ChannelsList/types.ts index b9304c5a54..66c43b5a26 100644 --- a/apps/tlon-web/src/groups/ChannelsList/types.ts +++ b/apps/tlon-web/src/groups/ChannelsList/types.ts @@ -1,4 +1,4 @@ -import { GroupChannel } from '@/types/groups'; +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; export interface SectionMap { [key: string]: SectionListItem; diff --git a/apps/tlon-web/src/groups/FindGroups.tsx b/apps/tlon-web/src/groups/FindGroups.tsx index 01d62e3de3..5f0084349e 100644 --- a/apps/tlon-web/src/groups/FindGroups.tsx +++ b/apps/tlon-web/src/groups/FindGroups.tsx @@ -1,3 +1,4 @@ +import { Gangs, ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -17,7 +18,6 @@ import { usePendingGangsWithoutClaim, } from '@/state/groups'; import { useConnectivityCheck } from '@/state/vitals'; -import { Gangs, ViewProps } from '@/types/groups'; import GroupJoinList, { GroupJoinItem } from './GroupJoinList'; import GroupJoinListPlaceholder from './GroupJoinListPlaceholder'; diff --git a/apps/tlon-web/src/groups/GangPreview/GangPreview.tsx b/apps/tlon-web/src/groups/GangPreview/GangPreview.tsx index c40884f02c..902679d5b2 100644 --- a/apps/tlon-web/src/groups/GangPreview/GangPreview.tsx +++ b/apps/tlon-web/src/groups/GangPreview/GangPreview.tsx @@ -1,6 +1,6 @@ +import { GroupPreview } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; -import { GroupPreview } from '../../types/groups'; import GroupAvatar from '../GroupAvatar'; export default function GangPreview(props: { preview: GroupPreview }) { diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupInfo.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupInfo.tsx index fb8549802b..2db5ec74d9 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupInfo.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupInfo.tsx @@ -1,9 +1,9 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { Helmet } from 'react-helmet'; import Dialog from '@/components/Dialog'; import { useDismissNavigate } from '@/logic/routing'; -import { ViewProps } from '@/types/groups'; import { useGroup, useRouteGroup } from '../../state/groups/groups'; import GroupSummary from '../GroupSummary'; diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupInfoEditor.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupInfoEditor.tsx index 7b5b831fa5..0b669c0973 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupInfoEditor.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupInfoEditor.tsx @@ -1,3 +1,9 @@ +import { + GroupFormSchema, + GroupMeta, + PrivacyType, + ViewProps, +} from '@tloncorp/shared/dist/urbit/groups'; import React, { useCallback } from 'react'; import { Helmet } from 'react-helmet'; import { FormProvider, useForm } from 'react-hook-form'; @@ -16,12 +22,6 @@ import { useRouteGroup, } from '@/state/groups'; import { useLure } from '@/state/lure/lure'; -import { - GroupFormSchema, - GroupMeta, - PrivacyType, - ViewProps, -} from '@/types/groups'; import GroupInfoFields from './GroupInfoFields'; diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupInfoFields.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupInfoFields.tsx index 1cd0e48fc8..1f7d1b2760 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupInfoFields.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupInfoFields.tsx @@ -1,3 +1,4 @@ +import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; @@ -9,7 +10,6 @@ import GroupInfoPreview from '@/groups/NewGroup/GroupInfoPreview'; import { useIsMobile } from '@/logic/useMedia'; import { isValidUrl } from '@/logic/utils'; import { useCalm } from '@/state/settings'; -import { GroupMeta } from '@/types/groups'; export default function GroupInfoFields() { const { diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupInvitesPrivacy.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupInvitesPrivacy.tsx index fbc6840b6a..77584d7d88 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupInvitesPrivacy.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupInvitesPrivacy.tsx @@ -1,10 +1,10 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { Helmet } from 'react-helmet'; import MobileHeader from '@/components/MobileHeader'; import { useIsMobile } from '@/logic/useMedia'; import { useGroup, useRouteGroup } from '@/state/groups'; -import { ViewProps } from '@/types/groups'; import { GroupInviteBlock } from '../GroupInviteDialog'; import LureInviteBlock from '../LureInviteBlock'; diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupMemberItem.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupMemberItem.tsx index db012999f4..161a85fc5e 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupMemberItem.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupMemberItem.tsx @@ -1,4 +1,5 @@ import * as Dropdown from '@radix-ui/react-dropdown-menu'; +import { Vessel } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import _ from 'lodash'; import React, { useCallback, useRef, useState } from 'react'; @@ -27,7 +28,6 @@ import { useSects, useVessel, } from '@/state/groups'; -import { Vessel } from '@/types/groups'; interface GroupMemberItemProps { member: string; diff --git a/apps/tlon-web/src/groups/GroupAdmin/GroupMembers.tsx b/apps/tlon-web/src/groups/GroupAdmin/GroupMembers.tsx index 870704fde3..54ead7d199 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/GroupMembers.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/GroupMembers.tsx @@ -1,10 +1,10 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { Helmet } from 'react-helmet'; import MobileHeader from '@/components/MobileHeader'; import { useIsMobile } from '@/logic/useMedia'; import { useGroup, useRouteGroup } from '@/state/groups/groups'; -import { ViewProps } from '@/types/groups'; import GroupMemberManager from './GroupMemberManager'; import GroupPendingManager from './GroupPendingManager'; diff --git a/apps/tlon-web/src/groups/GroupAdmin/PrivacySelector.tsx b/apps/tlon-web/src/groups/GroupAdmin/PrivacySelector.tsx index 58e2fc899c..0a65deeb85 100644 --- a/apps/tlon-web/src/groups/GroupAdmin/PrivacySelector.tsx +++ b/apps/tlon-web/src/groups/GroupAdmin/PrivacySelector.tsx @@ -1,3 +1,8 @@ +import { + GroupFormSchema, + GroupMeta, + PrivacyType, +} from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useCallback } from 'react'; import { FormProvider, useForm, useFormContext } from 'react-hook-form'; @@ -17,7 +22,6 @@ import { useRouteGroup, } from '@/state/groups'; import { useLure } from '@/state/lure/lure'; -import { GroupFormSchema, GroupMeta, PrivacyType } from '@/types/groups'; interface PrivacySetting { title: string; diff --git a/apps/tlon-web/src/groups/GroupJoinList.tsx b/apps/tlon-web/src/groups/GroupJoinList.tsx index cf45c49e98..5394aa4aec 100644 --- a/apps/tlon-web/src/groups/GroupJoinList.tsx +++ b/apps/tlon-web/src/groups/GroupJoinList.tsx @@ -1,10 +1,10 @@ +import { Gang, Gangs } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; import SidebarItem from '@/components/Sidebar/SidebarItem'; import { matchesBans } from '@/logic/utils'; import { useGang } from '@/state/groups'; -import { Gang, Gangs } from '@/types/groups'; import GroupAvatar from './GroupAvatar'; import useGroupJoin from './useGroupJoin'; diff --git a/apps/tlon-web/src/groups/GroupSidebar/ChannelList.test.tsx b/apps/tlon-web/src/groups/GroupSidebar/ChannelList.test.tsx index 4c388ca482..c52d80d9d0 100644 --- a/apps/tlon-web/src/groups/GroupSidebar/ChannelList.test.tsx +++ b/apps/tlon-web/src/groups/GroupSidebar/ChannelList.test.tsx @@ -1,9 +1,9 @@ /* eslint-disable import/no-extraneous-dependencies */ +import { Group } from '@tloncorp/shared/dist/urbit/groups'; import React from 'react'; import { describe, expect, it } from 'vitest'; import { createChannel, createMockGroup } from '@/mocks/groups'; -import { Group } from '@/types/groups'; import { render } from '../../../test/utils'; import ChannelList from './ChannelList'; diff --git a/apps/tlon-web/src/groups/GroupSidebar/ChannelList.tsx b/apps/tlon-web/src/groups/GroupSidebar/ChannelList.tsx index 63b175f463..522ac4bd7c 100644 --- a/apps/tlon-web/src/groups/GroupSidebar/ChannelList.tsx +++ b/apps/tlon-web/src/groups/GroupSidebar/ChannelList.tsx @@ -1,3 +1,4 @@ +import { GroupChannel } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import React, { useCallback, @@ -34,7 +35,6 @@ import { useGroupFlag, useVessel, } from '@/state/groups'; -import { GroupChannel } from '@/types/groups'; const UNZONED = 'default'; diff --git a/apps/tlon-web/src/groups/GroupSummary.tsx b/apps/tlon-web/src/groups/GroupSummary.tsx index c8c30225fc..a6bbfe4c12 100644 --- a/apps/tlon-web/src/groups/GroupSummary.tsx +++ b/apps/tlon-web/src/groups/GroupSummary.tsx @@ -1,3 +1,5 @@ +import { GroupPreview } from '@tloncorp/shared/dist/urbit/groups'; + import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; import ShipConnection from '@/components/ShipConnection'; import ShipName from '@/components/ShipName'; @@ -9,7 +11,6 @@ import useGroupPrivacy from '@/logic/useGroupPrivacy'; import { getFlagParts } from '@/logic/utils'; import { useGroup } from '@/state/groups'; import { useConnectivityCheck } from '@/state/vitals'; -import { GroupPreview } from '@/types/groups'; import GroupHostConnection from './GroupHostConnection'; diff --git a/apps/tlon-web/src/groups/GroupVolumeDialog.tsx b/apps/tlon-web/src/groups/GroupVolumeDialog.tsx index aef47cb057..efb9e54b1b 100644 --- a/apps/tlon-web/src/groups/GroupVolumeDialog.tsx +++ b/apps/tlon-web/src/groups/GroupVolumeDialog.tsx @@ -1,10 +1,10 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import { Helmet } from 'react-helmet'; import Dialog from '@/components/Dialog'; import VolumeSetting from '@/components/VolumeSetting'; import { useDismissNavigate } from '@/logic/routing'; import { useGroup, useRouteGroup } from '@/state/groups'; -import { ViewProps } from '@/types/groups'; import GroupSummary from './GroupSummary'; diff --git a/apps/tlon-web/src/groups/LureAutojoiner.tsx b/apps/tlon-web/src/groups/LureAutojoiner.tsx index 73c017ca0b..84101d5358 100644 --- a/apps/tlon-web/src/groups/LureAutojoiner.tsx +++ b/apps/tlon-web/src/groups/LureAutojoiner.tsx @@ -1,3 +1,4 @@ +import { Gangs } from '@tloncorp/shared/dist/urbit/groups'; import cookies from 'browser-cookies'; import { useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router'; @@ -7,7 +8,6 @@ import { useGroupJoinMutation, usePendingGangsWithoutClaim, } from '@/state/groups'; -import { Gangs } from '@/types/groups'; export default function LureAutojoiner(): React.ReactElement { const { mutateAsync: joinMutation } = useGroupJoinMutation(); diff --git a/apps/tlon-web/src/groups/LureInviteBlock.tsx b/apps/tlon-web/src/groups/LureInviteBlock.tsx index c09175f8c1..5cd4dcdc8b 100644 --- a/apps/tlon-web/src/groups/LureInviteBlock.tsx +++ b/apps/tlon-web/src/groups/LureInviteBlock.tsx @@ -1,3 +1,4 @@ +import { Group } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import { useEffect } from 'react'; @@ -5,7 +6,6 @@ import QRWidget, { QRWidgetPlaceholder } from '@/components/QRWidget'; import CheckIcon from '@/components/icons/CheckIcon'; import { isGroupHost } from '@/logic/utils'; import { useLureLinkStatus } from '@/state/lure/lure'; -import { Group } from '@/types/groups'; interface LureInviteBlock { flag: string; diff --git a/apps/tlon-web/src/groups/NewGroup/NewGroup.tsx b/apps/tlon-web/src/groups/NewGroup/NewGroup.tsx index 32eb133ddf..adc6bd6aae 100644 --- a/apps/tlon-web/src/groups/NewGroup/NewGroup.tsx +++ b/apps/tlon-web/src/groups/NewGroup/NewGroup.tsx @@ -1,3 +1,4 @@ +import { Cordon, GroupFormSchema } from '@tloncorp/shared/dist/urbit/groups'; import { ReactElement, useCallback, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; @@ -8,7 +9,6 @@ import NewGroupInvite from '@/groups/NewGroup/NewGroupInvite'; import NewGroupPrivacy from '@/groups/NewGroup/NewGroupPrivacy'; import { strToSym } from '@/logic/utils'; import { useCreateGroupMutation } from '@/state/groups'; -import { Cordon, GroupFormSchema } from '@/types/groups'; export type Role = 'Member' | 'Admin'; diff --git a/apps/tlon-web/src/groups/NewGroup/NewGroupInvite.tsx b/apps/tlon-web/src/groups/NewGroup/NewGroupInvite.tsx index e245535b03..e74e6abd0d 100644 --- a/apps/tlon-web/src/groups/NewGroup/NewGroupInvite.tsx +++ b/apps/tlon-web/src/groups/NewGroup/NewGroupInvite.tsx @@ -1,4 +1,5 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; +import { PrivacyType } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import _ from 'lodash'; import React, { useCallback, useState } from 'react'; @@ -13,7 +14,6 @@ import CaretDownIcon from '@/components/icons/CaretDownIcon'; import X16Icon from '@/components/icons/X16Icon'; import XIcon from '@/components/icons/XIcon'; import { Status } from '@/logic/status'; -import { PrivacyType } from '@/types/groups'; interface NewGroupInviteProps { groupName: string; diff --git a/apps/tlon-web/src/groups/RoleInput/RoleSelector.tsx b/apps/tlon-web/src/groups/RoleInput/RoleSelector.tsx index 63e73e7ade..b4060a82eb 100644 --- a/apps/tlon-web/src/groups/RoleInput/RoleSelector.tsx +++ b/apps/tlon-web/src/groups/RoleInput/RoleSelector.tsx @@ -1,3 +1,4 @@ +import { Vessel } from '@tloncorp/shared/dist/urbit/groups'; import { MouseEvent, useCallback, useState } from 'react'; import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; @@ -10,7 +11,6 @@ import { useGroupSectMutation, useVessel, } from '@/state/groups'; -import { Vessel } from '@/types/groups'; export default function RoleSelect({ role, diff --git a/apps/tlon-web/src/groups/useGroupJoin.ts b/apps/tlon-web/src/groups/useGroupJoin.ts index 56d582c00c..5f29a168c5 100644 --- a/apps/tlon-web/src/groups/useGroupJoin.ts +++ b/apps/tlon-web/src/groups/useGroupJoin.ts @@ -1,3 +1,4 @@ +import { Gang, Group, PrivacyType } from '@tloncorp/shared/dist/urbit/groups'; import { useCallback, useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; @@ -13,7 +14,6 @@ import { } from '@/state/groups'; import { useSawRopeMutation } from '@/state/hark'; import { useNewGroupFlags, usePutEntryMutation } from '@/state/settings'; -import { Gang, Group, PrivacyType } from '@/types/groups'; function getButtonText( privacy: PrivacyType, diff --git a/apps/tlon-web/src/heap/EditCurioForm.tsx b/apps/tlon-web/src/heap/EditCurioForm.tsx index 3fff5aef9b..6df3079008 100644 --- a/apps/tlon-web/src/heap/EditCurioForm.tsx +++ b/apps/tlon-web/src/heap/EditCurioForm.tsx @@ -1,5 +1,10 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; import { JSONContent } from '@tiptap/core'; +import { + chatStoryFromStory, + storyFromChatStory, +} from '@tloncorp/shared/dist/urbit/channel'; +import { Inline } from '@tloncorp/shared/dist/urbit/content'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router'; @@ -14,8 +19,6 @@ import useRequestState from '@/logic/useRequestState'; import { isLinkCurio, isValidUrl } from '@/logic/utils'; import { useEditPostMutation, usePost } from '@/state/channel/channel'; import { useRouteGroup } from '@/state/groups'; -import { chatStoryFromStory, storyFromChatStory } from '@/types/channel'; -import { Inline } from '@/types/content'; import HeapTextInput from './HeapTextInput'; import useCurioActions from './useCurioActions'; diff --git a/apps/tlon-web/src/heap/HeapBlock.tsx b/apps/tlon-web/src/heap/HeapBlock.tsx index f5243baa49..fa349b7869 100644 --- a/apps/tlon-web/src/heap/HeapBlock.tsx +++ b/apps/tlon-web/src/heap/HeapBlock.tsx @@ -1,3 +1,9 @@ +import { + Post, + Story, + imageUrlFromContent, + isCite, +} from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import { formatDistanceToNow } from 'date-fns'; import React, { useCallback, useEffect, useState } from 'react'; @@ -34,7 +40,6 @@ import { useRouteGroup, } from '@/state/groups/groups'; import { useCalm } from '@/state/settings'; -import { Post, Story, imageUrlFromContent, isCite } from '@/types/channel'; import useCurioActions from './useCurioActions'; diff --git a/apps/tlon-web/src/heap/HeapChannel.tsx b/apps/tlon-web/src/heap/HeapChannel.tsx index f3a04c4030..44956ecb83 100644 --- a/apps/tlon-web/src/heap/HeapChannel.tsx +++ b/apps/tlon-web/src/heap/HeapChannel.tsx @@ -1,4 +1,6 @@ import * as Toast from '@radix-ui/react-toast'; +import { PageTuple, Post } from '@tloncorp/shared/dist/urbit/channel'; +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import bigInt from 'big-integer'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -20,8 +22,6 @@ import { useInfinitePosts, useMarkReadMutation } from '@/state/channel/channel'; import { useRouteGroup } from '@/state/groups/groups'; import { useHeapDisplayMode, useHeapSortMode } from '@/state/settings'; import { useUploader } from '@/state/storage'; -import { PageTuple, Post } from '@/types/channel'; -import { ViewProps } from '@/types/groups'; import HeapHeader from './HeapHeader'; import HeapPlaceholder from './HeapPlaceholder'; diff --git a/apps/tlon-web/src/heap/HeapContent.tsx b/apps/tlon-web/src/heap/HeapContent.tsx index c8daa30e28..25042f3734 100644 --- a/apps/tlon-web/src/heap/HeapContent.tsx +++ b/apps/tlon-web/src/heap/HeapContent.tsx @@ -1,6 +1,9 @@ // eslint-disable-next-line import/no-cycle -import ContentReference from '@/components/References/ContentReference'; -import { Story, VerseBlock, VerseInline } from '@/types/channel'; +import { + Story, + VerseBlock, + VerseInline, +} from '@tloncorp/shared/dist/urbit/channel'; import { Inline, isBlockCode, @@ -11,7 +14,9 @@ import { isItalics, isLink, isStrikethrough, -} from '@/types/content'; +} from '@tloncorp/shared/dist/urbit/content'; + +import ContentReference from '@/components/References/ContentReference'; interface HeapContentProps { content: Story; diff --git a/apps/tlon-web/src/heap/HeapDetail.tsx b/apps/tlon-web/src/heap/HeapDetail.tsx index 19343b4b41..5489f751bd 100644 --- a/apps/tlon-web/src/heap/HeapDetail.tsx +++ b/apps/tlon-web/src/heap/HeapDetail.tsx @@ -1,3 +1,5 @@ +import { Post, newReplyMap } from '@tloncorp/shared/dist/urbit/channel'; +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import bigInt from 'big-integer'; import cn from 'classnames'; import { Helmet } from 'react-helmet'; @@ -20,8 +22,6 @@ import { usePost, } from '@/state/channel/channel'; import { useRouteGroup } from '@/state/groups'; -import { Post, newReplyMap } from '@/types/channel'; -import { ViewProps } from '@/types/groups'; import HeapDetailBody from './HeapDetail/HeapDetailBody'; import HeapDetailHeader from './HeapDetail/HeapDetailHeader'; diff --git a/apps/tlon-web/src/heap/HeapDetail/HeapDetailBody.tsx b/apps/tlon-web/src/heap/HeapDetail/HeapDetailBody.tsx index ac99fb5d03..f6a584efe5 100644 --- a/apps/tlon-web/src/heap/HeapDetail/HeapDetailBody.tsx +++ b/apps/tlon-web/src/heap/HeapDetail/HeapDetailBody.tsx @@ -1,3 +1,9 @@ +import { + PostEssay, + VerseBlock, + imageUrlFromContent, + isCite, +} from '@tloncorp/shared/dist/urbit/channel'; import { useEffect } from 'react'; import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; @@ -12,12 +18,6 @@ import { useIsMobile } from '@/logic/useMedia'; import { validOembedCheck } from '@/logic/utils'; import { useEmbed } from '@/state/embed'; import { useCalm } from '@/state/settings'; -import { - PostEssay, - VerseBlock, - imageUrlFromContent, - isCite, -} from '@/types/channel'; import HeapVideoPlayer from '../HeapVideoPlayer'; import HeapVimeoPlayer from '../HeapVimeoPlayer'; diff --git a/apps/tlon-web/src/heap/HeapDetail/HeapDetailHeader.tsx b/apps/tlon-web/src/heap/HeapDetail/HeapDetailHeader.tsx index df527b802d..7cde6e480e 100644 --- a/apps/tlon-web/src/heap/HeapDetail/HeapDetailHeader.tsx +++ b/apps/tlon-web/src/heap/HeapDetail/HeapDetailHeader.tsx @@ -1,3 +1,8 @@ +import { + PostEssay, + chatStoryFromStory, +} from '@tloncorp/shared/dist/urbit/channel'; +import { isLink } from '@tloncorp/shared/dist/urbit/content'; import cn from 'classnames'; import { Link } from 'react-router-dom'; @@ -12,8 +17,6 @@ import getHeapContentType from '@/logic/useHeapContentType'; import { useIsMobile } from '@/logic/useMedia'; import { isImageUrl, makePrettyDayAndTime } from '@/logic/utils'; import { useAmAdmin } from '@/state/groups'; -import { PostEssay, chatStoryFromStory } from '@/types/channel'; -import { isLink } from '@/types/content'; import useCurioActions from '../useCurioActions'; diff --git a/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailComments.tsx b/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailComments.tsx index 11e9d500da..56c9fd158c 100644 --- a/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailComments.tsx +++ b/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailComments.tsx @@ -1,3 +1,4 @@ +import { ReplyTuple } from '@tloncorp/shared/dist/urbit/channel'; import bigInt from 'big-integer'; import { useMemo } from 'react'; import { useLocation } from 'react-router-dom'; @@ -10,7 +11,6 @@ import { groupReplies, setNewDaysForReplies } from '@/replies/replies'; import { usePerms, useUnread } from '@/state/channel/channel'; import { useGroup, useRouteGroup, useVessel } from '@/state/groups/groups'; import { useDiaryCommentSortMode } from '@/state/settings'; -import { ReplyTuple } from '@/types/channel'; import HeapDetailCommentField from './HeapDetailCommentField'; diff --git a/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailSidebarInfo.tsx b/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailSidebarInfo.tsx index 2013acedb0..a1822be9f0 100644 --- a/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailSidebarInfo.tsx +++ b/apps/tlon-web/src/heap/HeapDetail/HeapDetailSidebar/HeapDetailSidebarInfo.tsx @@ -1,8 +1,9 @@ +import { PostEssay } from '@tloncorp/shared/dist/urbit/channel'; + import Author from '@/chat/ChatMessage/Author'; import getKindDataFromEssay from '@/logic/getKindData'; import { firstInlineSummary } from '@/logic/tiptap'; import { URL_REGEX, getFirstInline, makePrettyDay } from '@/logic/utils'; -import { PostEssay } from '@/types/channel'; interface HeapDetailSidebarProps { essay: PostEssay; diff --git a/apps/tlon-web/src/heap/HeapHeader.tsx b/apps/tlon-web/src/heap/HeapHeader.tsx index 80372b90e8..726a27eab8 100644 --- a/apps/tlon-web/src/heap/HeapHeader.tsx +++ b/apps/tlon-web/src/heap/HeapHeader.tsx @@ -1,4 +1,5 @@ import * as Dropdown from '@radix-ui/react-dropdown-menu'; +import { DisplayMode, SortMode } from '@tloncorp/shared/dist/urbit/channel'; import cn from 'classnames'; import React, { useEffect, useState } from 'react'; @@ -19,7 +20,6 @@ import { useHeapSettings, usePutEntryMutation, } from '@/state/settings'; -import { DisplayMode, SortMode } from '@/types/channel'; import AddCurioModal from './AddCurioModal'; diff --git a/apps/tlon-web/src/heap/HeapRow.tsx b/apps/tlon-web/src/heap/HeapRow.tsx index 936e0cfdd6..970d2d63af 100644 --- a/apps/tlon-web/src/heap/HeapRow.tsx +++ b/apps/tlon-web/src/heap/HeapRow.tsx @@ -1,3 +1,9 @@ +import { + Post, + Story, + VerseBlock, + imageUrlFromContent, +} from '@tloncorp/shared/dist/urbit/channel'; import { daToUnix } from '@urbit/api'; import bigInt from 'big-integer'; import cn from 'classnames'; @@ -33,7 +39,6 @@ import { useRouteGroup, } from '@/state/groups/groups'; import { useCalm } from '@/state/settings'; -import { Post, Story, VerseBlock, imageUrlFromContent } from '@/types/channel'; import useCurioActions from './useCurioActions'; diff --git a/apps/tlon-web/src/heap/HeapTextInput.tsx b/apps/tlon-web/src/heap/HeapTextInput.tsx index 096097ae45..3d1711746a 100644 --- a/apps/tlon-web/src/heap/HeapTextInput.tsx +++ b/apps/tlon-web/src/heap/HeapTextInput.tsx @@ -1,4 +1,6 @@ import { Editor, JSONContent } from '@tiptap/react'; +import { PostEssay, constructStory } from '@tloncorp/shared/dist/urbit/channel'; +import { Inline, InlineKey } from '@tloncorp/shared/dist/urbit/content'; import cn from 'classnames'; import { reduce } from 'lodash'; import React, { useCallback, useEffect } from 'react'; @@ -24,8 +26,6 @@ import { useAddPostMutation, useAddReplyMutation, } from '@/state/channel/channel'; -import { PostEssay, constructStory } from '@/types/channel'; -import { Inline, InlineKey } from '@/types/content'; interface HeapTextInputProps { flag: string; diff --git a/apps/tlon-web/src/logic/analytics.ts b/apps/tlon-web/src/logic/analytics.ts index 817659b740..22c74b4d49 100644 --- a/apps/tlon-web/src/logic/analytics.ts +++ b/apps/tlon-web/src/logic/analytics.ts @@ -1,7 +1,6 @@ +import { PrivacyType } from '@tloncorp/shared/dist/urbit/groups'; import posthog, { Properties } from 'posthog-js'; -import { PrivacyType } from '@/types/groups'; - import { isNativeApp } from './native'; import { log } from './utils'; diff --git a/apps/tlon-web/src/logic/channel.ts b/apps/tlon-web/src/logic/channel.ts index 7457790461..cdc5d9f079 100644 --- a/apps/tlon-web/src/logic/channel.ts +++ b/apps/tlon-web/src/logic/channel.ts @@ -1,3 +1,12 @@ +import { Perm, Story, Unreads } from '@tloncorp/shared/dist/urbit/channel'; +import { isLink } from '@tloncorp/shared/dist/urbit/content'; +import { + Channels, + Group, + GroupChannel, + Vessel, + Zone, +} from '@tloncorp/shared/dist/urbit/groups'; import _, { get, groupBy } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; @@ -18,9 +27,6 @@ import { import { useGroup, useRouteGroup } from '@/state/groups'; import { useLastReconnect } from '@/state/local'; import { useNegotiate } from '@/state/negotiation'; -import { Perm, Story, Unreads } from '@/types/channel'; -import { isLink } from '@/types/content'; -import { Channels, Group, GroupChannel, Vessel, Zone } from '@/types/groups'; import useRecentChannel from './useRecentChannel'; import useSidebarSort, { diff --git a/apps/tlon-web/src/logic/getKindData.ts b/apps/tlon-web/src/logic/getKindData.ts index c4c8a5dde5..6a8548d611 100644 --- a/apps/tlon-web/src/logic/getKindData.ts +++ b/apps/tlon-web/src/logic/getKindData.ts @@ -1,4 +1,4 @@ -import { PostEssay } from '@/types/channel'; +import { PostEssay } from '@tloncorp/shared/dist/urbit/channel'; export default function getKindDataFromEssay(essay: PostEssay | undefined): { title: string; diff --git a/apps/tlon-web/src/logic/heap.ts b/apps/tlon-web/src/logic/heap.ts index c149d8d76e..2b4833a75b 100644 --- a/apps/tlon-web/src/logic/heap.ts +++ b/apps/tlon-web/src/logic/heap.ts @@ -1,12 +1,15 @@ /* eslint-disable */ import { JSONContent } from '@tiptap/core'; +import { + PostEssay, + storyFromChatStory, +} from '@tloncorp/shared/dist/urbit/channel'; +import { ChatStory } from '@tloncorp/shared/dist/urbit/channel'; +import { Inline, InlineKey } from '@tloncorp/shared/dist/urbit/content'; import { reduce } from 'lodash'; import isURL from 'validator/lib/isURL'; import { JSONToInlines } from '@/logic/tiptap'; -import { PostEssay, storyFromChatStory } from '@/types/channel'; -import { ChatStory } from '@/types/channel'; -import { Inline, InlineKey } from '@/types/content'; import { isRef, pathToCite } from './utils'; import { isImageUrl } from './utils'; diff --git a/apps/tlon-web/src/logic/messageSender.ts b/apps/tlon-web/src/logic/messageSender.ts index 3ca7ed7364..beb76b98bc 100644 --- a/apps/tlon-web/src/logic/messageSender.ts +++ b/apps/tlon-web/src/logic/messageSender.ts @@ -1,8 +1,4 @@ import { JSONContent } from '@tiptap/core'; - -import { CacheId } from '@/state/channel/channel'; -import { SendMessageVariables, SendReplyVariables } from '@/state/chat'; -import { buildAddDelta, createMessage } from '@/state/chat/utils'; import { Block, Cite, @@ -10,8 +6,12 @@ import { Nest, PostEssay, constructStory, -} from '@/types/channel'; -import { WritDelta } from '@/types/dms'; +} from '@tloncorp/shared/dist/urbit/channel'; +import { WritDelta } from '@tloncorp/shared/dist/urbit/dms'; + +import { CacheId } from '@/state/channel/channel'; +import { SendMessageVariables, SendReplyVariables } from '@/state/chat'; +import { buildAddDelta, createMessage } from '@/state/chat/utils'; import { JSONToInlines } from './tiptap'; import { isImageUrl } from './utils'; diff --git a/apps/tlon-web/src/logic/tiptap.test.ts b/apps/tlon-web/src/logic/tiptap.test.ts index eca483df04..dbbc70d792 100644 --- a/apps/tlon-web/src/logic/tiptap.test.ts +++ b/apps/tlon-web/src/logic/tiptap.test.ts @@ -1,10 +1,9 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { JSONContent } from '@tiptap/react'; +import { Block } from '@tloncorp/shared/dist/urbit/channel'; +import { Inline } from '@tloncorp/shared/dist/urbit/content'; import { describe, expect, it } from 'vitest'; -import { Block } from '@/types/channel'; -import { Inline } from '@/types/content'; - import { JSONToInlines, inlineToContent, diff --git a/apps/tlon-web/src/logic/tiptap.ts b/apps/tlon-web/src/logic/tiptap.ts index 5d123aebb5..df09824fab 100644 --- a/apps/tlon-web/src/logic/tiptap.ts +++ b/apps/tlon-web/src/logic/tiptap.ts @@ -5,10 +5,13 @@ import { PasteRule, } from '@tiptap/core'; import { JSONContent } from '@tiptap/react'; -import { deSig } from '@urbit/api'; -import { isEqual, reduce } from 'lodash'; - -import { Block, Cite, HeaderLevel, Listing, Story } from '@/types/channel'; +import { + Block, + Cite, + HeaderLevel, + Listing, + Story, +} from '@tloncorp/shared/dist/urbit/channel'; import { Inline, InlineKey, @@ -21,7 +24,9 @@ import { isLink, isShip, isStrikethrough, -} from '@/types/content'; +} from '@tloncorp/shared/dist/urbit/content'; +import { deSig } from '@urbit/api'; +import { isEqual, reduce } from 'lodash'; import { citeToPath, getFirstInline, pathToCite, preSig } from './utils'; diff --git a/apps/tlon-web/src/logic/useGroupSort.ts b/apps/tlon-web/src/logic/useGroupSort.ts index 7898442f2c..cd762652e2 100644 --- a/apps/tlon-web/src/logic/useGroupSort.ts +++ b/apps/tlon-web/src/logic/useGroupSort.ts @@ -1,8 +1,8 @@ +import { Group, Groups } from '@tloncorp/shared/dist/urbit/groups'; import { get } from 'lodash'; import { useCallback, useMemo } from 'react'; import { ALPHABETICAL_SORT, RECENT_SORT } from '@/constants'; -import { Group, Groups } from '@/types/groups'; import { useChannelSort } from './channel'; import useSidebarSort, { diff --git a/apps/tlon-web/src/logic/useMessageSort.ts b/apps/tlon-web/src/logic/useMessageSort.ts index 629e7354e1..bc484a6bf3 100644 --- a/apps/tlon-web/src/logic/useMessageSort.ts +++ b/apps/tlon-web/src/logic/useMessageSort.ts @@ -1,6 +1,7 @@ +import { Unread } from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; + import { RECENT_SORT } from '@/constants'; -import { Unread } from '@/types/channel'; -import { DMUnread } from '@/types/dms'; import useSidebarSort, { Sorter, useRecentSort } from './useSidebarSort'; diff --git a/apps/tlon-web/src/logic/useScrollerMessages.ts b/apps/tlon-web/src/logic/useScrollerMessages.ts index 62dfecc33a..8d3bee2d89 100644 --- a/apps/tlon-web/src/logic/useScrollerMessages.ts +++ b/apps/tlon-web/src/logic/useScrollerMessages.ts @@ -1,10 +1,9 @@ +import { Post, Reply } from '@tloncorp/shared/dist/urbit/channel'; +import { Writ } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import bigInt, { BigInteger } from 'big-integer'; import { useMemo, useRef } from 'react'; -import { Post, Reply } from '@/types/channel'; -import { Writ } from '@/types/dms'; - import getKindDataFromEssay from './getKindData'; export type WritArray = [BigInteger, Writ | Post | Reply | null][]; diff --git a/apps/tlon-web/src/logic/utils.ts b/apps/tlon-web/src/logic/utils.ts index 52ad3c533e..442eeab786 100644 --- a/apps/tlon-web/src/logic/utils.ts +++ b/apps/tlon-web/src/logic/utils.ts @@ -1,3 +1,30 @@ +import { + ChatStory, + Cite, + Listing, + Story, + Verse, + VerseBlock, + VerseInline, +} from '@tloncorp/shared/dist/urbit/channel'; +import { + Bold, + Inline, + Italics, + Strikethrough, +} from '@tloncorp/shared/dist/urbit/content'; +import { + Cabals, + ChannelPrivacyType, + Cordon, + Gang, + Group, + GroupChannel, + GroupPreview, + PrivacyType, + Rank, + Saga, +} from '@tloncorp/shared/dist/urbit/groups'; import { BigIntOrderedMap, Docket, @@ -19,29 +46,6 @@ import ob from 'urbit-ob'; import { useCopyToClipboard } from 'usehooks-ts'; import isURL from 'validator/es/lib/isURL'; -import { - ChatStory, - Cite, - Listing, - Story, - Verse, - VerseBlock, - VerseInline, -} from '@/types/channel'; -import { Bold, Inline, Italics, Strikethrough } from '@/types/content'; -import { - Cabals, - ChannelPrivacyType, - Cordon, - Gang, - Group, - GroupChannel, - GroupPreview, - PrivacyType, - Rank, - Saga, -} from '@/types/groups'; - import type { ConnectionCompleteStatus, ConnectionPendingStatus, diff --git a/apps/tlon-web/src/mocks/chat.ts b/apps/tlon-web/src/mocks/chat.ts index 59eff714ae..629e8b5f44 100644 --- a/apps/tlon-web/src/mocks/chat.ts +++ b/apps/tlon-web/src/mocks/chat.ts @@ -1,14 +1,18 @@ /* eslint-disable import/no-cycle */ import faker from '@faker-js/faker'; +import { + Post, + Posts, + Story, + storyFromChatStory, +} from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnreads } from '@tloncorp/shared/dist/urbit/dms'; import { decToUd, unixToDa } from '@urbit/api'; import { subDays, subMinutes } from 'date-fns'; import _ from 'lodash'; import { AUTHORS } from '@/constants'; import { randomElement } from '@/logic/utils'; -import { Post, Posts, Story, storyFromChatStory } from '@/types/channel'; - -import { DMUnreads } from '../types/dms'; const getUnix = (count: number, setTime?: Date) => count > 1 diff --git a/apps/tlon-web/src/mocks/groups.ts b/apps/tlon-web/src/mocks/groups.ts index 39549052a8..bbae045779 100644 --- a/apps/tlon-web/src/mocks/groups.ts +++ b/apps/tlon-web/src/mocks/groups.ts @@ -1,9 +1,5 @@ /* eslint-disable import/no-cycle */ import faker from '@faker-js/faker'; - -import { AUTHORS } from '@/constants'; -import { randomElement } from '@/logic/utils'; - import { Cordon, Gang, @@ -13,7 +9,10 @@ import { GroupPreview, PrivacyType, Vessel, -} from '../types/groups'; +} from '@tloncorp/shared/dist/urbit/groups'; + +import { AUTHORS } from '@/constants'; +import { randomElement } from '@/logic/utils'; const emptyVessel = (): Vessel => ({ sects: [], diff --git a/apps/tlon-web/src/mocks/handlers.ts b/apps/tlon-web/src/mocks/handlers.ts index 040908031a..3f52bf055f 100644 --- a/apps/tlon-web/src/mocks/handlers.ts +++ b/apps/tlon-web/src/mocks/handlers.ts @@ -9,6 +9,16 @@ import UrbitMock, { SubscriptionRequestInterface, createResponse, } from '@tloncorp/mock-http-api'; +import { + Club, + ClubAction, + ClubCreate, + DMUnreads, + DMWhom, + DmRsvp, + WritDiff, +} from '@tloncorp/shared/dist/urbit/dms'; +import { GroupAction } from '@tloncorp/shared/dist/urbit/groups'; import { decToUd, udToDec, unixToDa } from '@urbit/api'; import bigInt from 'big-integer'; import _ from 'lodash'; @@ -27,16 +37,6 @@ import mockGroups, { pinnedGroups, } from '@/mocks/groups'; import heapHandlers from '@/mocks/heaps'; -import { - Club, - ClubAction, - ClubCreate, - DMUnreads, - DMWhom, - DmRsvp, - WritDiff, -} from '@/types/dms'; -import { GroupAction } from '@/types/groups'; const getNowUd = () => decToUd(unixToDa(Date.now() * 1000).toString()); diff --git a/apps/tlon-web/src/mocks/heaps.ts b/apps/tlon-web/src/mocks/heaps.ts index ff8633d94f..e13a466296 100644 --- a/apps/tlon-web/src/mocks/heaps.ts +++ b/apps/tlon-web/src/mocks/heaps.ts @@ -3,10 +3,9 @@ import { ScryHandler, SubscriptionHandler, } from '@tloncorp/mock-http-api'; +import { Channels, Perm, Posts } from '@tloncorp/shared/dist/urbit/channel'; import { subMinutes } from 'date-fns'; -import { Channels, Perm, Posts } from '@/types/channel'; - const unixTime = subMinutes(new Date(), 1).getTime(); const mockPerms: Perm = { diff --git a/apps/tlon-web/src/notifications/DMNotification.tsx b/apps/tlon-web/src/notifications/DMNotification.tsx index a25b0e8945..18ad42569c 100644 --- a/apps/tlon-web/src/notifications/DMNotification.tsx +++ b/apps/tlon-web/src/notifications/DMNotification.tsx @@ -1,8 +1,8 @@ +import { Skein, isYarnShip } from '@tloncorp/shared/dist/urbit/hark'; import React from 'react'; import Avatar from '@/components/Avatar'; import { useMultiDm } from '@/state/chat'; -import { Skein, isYarnShip } from '@/types/hark'; import Notification from './Notification'; diff --git a/apps/tlon-web/src/notifications/GroupNotification.tsx b/apps/tlon-web/src/notifications/GroupNotification.tsx index 1fc2f03386..0bb0cfd3f5 100644 --- a/apps/tlon-web/src/notifications/GroupNotification.tsx +++ b/apps/tlon-web/src/notifications/GroupNotification.tsx @@ -1,3 +1,5 @@ +import { Rope, Skein, isYarnShip } from '@tloncorp/shared/dist/urbit/hark'; + import Avatar from '@/components/Avatar'; import AddIcon16 from '@/components/icons/Add16Icon'; import ChatSmallIcon from '@/components/icons/ChatSmallIcon'; @@ -9,7 +11,6 @@ import X16Icon from '@/components/icons/X16Icon'; import GroupAvatar from '@/groups/GroupAvatar'; import { useIsMobile } from '@/logic/useMedia'; import { useGang, useGroup, useGroupFlag } from '@/state/groups'; -import { Rope, Skein, isYarnShip } from '@/types/hark'; import Notification from './Notification'; import { isDm } from './useNotifications'; diff --git a/apps/tlon-web/src/notifications/Notification.tsx b/apps/tlon-web/src/notifications/Notification.tsx index 3f7ada9537..4830937779 100644 --- a/apps/tlon-web/src/notifications/Notification.tsx +++ b/apps/tlon-web/src/notifications/Notification.tsx @@ -1,3 +1,4 @@ +import { Skein, YarnContent } from '@tloncorp/shared/dist/urbit/hark'; import cn from 'classnames'; import _ from 'lodash'; import { ReactNode, useCallback } from 'react'; @@ -14,7 +15,6 @@ import { PUNCTUATION_REGEX, makePrettyTime } from '@/logic/utils'; import { usePost } from '@/state/channel/channel'; import { useGang, useGroup } from '@/state/groups'; import { useSawRopeMutation } from '@/state/hark'; -import { Skein, YarnContent } from '@/types/hark'; import { isBlock, diff --git a/apps/tlon-web/src/notifications/Notifications.tsx b/apps/tlon-web/src/notifications/Notifications.tsx index b45aca3fb2..018b3d0150 100644 --- a/apps/tlon-web/src/notifications/Notifications.tsx +++ b/apps/tlon-web/src/notifications/Notifications.tsx @@ -1,3 +1,5 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; +import { Skein } from '@tloncorp/shared/dist/urbit/hark'; import cn from 'classnames'; import { ComponentType, PropsWithChildren, useCallback, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -13,8 +15,6 @@ import { useIsMobile } from '@/logic/useMedia'; import { randomElement, randomIntInRange } from '@/logic/utils'; import { useAmAdmin, useGroup, useRouteGroup } from '@/state/groups'; import { useSawRopeMutation, useSawSeamMutation } from '@/state/hark'; -import { ViewProps } from '@/types/groups'; -import { Skein } from '@/types/hark'; import { NotificationFilterType, useNotifications } from './useNotifications'; diff --git a/apps/tlon-web/src/notifications/useNotifications.tsx b/apps/tlon-web/src/notifications/useNotifications.tsx index 6c8bcd6e31..41bf6f2021 100644 --- a/apps/tlon-web/src/notifications/useNotifications.tsx +++ b/apps/tlon-web/src/notifications/useNotifications.tsx @@ -1,9 +1,9 @@ +import { Flag, Rope, Skein, Yarn } from '@tloncorp/shared/dist/urbit/hark'; import _ from 'lodash'; import { useIsMobile } from '@/logic/useMedia'; import { makePrettyDay } from '@/logic/utils'; import { useSkeins } from '@/state/hark'; -import { Flag, Rope, Skein, Yarn } from '@/types/hark'; export interface DayGrouping { date: string; diff --git a/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx b/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx index 1f7a19ba9d..97393f5e70 100644 --- a/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx +++ b/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx @@ -1,3 +1,10 @@ +import { + Contact, + ContactAddGroup, + ContactDelGroup, + ContactEditField, +} from '@tloncorp/shared/dist/urbit/contact'; +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import _ from 'lodash'; import React, { useCallback, useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -13,13 +20,6 @@ import { useIsMobile } from '@/logic/useMedia'; import useContactState, { useOurContact } from '@/state/contact'; import { useGroups } from '@/state/groups'; import { useProfileIsPublic } from '@/state/profile/profile'; -import { - Contact, - ContactAddGroup, - ContactDelGroup, - ContactEditField, -} from '@/types/contact'; -import { ViewProps } from '@/types/groups'; import ProfileCoverImage from '../ProfileCoverImage'; import PublicProfileSelector from '../PublicProfileSelector'; diff --git a/apps/tlon-web/src/profiles/Profile.tsx b/apps/tlon-web/src/profiles/Profile.tsx index a5d325b686..2d7660331a 100644 --- a/apps/tlon-web/src/profiles/Profile.tsx +++ b/apps/tlon-web/src/profiles/Profile.tsx @@ -1,3 +1,4 @@ +import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; import cn from 'classnames'; import { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -30,7 +31,6 @@ import { import { useIsMobile } from '@/logic/useMedia'; import { isHosted, useCopy, useIsHttps } from '@/logic/utils'; import { useOurContact } from '@/state/contact'; -import { ViewProps } from '@/types/groups'; import ProfileCoverImage from './ProfileCoverImage'; import PublicProfileSelector from './PublicProfileSelector'; diff --git a/apps/tlon-web/src/replies/ReplyMessage.tsx b/apps/tlon-web/src/replies/ReplyMessage.tsx index 3244989c22..91d6f97a21 100644 --- a/apps/tlon-web/src/replies/ReplyMessage.tsx +++ b/apps/tlon-web/src/replies/ReplyMessage.tsx @@ -1,5 +1,12 @@ /* eslint-disable react/no-unused-prop-types */ // eslint-disable-next-line import/no-cycle +import { + Reply, + Story, + Unread, + emptyReply, +} from '@tloncorp/shared/dist/urbit/channel'; +import { DMUnread } from '@tloncorp/shared/dist/urbit/dms'; import { daToUnix } from '@urbit/api'; import { BigInteger } from 'big-integer'; import cn from 'classnames'; @@ -38,8 +45,6 @@ import { useMessageToggler, useTrackedMessageStatus, } from '@/state/chat'; -import { Reply, Story, Unread, emptyReply } from '@/types/channel'; -import { DMUnread } from '@/types/dms'; import ReplyMessageOptions from './ReplyMessageOptions'; import ReplyReactions from './ReplyReactions/ReplyReactions'; diff --git a/apps/tlon-web/src/replies/ReplyMessageOptions.tsx b/apps/tlon-web/src/replies/ReplyMessageOptions.tsx index a9e0bf158b..871854f7b4 100644 --- a/apps/tlon-web/src/replies/ReplyMessageOptions.tsx +++ b/apps/tlon-web/src/replies/ReplyMessageOptions.tsx @@ -1,3 +1,4 @@ +import { Reply, emptyReply } from '@tloncorp/shared/dist/urbit/channel'; import { decToUd } from '@urbit/api'; import cn from 'classnames'; import { useCallback, useEffect, useMemo } from 'react'; @@ -44,7 +45,6 @@ import { useRouteGroup, useVessel, } from '@/state/groups'; -import { Reply, emptyReply } from '@/types/channel'; export default function ReplyMessageOptions(props: { open: boolean; diff --git a/apps/tlon-web/src/replies/ReplyReactions/ReplyReactions.tsx b/apps/tlon-web/src/replies/ReplyReactions/ReplyReactions.tsx index 818aec31c7..81a8d090eb 100644 --- a/apps/tlon-web/src/replies/ReplyReactions/ReplyReactions.tsx +++ b/apps/tlon-web/src/replies/ReplyReactions/ReplyReactions.tsx @@ -1,3 +1,4 @@ +import { ReplySeal } from '@tloncorp/shared/dist/urbit/channel'; import _ from 'lodash'; import { useCallback, useState } from 'react'; @@ -10,7 +11,6 @@ import { useAddReplyReactMutation, } from '@/state/channel/channel'; import { useAddDMReplyReactMutation } from '@/state/chat'; -import { ReplySeal } from '@/types/channel'; import ReplyReaction from './ReplyReaction'; diff --git a/apps/tlon-web/src/replies/replies.ts b/apps/tlon-web/src/replies/replies.ts index a44e022a30..57a56dcf9f 100644 --- a/apps/tlon-web/src/replies/replies.ts +++ b/apps/tlon-web/src/replies/replies.ts @@ -1,9 +1,13 @@ +import { + Kind, + Reply, + ReplyTuple, + Unread, +} from '@tloncorp/shared/dist/urbit/channel'; import { daToUnix } from '@urbit/aura'; import bigInt, { BigInteger } from 'big-integer'; import { isSameDay } from 'date-fns'; -import { Kind, Reply, ReplyTuple, Unread } from '@/types/channel'; - export interface ReplyProps { han: Kind; noteId: string; diff --git a/apps/tlon-web/src/state/bootstrap.ts b/apps/tlon-web/src/state/bootstrap.ts index 99e3f78401..65c0a51d06 100644 --- a/apps/tlon-web/src/state/bootstrap.ts +++ b/apps/tlon-web/src/state/bootstrap.ts @@ -1,3 +1,4 @@ +import { GroupsInit } from '@tloncorp/shared/dist/urbit/ui'; import Urbit from '@urbit/http-api'; import _ from 'lodash'; @@ -5,7 +6,6 @@ import api from '@/api'; import { useChatStore } from '@/chat/useChatStore'; import { asyncWithDefault, whomIsDm } from '@/logic/utils'; import queryClient from '@/queryClient'; -import { GroupsInit } from '@/types/ui'; import { infinitePostQueryFn } from './channel/channel'; import { ChannnelKeys } from './channel/keys'; diff --git a/apps/tlon-web/src/state/channel/channel.ts b/apps/tlon-web/src/state/channel/channel.ts index 8965f03e5e..a4d7b25e6c 100644 --- a/apps/tlon-web/src/state/channel/channel.ts +++ b/apps/tlon-web/src/state/channel/channel.ts @@ -1,24 +1,4 @@ import { QueryKey, useInfiniteQuery, useMutation } from '@tanstack/react-query'; -import { decToUd, udToDec, unixToDa } from '@urbit/api'; -import { Poke } from '@urbit/http-api'; -import bigInt from 'big-integer'; -import _ from 'lodash'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import create from 'zustand'; - -import api from '@/api'; -import { useChatStore } from '@/chat/useChatStore'; -import { - LARGE_MESSAGE_FETCH_PAGE_SIZE, - STANDARD_MESSAGE_FETCH_PAGE_SIZE, -} from '@/constants'; -import asyncCallWithTimeout from '@/logic/asyncWithTimeout'; -import { isNativeApp } from '@/logic/native'; -import useReactQueryScry from '@/logic/useReactQueryScry'; -import useReactQuerySubscribeOnce from '@/logic/useReactQuerySubscribeOnce'; -import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; -import { checkNest, log, nestToFlag, whomIsFlag } from '@/logic/utils'; -import queryClient from '@/queryClient'; import { Action, Channel, @@ -49,8 +29,28 @@ import { UnreadUpdate, Unreads, newChatMap, -} from '@/types/channel'; -import { Flag } from '@/types/hark'; +} from '@tloncorp/shared/dist/urbit/channel'; +import { Flag } from '@tloncorp/shared/dist/urbit/hark'; +import { decToUd, udToDec, unixToDa } from '@urbit/api'; +import { Poke } from '@urbit/http-api'; +import bigInt from 'big-integer'; +import _ from 'lodash'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import create from 'zustand'; + +import api from '@/api'; +import { useChatStore } from '@/chat/useChatStore'; +import { + LARGE_MESSAGE_FETCH_PAGE_SIZE, + STANDARD_MESSAGE_FETCH_PAGE_SIZE, +} from '@/constants'; +import asyncCallWithTimeout from '@/logic/asyncWithTimeout'; +import { isNativeApp } from '@/logic/native'; +import useReactQueryScry from '@/logic/useReactQueryScry'; +import useReactQuerySubscribeOnce from '@/logic/useReactQuerySubscribeOnce'; +import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; +import { checkNest, log, nestToFlag, whomIsFlag } from '@/logic/utils'; +import queryClient from '@/queryClient'; import { channelKey } from './keys'; import shouldAddPostToCache from './util'; diff --git a/apps/tlon-web/src/state/channel/util.ts b/apps/tlon-web/src/state/channel/util.ts index e656b30d84..137ff366a3 100644 --- a/apps/tlon-web/src/state/channel/util.ts +++ b/apps/tlon-web/src/state/channel/util.ts @@ -1,4 +1,4 @@ -import { PagedPosts } from '@/types/channel'; +import { PagedPosts } from '@tloncorp/shared/dist/urbit/channel'; type QueryData = { pages: PagedPosts[]; diff --git a/apps/tlon-web/src/state/chat/chat.ts b/apps/tlon-web/src/state/chat/chat.ts index 5b39d16887..e713545b27 100644 --- a/apps/tlon-web/src/state/chat/chat.ts +++ b/apps/tlon-web/src/state/chat/chat.ts @@ -1,24 +1,10 @@ import { QueryKey, useInfiniteQuery, useMutation } from '@tanstack/react-query'; -import { decToUd, udToDec } from '@urbit/api'; -import { formatUd, unixToDa } from '@urbit/aura'; -import { Poke } from '@urbit/http-api'; -import bigInt, { BigInteger } from 'big-integer'; -import _ from 'lodash'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import create from 'zustand'; - -import api from '@/api'; -import { ChatStore, useChatInfo, useChatStore } from '@/chat/useChatStore'; import { - LARGE_MESSAGE_FETCH_PAGE_SIZE, - STANDARD_MESSAGE_FETCH_PAGE_SIZE, -} from '@/constants'; -import { isNativeApp } from '@/logic/native'; -import useReactQueryScry from '@/logic/useReactQueryScry'; -import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; -import { whomIsDm } from '@/logic/utils'; -import queryClient from '@/queryClient'; -import { ChannelsAction, Replies, Reply, ReplyTuple } from '@/types/channel'; + ChannelsAction, + Replies, + Reply, + ReplyTuple, +} from '@tloncorp/shared/dist/urbit/channel'; import { BlockedByShips, BlockedShips, @@ -45,8 +31,27 @@ import { WritSeal, WritTuple, Writs, -} from '@/types/dms'; -import { GroupMeta } from '@/types/groups'; +} from '@tloncorp/shared/dist/urbit/dms'; +import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; +import { decToUd, udToDec } from '@urbit/api'; +import { formatUd, unixToDa } from '@urbit/aura'; +import { Poke } from '@urbit/http-api'; +import bigInt, { BigInteger } from 'big-integer'; +import _ from 'lodash'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import create from 'zustand'; + +import api from '@/api'; +import { ChatStore, useChatInfo, useChatStore } from '@/chat/useChatStore'; +import { + LARGE_MESSAGE_FETCH_PAGE_SIZE, + STANDARD_MESSAGE_FETCH_PAGE_SIZE, +} from '@/constants'; +import { isNativeApp } from '@/logic/native'; +import useReactQueryScry from '@/logic/useReactQueryScry'; +import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; +import { whomIsDm } from '@/logic/utils'; +import queryClient from '@/queryClient'; import { CacheId, PostStatus, TrackedPost } from '../channel/channel'; import ChatKeys from './keys'; diff --git a/apps/tlon-web/src/state/chat/search.ts b/apps/tlon-web/src/state/chat/search.ts index 53f25a66f7..c0c31988dd 100644 --- a/apps/tlon-web/src/state/chat/search.ts +++ b/apps/tlon-web/src/state/chat/search.ts @@ -1,4 +1,16 @@ import { useInfiniteQuery } from '@tanstack/react-query'; +import { + ChatMap, + ReplyTuple, + newChatMap, +} from '@tloncorp/shared/dist/urbit/channel'; +import { + ChatScan, + ChatScanItem, + Writ, + WritTuple, + newWritMap, +} from '@tloncorp/shared/dist/urbit/dms'; import { decToUd } from '@urbit/api'; import bigInt from 'big-integer'; import { useMemo } from 'react'; @@ -7,14 +19,6 @@ import { persist } from 'zustand/middleware'; import api from '@/api'; import { createStorageKey, whomIsDm, whomIsMultiDm } from '@/logic/utils'; -import { ChatMap, ReplyTuple, newChatMap } from '@/types/channel'; -import { - ChatScan, - ChatScanItem, - Writ, - WritTuple, - newWritMap, -} from '@/types/dms'; type Whom = string; type SearchResult = ChatMap; diff --git a/apps/tlon-web/src/state/chat/utils.ts b/apps/tlon-web/src/state/chat/utils.ts index cab8d7f54b..2f6dc3170f 100644 --- a/apps/tlon-web/src/state/chat/utils.ts +++ b/apps/tlon-web/src/state/chat/utils.ts @@ -1,16 +1,11 @@ import { QueryClient } from '@tanstack/react-query'; -import { udToDec } from '@urbit/api'; -import { formatUd, unixToDa } from '@urbit/aura'; -import bigInt from 'big-integer'; -import _ from 'lodash'; - import { PostEssay, Replies, Reply, ReplyMeta, ReplyTuple, -} from '@/types/channel'; +} from '@tloncorp/shared/dist/urbit/channel'; import { Club, DMUnreads, @@ -21,7 +16,11 @@ import { WritInCache, WritMemo, WritSeal, -} from '@/types/dms'; +} from '@tloncorp/shared/dist/urbit/dms'; +import { udToDec } from '@urbit/api'; +import { formatUd, unixToDa } from '@urbit/aura'; +import bigInt from 'big-integer'; +import _ from 'lodash'; export default function emptyMultiDm(): Club { return { diff --git a/apps/tlon-web/src/state/contact.ts b/apps/tlon-web/src/state/contact.ts index 3a7c143f9f..e83eabe36b 100644 --- a/apps/tlon-web/src/state/contact.ts +++ b/apps/tlon-web/src/state/contact.ts @@ -1,11 +1,4 @@ /* eslint-disable no-param-reassign */ -import { Patp, preSig } from '@urbit/api'; -import produce from 'immer'; -import _ from 'lodash'; -import { useCallback, useMemo } from 'react'; - -import api from '@/api'; -import { BaseState, createState } from '@/state/base'; import { ContactAnon, ContactEdit, @@ -13,7 +6,14 @@ import { ContactHeed, ContactNews, ContactRolodex, -} from '@/types/contact'; +} from '@tloncorp/shared/dist/urbit/contact'; +import { Patp, preSig } from '@urbit/api'; +import produce from 'immer'; +import _ from 'lodash'; +import { useCallback, useMemo } from 'react'; + +import api from '@/api'; +import { BaseState, createState } from '@/state/base'; export interface BaseContactState { contacts: ContactRolodex; diff --git a/apps/tlon-web/src/state/groups/groups.ts b/apps/tlon-web/src/state/groups/groups.ts index 1b21054a9c..88d58536b6 100644 --- a/apps/tlon-web/src/state/groups/groups.ts +++ b/apps/tlon-web/src/state/groups/groups.ts @@ -4,25 +4,7 @@ import { useMutation, useQueryClient, } from '@tanstack/react-query'; -import { decToUd } from '@urbit/api'; -import { Poke } from '@urbit/http-api'; -import _ from 'lodash'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { useParams } from 'react-router'; -import create from 'zustand'; - -import api from '@/api'; -import { captureGroupsAnalyticsEvent } from '@/logic/analytics'; -import useReactQueryScry from '@/logic/useReactQueryScry'; -import useReactQuerySubscribeOnce from '@/logic/useReactQuerySubscribeOnce'; -import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; -import { - getCompatibilityText, - getFlagParts, - preSig, - sagaCompatible, -} from '@/logic/utils'; -import { BaitCite, Post, Reply } from '@/types/channel'; +import { BaitCite, Post, Reply } from '@tloncorp/shared/dist/urbit/channel'; import { ChannelPreview, Cordon, @@ -41,8 +23,26 @@ import { PrivacyType, Vessel, isGroup, -} from '@/types/groups'; -import { Scope, VolumeValue } from '@/types/volume'; +} from '@tloncorp/shared/dist/urbit/groups'; +import { Scope, VolumeValue } from '@tloncorp/shared/dist/urbit/volume'; +import { decToUd } from '@urbit/api'; +import { Poke } from '@urbit/http-api'; +import _ from 'lodash'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useParams } from 'react-router'; +import create from 'zustand'; + +import api from '@/api'; +import { captureGroupsAnalyticsEvent } from '@/logic/analytics'; +import useReactQueryScry from '@/logic/useReactQueryScry'; +import useReactQuerySubscribeOnce from '@/logic/useReactQuerySubscribeOnce'; +import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; +import { + getCompatibilityText, + getFlagParts, + preSig, + sagaCompatible, +} from '@/logic/utils'; import { useGroupPins } from '../pins'; import { useNewGroupFlags } from '../settings'; diff --git a/apps/tlon-web/src/state/groups/groupsReducer.ts b/apps/tlon-web/src/state/groups/groupsReducer.ts index fbfa4cac44..26ba4a3291 100644 --- a/apps/tlon-web/src/state/groups/groupsReducer.ts +++ b/apps/tlon-web/src/state/groups/groupsReducer.ts @@ -5,7 +5,8 @@ import { Group, GroupDiff, GroupUpdate, -} from '../../types/groups'; +} from '@tloncorp/shared/dist/urbit/groups'; + import { GroupState } from './type'; function reduceCordon(draft: Group, diff: CordonDiff) { diff --git a/apps/tlon-web/src/state/groups/type.ts b/apps/tlon-web/src/state/groups/type.ts index 74caf57c13..f12fd572a2 100644 --- a/apps/tlon-web/src/state/groups/type.ts +++ b/apps/tlon-web/src/state/groups/type.ts @@ -1,8 +1,4 @@ -import { GroupMeta } from '@tloncorp/shared'; - -import { BaitCite } from '@/types/channel'; -import { GroupsInit } from '@/types/ui'; - +import { BaitCite } from '@tloncorp/shared/dist/urbit/channel'; import { ChannelPreview, Cordon, @@ -10,8 +6,10 @@ import { Group, GroupChannel, GroupIndex, + GroupMeta, Rank, -} from '../../types/groups'; +} from '@tloncorp/shared/dist/urbit/groups'; +import { GroupsInit } from '@tloncorp/shared/dist/urbit/ui'; export interface GroupState { set: (fn: (sta: GroupState) => void) => void; diff --git a/apps/tlon-web/src/state/hark.ts b/apps/tlon-web/src/state/hark.ts index 02b24292f2..c11837c51e 100644 --- a/apps/tlon-web/src/state/hark.ts +++ b/apps/tlon-web/src/state/hark.ts @@ -1,8 +1,4 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { decToUd } from '@urbit/api'; - -import api from '@/api'; -import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; import { Blanket, Carpet, @@ -14,7 +10,11 @@ import { Seam, Skein, Yarn, -} from '@/types/hark'; +} from '@tloncorp/shared/dist/urbit/hark'; +import { decToUd } from '@urbit/api'; + +import api from '@/api'; +import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; function harkAction(action: HarkAction) { return { diff --git a/apps/tlon-web/src/state/lure/lure.ts b/apps/tlon-web/src/state/lure/lure.ts index 17b8bbae00..ee67a8bda7 100644 --- a/apps/tlon-web/src/state/lure/lure.ts +++ b/apps/tlon-web/src/state/lure/lure.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; +import { GroupMeta } from '@tloncorp/shared/dist/urbit/groups'; import produce from 'immer'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import create from 'zustand'; @@ -15,7 +16,6 @@ import { getFlagParts, storageVersion, } from '@/logic/utils'; -import { GroupMeta } from '@/types/groups'; import { useLocalState } from '../local'; diff --git a/apps/tlon-web/src/state/negotiation.ts b/apps/tlon-web/src/state/negotiation.ts index ce25bcbcae..bf72a882f8 100644 --- a/apps/tlon-web/src/state/negotiation.ts +++ b/apps/tlon-web/src/state/negotiation.ts @@ -1,10 +1,13 @@ import { useQuery } from '@tanstack/react-query'; +import { + MatchingEvent, + MatchingResponse, +} from '@tloncorp/shared/dist/urbit/negotiation'; import { debounce } from 'lodash'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import api from '@/api'; import queryClient from '@/queryClient'; -import { MatchingEvent, MatchingResponse } from '@/types/negotiation'; function negotiationUpdater( event: MatchingEvent | null, diff --git a/apps/tlon-web/src/state/pins.ts b/apps/tlon-web/src/state/pins.ts index 8324d88b31..78bf20d617 100644 --- a/apps/tlon-web/src/state/pins.ts +++ b/apps/tlon-web/src/state/pins.ts @@ -1,4 +1,5 @@ import { useMutation } from '@tanstack/react-query'; +import { Nest } from '@tloncorp/shared/dist/urbit/channel'; import _ from 'lodash'; import { useMemo } from 'react'; @@ -6,7 +7,6 @@ import api from '@/api'; import useReactQueryScry from '@/logic/useReactQueryScry'; import { whomIsDm, whomIsFlag, whomIsMultiDm, whomIsNest } from '@/logic/utils'; import queryClient from '@/queryClient'; -import { Nest } from '@/types/channel'; export const pinsKey = () => ['groups-ui', 'pins']; diff --git a/apps/tlon-web/src/state/settings.ts b/apps/tlon-web/src/state/settings.ts index d038c39372..2d40554f8a 100644 --- a/apps/tlon-web/src/state/settings.ts +++ b/apps/tlon-web/src/state/settings.ts @@ -1,4 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { DisplayMode, SortMode } from '@tloncorp/shared/dist/urbit/channel'; import { DelBucket, DelEntry, PutBucket, Value } from '@urbit/api'; import cookies from 'browser-cookies'; import produce from 'immer'; @@ -17,7 +18,6 @@ import { import { isNativeApp } from '@/logic/native'; import useReactQuerySubscription from '@/logic/useReactQuerySubscription'; import { isHosted } from '@/logic/utils'; -import { DisplayMode, SortMode } from '@/types/channel'; interface ChannelSetting { flag: string; diff --git a/apps/tlon-web/src/window.ts b/apps/tlon-web/src/window.ts index 29415af336..92506369bc 100644 --- a/apps/tlon-web/src/window.ts +++ b/apps/tlon-web/src/window.ts @@ -1,6 +1,5 @@ import type { NativeWebViewOptions } from '@tloncorp/shared'; - -import { Rope } from './types/hark'; +import { Rope } from '@tloncorp/shared/dist/urbit/hark'; declare global { interface Window { diff --git a/packages/shared/package.json b/packages/shared/package.json index bb52a5c379..a5e54e0cc5 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -2,10 +2,10 @@ "name": "@tloncorp/shared", "version": "1.0.0", "type": "module", - "exports": "./dist/index.js", + "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { - "build": "tsup src/index.ts --format esm --minify --dts --out-dir dist", + "build": "tsup --entry src/urbit/* --format esm --minify --dts --out-dir dist", "dev": "npm run build -- --watch" }, "dependencies": {} diff --git a/apps/tlon-web/src/types/channel.ts b/packages/shared/src/urbit/channel.ts similarity index 100% rename from apps/tlon-web/src/types/channel.ts rename to packages/shared/src/urbit/channel.ts diff --git a/apps/tlon-web/src/types/contact.ts b/packages/shared/src/urbit/contact.ts similarity index 100% rename from apps/tlon-web/src/types/contact.ts rename to packages/shared/src/urbit/contact.ts diff --git a/apps/tlon-web/src/types/content.ts b/packages/shared/src/urbit/content.ts similarity index 100% rename from apps/tlon-web/src/types/content.ts rename to packages/shared/src/urbit/content.ts diff --git a/apps/tlon-web/src/types/dms.ts b/packages/shared/src/urbit/dms.ts similarity index 100% rename from apps/tlon-web/src/types/dms.ts rename to packages/shared/src/urbit/dms.ts diff --git a/apps/tlon-web/src/types/groups.ts b/packages/shared/src/urbit/groups.ts similarity index 100% rename from apps/tlon-web/src/types/groups.ts rename to packages/shared/src/urbit/groups.ts diff --git a/apps/tlon-web/src/types/hark.ts b/packages/shared/src/urbit/hark.ts similarity index 100% rename from apps/tlon-web/src/types/hark.ts rename to packages/shared/src/urbit/hark.ts diff --git a/apps/tlon-web/src/types/negotiation.ts b/packages/shared/src/urbit/negotiation.ts similarity index 100% rename from apps/tlon-web/src/types/negotiation.ts rename to packages/shared/src/urbit/negotiation.ts diff --git a/apps/tlon-web/src/types/sigil.ts b/packages/shared/src/urbit/sigil.ts similarity index 100% rename from apps/tlon-web/src/types/sigil.ts rename to packages/shared/src/urbit/sigil.ts diff --git a/apps/tlon-web/src/types/ui.ts b/packages/shared/src/urbit/ui.ts similarity index 100% rename from apps/tlon-web/src/types/ui.ts rename to packages/shared/src/urbit/ui.ts diff --git a/apps/tlon-web/src/types/volume.ts b/packages/shared/src/urbit/volume.ts similarity index 100% rename from apps/tlon-web/src/types/volume.ts rename to packages/shared/src/urbit/volume.ts From be9a9ac1fe3ff4c073a42f8a970663ff6763dffb Mon Sep 17 00:00:00 2001 From: Alec Ananian <1013230+alecananian@users.noreply.github.com> Date: Thu, 22 Feb 2024 22:08:05 -0800 Subject: [PATCH 047/182] mobile: generate profile tab icon from user sigil --- apps/tlon-mobile/package.json | 1 + apps/tlon-mobile/src/App.tsx | 2 +- .../tlon-mobile/src/components/UrbitSigil.tsx | 39 +++++ apps/tlon-mobile/src/navigation/TabStack.tsx | 149 +++++++++--------- package-lock.json | 8 +- .../ui/src/components/TamaguiProvider.tsx | 13 +- 6 files changed, 130 insertions(+), 82 deletions(-) create mode 100644 apps/tlon-mobile/src/components/UrbitSigil.tsx diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json index 6c873553e0..6942482964 100644 --- a/apps/tlon-mobile/package.json +++ b/apps/tlon-mobile/package.json @@ -40,6 +40,7 @@ "@react-navigation/native-stack": "^6.9.13", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", + "@urbit/sigil-js": "^2.2.0", "classnames": "^2.3.2", "expo": "^50.0.6", "expo-application": "~5.8.3", diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx index e7f42c7ba2..5d28197ec1 100644 --- a/apps/tlon-mobile/src/App.tsx +++ b/apps/tlon-mobile/src/App.tsx @@ -226,7 +226,7 @@ const App = ({ wer: initialWer }: Props) => { export default function AnalyticsApp(props: Props) { const isDarkMode = useIsDarkMode(); return ( - + diff --git a/apps/tlon-mobile/src/components/UrbitSigil.tsx b/apps/tlon-mobile/src/components/UrbitSigil.tsx new file mode 100644 index 0000000000..2a3637514c --- /dev/null +++ b/apps/tlon-mobile/src/components/UrbitSigil.tsx @@ -0,0 +1,39 @@ +import sigil from '@urbit/sigil-js/dist/core'; +import { useMemo } from 'react'; +import { View } from 'react-native'; +import { SvgXml } from 'react-native-svg'; +import { useTailwind } from 'tailwind-rn'; + +import { useIsDarkMode } from '../hooks/useIsDarkMode'; + +type Props = { + ship: string; +}; + +export const UrbitSigil = ({ ship }: Props) => { + const tailwind = useTailwind(); + const isDarkMode = useIsDarkMode(); + + const sigilXml = useMemo( + () => + sigil({ + point: ship, + detail: 'none', + size: 12, + space: 'none', + foreground: '#ffffff', + background: isDarkMode ? '#333333' : '#000000', + }), + [ship, isDarkMode] + ); + + return ( + + + + ); +}; diff --git a/apps/tlon-mobile/src/navigation/TabStack.tsx b/apps/tlon-mobile/src/navigation/TabStack.tsx index 0fd659064d..a83b17883f 100644 --- a/apps/tlon-mobile/src/navigation/TabStack.tsx +++ b/apps/tlon-mobile/src/navigation/TabStack.tsx @@ -2,82 +2,89 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Icon } from '@tloncorp/ui'; import type { IconType } from '@tloncorp/ui'; +import { UrbitSigil } from '../components/UrbitSigil'; +import { useShip } from '../contexts/ship'; import type { TabParamList } from '../types'; import { WebViewStack } from './WebViewStack'; const Tab = createBottomTabNavigator(); -export const TabStack = () => ( - - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - -); +export const TabStack = () => { + const { ship } = useShip(); + return ( + + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + (ship ? : undefined), + tabBarShowLabel: false, + }} + /> + + ); +}; function TabIcon({ type, diff --git a/package-lock.json b/package-lock.json index 0f3dd2082f..5fc4c87bbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "homestead", + "name": "landscape-apps", "lockfileVersion": 3, "requires": true, "packages": { @@ -36,6 +36,7 @@ "@react-navigation/native-stack": "^6.9.13", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", + "@urbit/sigil-js": "^2.2.0", "classnames": "^2.3.2", "expo": "^50.0.6", "expo-application": "~5.8.3", @@ -17032,8 +17033,9 @@ } }, "node_modules/@urbit/sigil-js": { - "version": "2.1.0", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@urbit/sigil-js/-/sigil-js-2.2.0.tgz", + "integrity": "sha512-UNtRJ0K4CAXe+IP4wARNRMjuI5GTF2MNj9FL5l4RSMddCPJqSWSpkrYcREBI4AOB+80AEyN1kK/jtzhlm5WW+w==", "dependencies": { "invariant": "^2.2.4", "lodash.memoize": "^4.1.2" diff --git a/packages/ui/src/components/TamaguiProvider.tsx b/packages/ui/src/components/TamaguiProvider.tsx index 4695ad4d76..12c91ceb9b 100644 --- a/packages/ui/src/components/TamaguiProvider.tsx +++ b/packages/ui/src/components/TamaguiProvider.tsx @@ -1,10 +1,9 @@ -import { TamaguiProvider as BaseTamaguiProvider } from "tamagui"; +import { + TamaguiProvider as BaseTamaguiProvider, + TamaguiProviderProps, +} from "tamagui"; import { config } from "../tamagui.config"; -export function TamaguiProvider({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); +export function TamaguiProvider(props: TamaguiProviderProps) { + return ; } From 942a8ee178f83839f4a56dbb84e047e745223031 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Fri, 23 Feb 2024 10:25:13 -0500 Subject: [PATCH 048/182] global: ensure cycle error ignore --- apps/tlon-web/src/heap/HeapContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tlon-web/src/heap/HeapContent.tsx b/apps/tlon-web/src/heap/HeapContent.tsx index 25042f3734..e4fb968c3c 100644 --- a/apps/tlon-web/src/heap/HeapContent.tsx +++ b/apps/tlon-web/src/heap/HeapContent.tsx @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-cycle import { Story, VerseBlock, @@ -16,6 +15,7 @@ import { isStrikethrough, } from '@tloncorp/shared/dist/urbit/content'; +// eslint-disable-next-line import/no-cycle import ContentReference from '@/components/References/ContentReference'; interface HeapContentProps { From 203d0e3cf200ffe43e497370bf1c5b4ff4ac42e4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 17:05:12 +0000 Subject: [PATCH 049/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 60e1e8388f..af830452f2 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.u2o33.8vku1.n45v9.q58t2.ikovc.glob' 0v6.u2o33.8vku1.n45v9.q58t2.ikovc] + glob-http+['https://bootstrap.urbit.org/glob-0v3.fhrju.cvgrb.apkh3.8kmnq.6riq3.glob' 0v3.fhrju.cvgrb.apkh3.8kmnq.6riq3] base+'groups' version+[5 5 1] website+'https://tlon.io' From ac353152389a34e5875538e2584554a3620dc6ea Mon Sep 17 00:00:00 2001 From: James Acklin Date: Fri, 23 Feb 2024 13:41:31 -0500 Subject: [PATCH 050/182] upload: handle no publicUrlBase --- apps/tlon-web/src/state/storage/upload.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/tlon-web/src/state/storage/upload.ts b/apps/tlon-web/src/state/storage/upload.ts index b9826d9ff7..5511af5dc1 100644 --- a/apps/tlon-web/src/state/storage/upload.ts +++ b/apps/tlon-web/src/state/storage/upload.ts @@ -255,7 +255,21 @@ export const useFileStore = create((set, get) => ({ }); const { isSecureContext } = window; - const s3Url = new URL(config.publicUrlBase); + + let s3Url: URL; + + if (config.publicUrlBase) { + s3Url = new URL(config.publicUrlBase); + } else { + s3Url = new URL( + await getSignedUrl(client, command) + .then((res) => res.split('?')[0]) + .catch((e) => { + console.log('failed to get signed url', { e }); + return ''; + }) + ); + } const url = config.publicUrlBase ? s3Url.toString() From 2268ae77e99ee01e670b9895fa8b6a103a7ec073 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Feb 2024 20:01:06 +0000 Subject: [PATCH 051/182] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index af830452f2..baf4fdffe0 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v3.fhrju.cvgrb.apkh3.8kmnq.6riq3.glob' 0v3.fhrju.cvgrb.apkh3.8kmnq.6riq3] + glob-http+['https://bootstrap.urbit.org/glob-0v5.1taao.dirna.oenc6.epg2p.g1cfl.glob' 0v5.1taao.dirna.oenc6.epg2p.g1cfl] base+'groups' version+[5 5 1] website+'https://tlon.io' From c0c3991a098288aa9b687809cd0d6aeb5d222554 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Fri, 23 Feb 2024 15:15:06 -0500 Subject: [PATCH 052/182] native: sync contacts --- .../android/app/proguard-rules.pro | 3 + .../ios/Landscape.xcodeproj/project.pbxproj | 22 +- apps/tlon-mobile/ios/Podfile.lock | 8 +- apps/tlon-mobile/package.json | 2 + apps/tlon-mobile/src/App.tsx | 23 +- apps/tlon-mobile/src/db/index.ts | 2 + apps/tlon-mobile/src/db/realm.tsx | 90 ++++++ apps/tlon-mobile/src/db/schemas.ts | 35 +++ apps/tlon-mobile/src/lib/api.ts | 10 + apps/tlon-mobile/src/lib/contactsApi.ts | 16 + apps/tlon-mobile/src/lib/sync.ts | 7 + package-lock.json | 283 ++++++++++++++++-- 12 files changed, 454 insertions(+), 47 deletions(-) create mode 100644 apps/tlon-mobile/src/db/index.ts create mode 100644 apps/tlon-mobile/src/db/realm.tsx create mode 100644 apps/tlon-mobile/src/db/schemas.ts create mode 100644 apps/tlon-mobile/src/lib/contactsApi.ts create mode 100644 apps/tlon-mobile/src/lib/sync.ts diff --git a/apps/tlon-mobile/android/app/proguard-rules.pro b/apps/tlon-mobile/android/app/proguard-rules.pro index 551eb41da2..392da8436c 100644 --- a/apps/tlon-mobile/android/app/proguard-rules.pro +++ b/apps/tlon-mobile/android/app/proguard-rules.pro @@ -11,4 +11,7 @@ -keep class com.swmansion.reanimated.** { *; } -keep class com.facebook.react.turbomodule.** { *; } +# realm +-keep class io.realm.react.** + # Add any project specific keep options here: diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index a3a421cf6c..83a2bb814f 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -482,7 +482,7 @@ mainGroup = 83CBB9F61A601CBA00E9B192; packageReferences = ( 70A62C5F2A5A6B1A00EBED16 /* XCRemoteSwiftPackageReference "SimpleKeychain" */, - 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire" */, + 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire.git" */, 70D3866D2A60A3B300AFB46E /* XCRemoteSwiftPackageReference "UrsusSigil" */, ); productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; @@ -1177,6 +1177,12 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1250,6 +1256,12 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1318,7 +1330,7 @@ minimumVersion = 1.0.0; }; }; - 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire" */ = { + 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire.git"; requirement = { @@ -1342,7 +1354,7 @@ minimumVersion = 1.0.0; }; }; - 70DBBFE32B7C60B50021EA96 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + 70DBBFE32B7C60B50021EA96 /* XCRemoteSwiftPackageReference "Alamofire.git" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Alamofire/Alamofire.git"; requirement = { @@ -1368,7 +1380,7 @@ }; 70D386472A6098F800AFB46E /* Alamofire */ = { isa = XCSwiftPackageProductDependency; - package = 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire" */; + package = 70D386462A6098F800AFB46E /* XCRemoteSwiftPackageReference "Alamofire.git" */; productName = Alamofire; }; 70D3866E2A60A3B300AFB46E /* UrsusSigil */ = { @@ -1383,7 +1395,7 @@ }; 70DBBFE22B7C60B50021EA96 /* Alamofire */ = { isa = XCSwiftPackageProductDependency; - package = 70DBBFE32B7C60B50021EA96 /* XCRemoteSwiftPackageReference "Alamofire" */; + package = 70DBBFE32B7C60B50021EA96 /* XCRemoteSwiftPackageReference "Alamofire.git" */; productName = Alamofire; }; 70DBBFE42B7C60B50021EA96 /* UrsusSigil */ = { diff --git a/apps/tlon-mobile/ios/Podfile.lock b/apps/tlon-mobile/ios/Podfile.lock index 9e88dde902..d120bd8db0 100644 --- a/apps/tlon-mobile/ios/Podfile.lock +++ b/apps/tlon-mobile/ios/Podfile.lock @@ -1174,6 +1174,8 @@ PODS: - React-jsi (= 0.73.4) - React-logger (= 0.73.4) - React-perflogger (= 0.73.4) + - RealmJS (12.6.0): + - React - recaptcha-enterprise-react-native (18.4.0): - React-Core - RecaptchaEnterprise (= 18.4.0) @@ -1289,6 +1291,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - RealmJS (from `../../../node_modules/realm`) - "recaptcha-enterprise-react-native (from `../../../node_modules/@google-cloud/recaptcha-enterprise-react-native`)" - "RNCAsyncStorage (from `../../../node_modules/@react-native-async-storage/async-storage`)" - RNDeviceInfo (from `../../../node_modules/react-native-device-info`) @@ -1468,6 +1471,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RealmJS: + :path: "../../../node_modules/realm" recaptcha-enterprise-react-native: :path: "../../../node_modules/@google-cloud/recaptcha-enterprise-react-native" RNCAsyncStorage: @@ -1579,6 +1584,7 @@ SPEC CHECKSUMS: React-runtimescheduler: 1c054b58fef2ce74cdcbdcd70db190e10f56a617 React-utils: 21a798438d45e70ed9c2e2fe0894ee32ba7b7c5b ReactCommon: dcc65c813041388dead6c8b477444757425ce961 + RealmJS: a62dc7a1f94b888fe9e8712cd650167ad97dc636 recaptcha-enterprise-react-native: 7d63c5bdde3b48996b984a86ac2b536a1d8f5f16 RecaptchaEnterprise: dc302910b77963a0cc6f6908407e30b35268a755 RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21 @@ -1593,7 +1599,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c UMAppLoader: 5df85360d65cabaef544be5424ac64672e648482 - Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 + Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 PODFILE CHECKSUM: 82da24eb176d4abdeaf445b3581717ec492dd7e8 diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json index 6c873553e0..c397a590df 100644 --- a/apps/tlon-mobile/package.json +++ b/apps/tlon-mobile/package.json @@ -38,6 +38,7 @@ "@react-navigation/bottom-tabs": "^6.5.12", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", + "@realm/react": "^0.6.2", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", "classnames": "^2.3.2", @@ -71,6 +72,7 @@ "react-native-storage": "^1.0.1", "react-native-svg": "^14.1.0", "react-native-webview": "13.6.4", + "realm": "^12.6.0", "tailwind-rn": "^4.2.0" }, "devDependencies": { diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx index e7f42c7ba2..37358c8c1d 100644 --- a/apps/tlon-mobile/src/App.tsx +++ b/apps/tlon-mobile/src/App.tsx @@ -17,6 +17,7 @@ import { useTailwind } from 'tailwind-rn'; import { LoadingSpinner } from './components/LoadingSpinner'; import { ShipProvider, useShip } from './contexts/ship'; +import * as db from './db'; import { useDeepLink } from './hooks/useDeepLink'; import { useIsDarkMode } from './hooks/useIsDarkMode'; import { useScreenOptions } from './hooks/useScreenOptions'; @@ -223,17 +224,19 @@ const App = ({ wer: initialWer }: Props) => { ); }; -export default function AnalyticsApp(props: Props) { +export default function ConnectedApp(props: Props) { const isDarkMode = useIsDarkMode(); return ( - - - - - - - - - + + + + + + + + + + + ); } diff --git a/apps/tlon-mobile/src/db/index.ts b/apps/tlon-mobile/src/db/index.ts new file mode 100644 index 0000000000..ac2f66eea9 --- /dev/null +++ b/apps/tlon-mobile/src/db/index.ts @@ -0,0 +1,2 @@ +export * from './realm'; +export * from './schemas'; diff --git a/apps/tlon-mobile/src/db/realm.tsx b/apps/tlon-mobile/src/db/realm.tsx new file mode 100644 index 0000000000..3d46a4a48d --- /dev/null +++ b/apps/tlon-mobile/src/db/realm.tsx @@ -0,0 +1,90 @@ +import { createRealmContext } from '@realm/react'; +import React from 'react'; +import type { PropsWithChildren } from 'react'; +import type Realm from 'realm'; + +import type { SchemaMap, SchemaName } from './schemas'; +import { schemas } from './schemas'; + +// This is a copy of Realm's `UpdateMode` enum. Not ideal, but realm only +// exports `UpdateMode` as a type, which causes a lint error if we try to use it +// directly. +export enum UpdateMode { + Never = 'never', + Modified = 'modified', + All = 'all', +} + +// Realm provider setup + +const config: Realm.Configuration = { + schema: schemas, + schemaVersion: 0, +}; + +const { + RealmProvider: BaseRealmProvider, + useObject, + useQuery, + useRealm, +} = createRealmContext(config); + +let realmInstance: Realm | null = null; + +function realm() { + if (!realmInstance) { + throw new Error('Realm instance not available'); + } + return realmInstance; +} + +// The only straightforward way to get the realm instance here is to use the +// `realmRef` property. Since the property takes a ref, we use a proxy to +// synchronously mirror the set value to the local `realm` variable. +const realmRefProxy = { + set current(val: Realm | null) { + realmInstance = val; + }, +}; + +const RealmProvider = ({ children }: PropsWithChildren) => { + return ( + {children} + ); +}; + +export { RealmProvider, useObject, useQuery, useRealm }; + +// Utility functions + +export function createBatch( + model: T, + data: SchemaMap[T][], + updateMode = UpdateMode.Modified +): SchemaMap[T][] { + return realm().write(() => + data.map((d) => { + return realm().create(model, d, updateMode); + }) + ); +} + +export function create( + model: T, + data: SchemaMap[T], + updateMode = UpdateMode.Modified +): SchemaMap[T] { + return realm().write(() => + realm().create(model, data, updateMode) + ); +} + +export function update( + model: T, + data: Partial, + updateMode = UpdateMode.Modified +): SchemaMap[T] { + return realm().write(() => + realm().create(model, data, updateMode) + ); +} diff --git a/apps/tlon-mobile/src/db/schemas.ts b/apps/tlon-mobile/src/db/schemas.ts new file mode 100644 index 0000000000..8226caa29b --- /dev/null +++ b/apps/tlon-mobile/src/db/schemas.ts @@ -0,0 +1,35 @@ +export type Contact = { + id: string; + nickname?: string; + bio?: string; + status?: string; + color?: string; + avatar?: string | null; + cover?: string | null; + groups?: string[]; +}; + +const contactSchema = { + name: 'Contact', + properties: { + id: 'string', + nickname: 'string?', + bio: 'string?', + status: 'string?', + color: 'string?', + avatar: 'string?', + cover: 'string?', + groups: 'string[]', + }, + primaryKey: 'id', +}; + +// Should contain all schemas, will be passed to Realm constructor +export const schemas = [contactSchema]; + +// Should contain all schema types, used to map Realm object types to TypeScript types +export type SchemaMap = { + Contact: Contact; +}; + +export type SchemaName = keyof SchemaMap; diff --git a/apps/tlon-mobile/src/lib/api.ts b/apps/tlon-mobile/src/lib/api.ts index 6137778379..2246243fde 100644 --- a/apps/tlon-mobile/src/lib/api.ts +++ b/apps/tlon-mobile/src/lib/api.ts @@ -53,3 +53,13 @@ export const poke = async ({ mark, json, }); + +export const scry = async ({ app, path }: { app: string; path: string }) => { + return fetch(`${config.shipUrl}/~/scry/${app}${path}.json`, { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + credentials: 'include', + }).then((res) => res.json()) as Promise; +}; diff --git a/apps/tlon-mobile/src/lib/contactsApi.ts b/apps/tlon-mobile/src/lib/contactsApi.ts new file mode 100644 index 0000000000..b238dfa001 --- /dev/null +++ b/apps/tlon-mobile/src/lib/contactsApi.ts @@ -0,0 +1,16 @@ +import type { ContactRolodex } from '@tloncorp/shared/dist/urbit/contact'; + +import type * as db from '../db'; +import { scry } from './api'; + +export const getContacts = async () => { + const results = await scry({ app: 'contacts', path: '/all' }); + return toClientContacts(results); +}; + +const toClientContacts = (contacts: ContactRolodex): db.Contact[] => { + return Object.entries(contacts).map(([ship, contact]) => ({ + id: ship, + contact, + })); +}; diff --git a/apps/tlon-mobile/src/lib/sync.ts b/apps/tlon-mobile/src/lib/sync.ts new file mode 100644 index 0000000000..d3f02ec585 --- /dev/null +++ b/apps/tlon-mobile/src/lib/sync.ts @@ -0,0 +1,7 @@ +import * as db from '../db'; +import { getContacts } from './contactsApi'; + +export const syncContacts = async () => { + const contacts = await getContacts(); + db.createBatch('Contact', contacts, db.UpdateMode.All); +}; diff --git a/package-lock.json b/package-lock.json index 0f3dd2082f..7292c656c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "homestead", + "name": "landscape-apps", "lockfileVersion": 3, "requires": true, "packages": { @@ -34,6 +34,7 @@ "@react-navigation/bottom-tabs": "^6.5.12", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", + "@realm/react": "^0.6.2", "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", "classnames": "^2.3.2", @@ -67,6 +68,7 @@ "react-native-storage": "^1.0.1", "react-native-svg": "^14.1.0", "react-native-webview": "13.6.4", + "realm": "^12.6.0", "tailwind-rn": "^4.2.0" }, "devDependencies": { @@ -12168,7 +12170,6 @@ "version": "0.73.14", "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.14.tgz", "integrity": "sha512-KzIwsTvAJrXPtwhGOSm+OcJH1B8TpY8cS4xxzu/e2qv3a2n4VLePHTPAfco1tmvekV8OHWvvD9JSIX7i2fB1gg==", - "peer": true, "dependencies": { "@react-native-community/cli-server-api": "12.3.2", "@react-native-community/cli-tools": "12.3.2", @@ -12190,7 +12191,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -12205,7 +12205,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -12221,7 +12220,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -12232,14 +12230,12 @@ "node_modules/@react-native/community-cli-plugin/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/@react-native/community-cli-plugin/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -12262,7 +12258,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "peer": true, "engines": { "node": ">=10" }, @@ -12274,7 +12269,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, "engines": { "node": ">=8" } @@ -12283,7 +12277,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "peer": true, "engines": { "node": ">=10.17.0" } @@ -12292,7 +12285,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -12304,7 +12296,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "peer": true, "engines": { "node": ">=6" } @@ -12313,7 +12304,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -12397,7 +12387,6 @@ "version": "0.73.14", "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.14.tgz", "integrity": "sha512-5wLeYw/lormpSqYfI9H/geZ/EtPmi+x5qLkEit15Q/70hkzYo/M+aWztUtbOITfgTEOP8d6ybROzoGsqgyZLcw==", - "peer": true, "dependencies": { "@babel/core": "^7.20.0", "@react-native/babel-preset": "0.73.20", @@ -12668,6 +12657,22 @@ "nanoid": "^3.1.23" } }, + "node_modules/@realm/react": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@realm/react/-/react-0.6.2.tgz", + "integrity": "sha512-FQ05Ab8Ty9FbKcZtW9i9+eP6pfxwsRV9cwhkVxFJ7kwBoozyAGCByIx3Fwfm/pPTGoHqPMfqaXe5HYEnde/CHg==", + "dependencies": { + "lodash": "^4.17.21" + }, + "optionalDependencies": { + "@babel/runtime": ">=7", + "react-native": ">=0.68" + }, + "peerDependencies": { + "react": ">=17.0.2", + "realm": "^12.0.0-browser || ^12.0.0 || ^12.0.0-rc || ^11.0.0" + } + }, "node_modules/@remirror/core-constants": { "version": "2.0.1", "license": "MIT", @@ -18377,6 +18382,17 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "funding": [ @@ -19805,6 +19821,20 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -21693,6 +21723,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "28.1.3", "dev": true, @@ -22795,6 +22833,11 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -23006,6 +23049,11 @@ "node": ">=6" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -27242,6 +27290,17 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "license": "MIT", @@ -27421,7 +27480,6 @@ }, "node_modules/mkdirp-classic": { "version": "0.5.3", - "dev": true, "license": "MIT" }, "node_modules/mlly": { @@ -27661,6 +27719,11 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -27761,6 +27824,31 @@ "node": ">=12.0.0" } }, + "node_modules/node-abi": { + "version": "3.55.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.55.0.tgz", + "integrity": "sha512-uPEjtyh2tFEvWYt4Jw7McOD5FPcHkcxm/tHZc5PWaDB3JYq0rGFUbgaAK+CT5pYpQddBfsZVWI08OwoRfdfbcQ==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -27824,6 +27912,11 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -29344,6 +29437,70 @@ "fflate": "^0.4.1" } }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/prebuild-install/node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -30215,7 +30372,6 @@ "version": "0.73.3", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.3.tgz", "integrity": "sha512-RSQDtT2DNUcmB4IgmW9NhRb5wqvXFl6DI2NEJmt0ps2OrVHpoA8Tkq+lkFOA/fvPscJKtFKEHFBDSR5UHR3PUw==", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "12.3.2", @@ -30476,7 +30632,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -30492,7 +30647,6 @@ "version": "15.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -30501,7 +30655,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -30516,7 +30669,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -30532,7 +30684,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -30543,14 +30694,12 @@ "node_modules/react-native/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/react-native/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, "engines": { "node": ">=8" } @@ -30559,7 +30708,6 @@ "version": "26.6.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "peer": true, "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", @@ -30573,14 +30721,12 @@ "node_modules/react-native/node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "peer": true + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/react-native/node_modules/scheduler": { "version": "0.24.0-canary-efb381bbf-20230505", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -30589,7 +30735,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -30601,7 +30746,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "peer": true, "dependencies": { "async-limiter": "~1.0.0" } @@ -30986,6 +31130,29 @@ "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" }, + "node_modules/realm": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/realm/-/realm-12.6.0.tgz", + "integrity": "sha512-lwixjVE8uiHXqRggJ9DwCxy3P1I0SUGBFG3dLQnXT20o6PdDVpXsTgE82m0svviKyDLs8yb5hLim5HRcHkH5rA==", + "hasInstallScript": true, + "dependencies": { + "bson": "^4.7.2", + "debug": "^4.3.4", + "node-machine-id": "^1.1.12", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": ">=0.71.0" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + } + } + }, "node_modules/recast": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", @@ -31782,6 +31949,49 @@ "version": "3.0.7", "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-plist": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", @@ -34043,6 +34253,17 @@ "dev": true, "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, From 736617410e41038bb8f2f67bc9b1aee82104b97b Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Fri, 23 Feb 2024 15:21:03 -0500 Subject: [PATCH 053/182] native: run contact sync on login --- apps/tlon-mobile/src/App.tsx | 7 +++++++ apps/tlon-mobile/src/lib/sync.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx index 37358c8c1d..c973a7e990 100644 --- a/apps/tlon-mobile/src/App.tsx +++ b/apps/tlon-mobile/src/App.tsx @@ -22,6 +22,7 @@ import { useDeepLink } from './hooks/useDeepLink'; import { useIsDarkMode } from './hooks/useIsDarkMode'; import { useScreenOptions } from './hooks/useScreenOptions'; import { inviteShipWithLure } from './lib/hostingApi'; +import { syncContacts } from './lib/sync'; import { TabStack } from './navigation/TabStack'; import { CheckVerifyScreen } from './screens/CheckVerifyScreen'; import { EULAScreen } from './screens/EULAScreen'; @@ -56,6 +57,12 @@ const App = ({ wer: initialWer }: Props) => { const screenOptions = useScreenOptions(); const gotoPath = wer ?? initialWer; + useEffect(() => { + if (isAuthenticated) { + syncContacts(); + } + }, [isAuthenticated]); + useEffect(() => { const unsubscribeFromNetInfo = NetInfo.addEventListener( ({ isConnected }) => { diff --git a/apps/tlon-mobile/src/lib/sync.ts b/apps/tlon-mobile/src/lib/sync.ts index d3f02ec585..dd4bcba663 100644 --- a/apps/tlon-mobile/src/lib/sync.ts +++ b/apps/tlon-mobile/src/lib/sync.ts @@ -4,4 +4,5 @@ import { getContacts } from './contactsApi'; export const syncContacts = async () => { const contacts = await getContacts(); db.createBatch('Contact', contacts, db.UpdateMode.All); + console.log('Synced', contacts.length, 'contacts'); }; From 0a1dd8df73882b0dfecfa9225278df2d6625a78b Mon Sep 17 00:00:00 2001 From: James Acklin Date: Mon, 26 Feb 2024 11:40:40 -0500 Subject: [PATCH 054/182] WIP: new AppNav component, route and layout changes --- apps/tlon-web/src/app.tsx | 117 ++++--- .../src/components/Sidebar/AppNav.tsx | 285 ++++++++++++++++++ .../src/components/Sidebar/Sidebar.tsx | 6 - apps/tlon-web/src/nav/GroupsNav.tsx | 2 +- apps/tlon-web/src/profiles/Profile.tsx | 2 +- 5 files changed, 338 insertions(+), 74 deletions(-) create mode 100644 apps/tlon-web/src/components/Sidebar/AppNav.tsx diff --git a/apps/tlon-web/src/app.tsx b/apps/tlon-web/src/app.tsx index faf7be079a..e8c29685cc 100644 --- a/apps/tlon-web/src/app.tsx +++ b/apps/tlon-web/src/app.tsx @@ -32,7 +32,7 @@ import { LeapProvider } from '@/components/Leap/useLeap'; import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner'; import SettingsDialog from '@/components/Settings/SettingsDialog'; import SettingsView from '@/components/Settings/SettingsView'; -import MobileSidebar from '@/components/Sidebar/MobileSidebar'; +import AppNav from '@/components/Sidebar/AppNav'; import DiaryChannel from '@/diary/DiaryChannel'; import DiaryNote from '@/diary/DiaryNote'; import DMHome from '@/dms/DMHome'; @@ -175,38 +175,16 @@ function GroupsRoutes({ state, location, isMobile, isSmall }: RoutesProps) { <> - }> - : undefined}> + }> + }> } /> - - ) : ( - - ) - } - /> - - } - /> + : null} /> : null} /> }> } /> - } /> } /> @@ -257,49 +235,27 @@ function GroupsRoutes({ state, location, isMobile, isSmall }: RoutesProps) { /> )} - } - /> - } - /> - } - /> - } - /> - } - /> } /> } /> }> - : undefined}> - : null} - /> - - } - /> - } - /> - } /> - + : null} + /> + + } + /> + } + /> + } /> }> {!isMobile && ( <> @@ -411,6 +367,35 @@ function GroupsRoutes({ state, location, isMobile, isSmall }: RoutesProps) { + + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> {state?.backgroundLocation ? ( diff --git a/apps/tlon-web/src/components/Sidebar/AppNav.tsx b/apps/tlon-web/src/components/Sidebar/AppNav.tsx new file mode 100644 index 0000000000..699432b84c --- /dev/null +++ b/apps/tlon-web/src/components/Sidebar/AppNav.tsx @@ -0,0 +1,285 @@ +import cn from 'classnames'; +import { useContext, useEffect, useState } from 'react'; +import { Outlet, useLocation, useNavigate } from 'react-router'; + +import Asterisk16Icon from '@/components/icons/Asterisk16Icon'; +import { useChatInputFocus } from '@/logic/ChatInputFocusContext'; +import { isNativeApp, useSafeAreaInsets } from '@/logic/native'; +import { AppUpdateContext } from '@/logic/useAppUpdates'; +import { useIsAnyGroupUnread } from '@/logic/useIsGroupUnread'; +import { useIsDark, useIsMobile } from '@/logic/useMedia'; +import useShowTabBar from '@/logic/useShowTabBar'; +import { useNotifications } from '@/notifications/useNotifications'; +import { useHasUnreadMessages } from '@/state/chat'; +import { useCharge } from '@/state/docket'; +import { useLocalState } from '@/state/local'; + +import Avatar from '../Avatar'; +import NavTab, { DoubleClickableNavTab } from '../NavTab'; +import BellIcon from '../icons/BellIcon'; +import HomeIconMobileNav from '../icons/HomeIconMobileNav'; +import MessagesIcon from '../icons/MessagesIcon'; + +function GroupsTab(props: { isInactive: boolean; isDarkMode: boolean }) { + const navigate = useNavigate(); + const { groupsLocation } = useLocalState.getState(); + const groupsUnread = useIsAnyGroupUnread(); + const isMobile = useIsMobile(); + + const onSingleClick = () => { + if (isNativeApp()) { + if (props.isInactive) { + navigate(groupsLocation); + } + } else { + navigate('/'); + } + }; + + return ( + navigate('/')} + linkClass="h-full !pb-0 flex flex-col items-start justify-start" + > +
+
+ +
+
+
+
+ + ); +} + +function MessagesTab(props: { isInactive: boolean; isDarkMode: boolean }) { + const navigate = useNavigate(); + const { messagesLocation } = useLocalState.getState(); + const hasUnreads = useHasUnreadMessages(); + + const onSingleClick = () => { + if (isNativeApp()) { + if (props.isInactive) { + navigate(messagesLocation); + } + } else { + navigate('/messages'); + } + }; + + return ( + navigate('/messages')} + linkClass="h-full !pb-0 flex flex-col items-start justify-start" + > +
+
+ +
+
+
+
+ + ); +} + +function ActivityTab(props: { isInactive: boolean; isDarkMode: boolean }) { + const navigate = useNavigate(); + const { count } = useNotifications('', 'all'); + + return ( + navigate('/notifications')} + onDoubleClick={() => navigate('/notifications')} + linkClass="h-full !pb-0 flex flex-col items-start justify-start" + > +
+
+ +
+
+
0 && 'bg-blue' + )} + /> +
+ + ); +} + +export default function AppNav() { + const isMobile = useIsMobile(); + const location = useLocation(); + const navigate = useNavigate(); + const [informedOfUpdate, setInformedOfUpdate] = useState(false); + const isInactive = (path: string) => !location.pathname.startsWith(path); + const isDarkMode = useIsDark(); + const { needsUpdate } = useContext(AppUpdateContext); + const safeAreaInsets = useSafeAreaInsets(); + const { isChatInputFocused } = useChatInputFocus(); + const groupsCharge = useCharge('groups'); + const showTabBar = useShowTabBar(); + + useEffect(() => { + if (groupsCharge && needsUpdate && !informedOfUpdate) { + navigate('/update-needed', { state: { backgroundLocation: location } }); + setInformedOfUpdate(true); + } + }, [ + needsUpdate, + navigate, + location, + informedOfUpdate, + setInformedOfUpdate, + groupsCharge, + ]); + + if (!isMobile) { + return ( +
+ +
+ +
+
+ ); + } + return ( +
+ + {showTabBar ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/apps/tlon-web/src/components/Sidebar/Sidebar.tsx b/apps/tlon-web/src/components/Sidebar/Sidebar.tsx index 8b64e64ce3..b0f68c1c5e 100644 --- a/apps/tlon-web/src/components/Sidebar/Sidebar.tsx +++ b/apps/tlon-web/src/components/Sidebar/Sidebar.tsx @@ -145,14 +145,8 @@ export default function Sidebar() { [newGroups] ); - if (isMobile) { - return ; - } - return ( -
- -
-
-

Connect with Others

- - - + {!isMobile && ( +
+
-

- Anybody on Tlon can use this link to send you a direct message. -

-
-
- {dmLink ? ( - - ) : ( - - )} -
- -
+ )} +
+ ); } diff --git a/apps/tlon-web/src/profiles/ProfileCoverImage.tsx b/apps/tlon-web/src/profiles/ProfileCoverImage.tsx index ff054aef0b..70380b32f5 100644 --- a/apps/tlon-web/src/profiles/ProfileCoverImage.tsx +++ b/apps/tlon-web/src/profiles/ProfileCoverImage.tsx @@ -33,7 +33,7 @@ export default function ProfileCoverImage({ return (
{ + async function populateLink() { + const link = await getDmLink(); + setDmLink(link || ''); + } + populateLink(); + }, []); + + return ( + + ) : null + } + className="flex flex-col" + mainClass="overflow-scroll flex-grow h-full bg-gray-50" + > + + {title} + +
+ {!isMobile && ( +
+

Share with Friends

+

+ Share the Tlon app with friends, invite them to join +

+
+ )} +
+
+

+ Invite someone from outside the Urbit network +

+

Courtesy of Tlon Hosting

+
+

+ Have friends, family or collaborators who aren’t on Urbit? You can + now gift them an Urbit ID and onboard them to your group all in one + free and easy sweep. Just copy and share the link below. +

+ {dmLink ? ( + + ) : ( + + )} +
+
+
+ ); +} From 4a32e6719e2a8c285b5fbb137c3ac81c1c433d14 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Tue, 27 Feb 2024 15:26:21 -0500 Subject: [PATCH 085/182] conflicts: fix mistake with tabstack conflict resolution --- .../ios/Landscape.xcodeproj/project.pbxproj | 6 + apps/tlon-mobile/src/navigation/TabStack.tsx | 144 +++++++++--------- 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 3cf1917811..67d9a63546 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1189,6 +1189,9 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -1274,6 +1277,9 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", + " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/apps/tlon-mobile/src/navigation/TabStack.tsx b/apps/tlon-mobile/src/navigation/TabStack.tsx index e0f4b7171a..348ccd94d9 100644 --- a/apps/tlon-mobile/src/navigation/TabStack.tsx +++ b/apps/tlon-mobile/src/navigation/TabStack.tsx @@ -14,78 +14,80 @@ import { WebViewStack } from './WebViewStack'; const Tab = createBottomTabNavigator(); -export const TabStack = () => ( - - - - - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - ( - - ), - tabBarShowLabel: false, - }} - /> - +export const TabStack = () => { + return ( + + + + + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + ( + + ), + tabBarShowLabel: false, + }} + /> + - - - - -); + + + + + ); +}; function TabIcon({ type, From 81141f1c62ff6024d73f8ff3199f86c9ea1cf4e0 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Tue, 27 Feb 2024 15:54:00 -0500 Subject: [PATCH 086/182] whoops, actually fix that conflict mistake --- apps/tlon-mobile/src/navigation/TabStack.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/tlon-mobile/src/navigation/TabStack.tsx b/apps/tlon-mobile/src/navigation/TabStack.tsx index 348ccd94d9..72d1171e97 100644 --- a/apps/tlon-mobile/src/navigation/TabStack.tsx +++ b/apps/tlon-mobile/src/navigation/TabStack.tsx @@ -1,8 +1,9 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import { Icon, View, ZStack } from '@tloncorp/ui'; +import { Icon, UrbitSigil, View, ZStack } from '@tloncorp/ui'; import type { IconType } from '@tloncorp/ui'; import { SingletonWebview } from '../components/SingletonWebview'; +import { useShip } from '../contexts/ship'; import { WebviewPositionProvider, useWebviewPositionContext, @@ -15,6 +16,7 @@ import { WebViewStack } from './WebViewStack'; const Tab = createBottomTabNavigator(); export const TabStack = () => { + const { ship } = useShip(); return ( @@ -74,9 +76,9 @@ export const TabStack = () => { component={WebViewStack} initialParams={{ initialPath: getInitialPath('Profile') }} options={{ - tabBarIcon: ({ focused }) => ( - - ), + tabBarIcon: () => + ship ? : undefined, + tabBarShowLabel: false, }} /> From accd470fe6bfe131638782c5c8662204ef5f9112 Mon Sep 17 00:00:00 2001 From: James Acklin Date: Tue, 27 Feb 2024 16:09:35 -0500 Subject: [PATCH 087/182] profile: redesign profile cover component to fit sidebar --- .../src/profiles/EditProfile/EditProfile.tsx | 8 ++- apps/tlon-web/src/profiles/Profile.tsx | 54 ++++++++++--------- .../src/profiles/ProfileCoverImage.tsx | 25 ++------- 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx b/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx index 7a8e5f00d2..ba9a813a9a 100644 --- a/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx +++ b/apps/tlon-web/src/profiles/EditProfile/EditProfile.tsx @@ -5,6 +5,7 @@ import { ContactEditField, } from '@tloncorp/shared/dist/urbit/contact'; import { ViewProps } from '@tloncorp/shared/dist/urbit/groups'; +import cn from 'classnames'; import _ from 'lodash'; import React, { useCallback, useEffect, useState } from 'react'; import { Helmet } from 'react-helmet'; @@ -148,7 +149,7 @@ function EditProfileContent() { return (