on main help sign in
frontend support for search
commit74331e004e0204bbc326fe605688b853c736a25e authorJohn Hyde <johnhydefour@gmail.com> dateTue 14 Jun 2022, 10:45 (4 years ago) parent9ea2d6f
15 files changed+288−111
desk/app/astrolabe.hoon+4−1 desk/lib/astrolabe.hoon+17−7 ui/package-lock.json+3−4 ui/package.json+1 ui/src/App.svelte+2−1 ui/src/components/SearchResults.svelte+79−55 ui/src/components/ShipListing.svelte+17−11 ui/src/components/ShipListings.svelte+19 ui/src/components/ShipView.svelte+11−15 ui/src/lib/api.ts+63−3 ui/src/main.ts+3 ui/src/routes/ShipViewPage.svelte+12−4 ui/src/stores/store.ts+19−4 ui/src/styles/tailwind-output.css+33−4 ui/src/types/store.ts+5−2
desk/app/astrolabe.hoon+4−1
@@ -145,6 +145,9 @@
145145 =? state ?=([%tx [* * * %spawn ship *] *] diff)
146146 %- put-in-opoints
147147 ship.tx.raw-tx.diff
148+ =? state ?=([%point @ %owner *] diff)
149+ %- put-in-opoints
150+ ship.diff
148151 `state
149152::
150153++ is-spawn-tx
@@ -248,7 +251,7 @@
248251 ?: (gth pain.spoints 2.820) ~|(%search-predicted-too-broad !!)
249252 =/ results
250253 %-
251− (run-wildcard-search (list @q) search-full points.spoints *page-info)
254+ (run-wildcard-search (list @q) search-full points.spoints [0 0])
252255 |= [agg=(list @q) result=@q]
253256 [result agg]
254257 ~& "returning {<(lent agg.results)>} of {<count.results>} results"
desk/lib/astrolabe.hoon+17−7
@@ -162,11 +162,17 @@
162162 ?: |(=(i.search i.cand) =(i.search '*'))
163163 $(search t.search, cand t.cand)
164164 %.n
165+++ tosd
166+ |= [a=@ =pfsf]
167+ %- ?:(=(%prefix pfsf) tos:po tod:po)
168+ a
169+++ syl-equals
170+ |= [=syl cand=@]
171+ =((crip tape.syl) (tosd cand pfsf.syl))
165172++ syl-matches
166173 |= [=syl cand=@]
167174 :: todo check cand < 256
168− =/ tof ?: =(%prefix pfsf.syl) tos:po tod:po
169− (tape-matches tape.syl (trip (tof cand)))
175+ (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
170176++ matches-search
171177 |= [search=search-syls cand-atom=@]
172178 =/ cand (rip 3 cand-atom)
@@ -175,11 +181,15 @@
175181 ?~ search =(cand ~)
176182 :: =? cand =(cand ~)
177183 :: `(lest @)`~[0]
178− =/ nncand ^- (lest @)
179− ?~ cand ~[0] cand
180− ?: (syl-matches i.search i.nncand)
181− $(search t.search, cand t.nncand)
182− %.n
184+ ?~ cand
185+ ?& (syl-equals i.search 0)
186+ $(search t.search)
187+ ==
188+ :: =/ nncand ^- (lest @)
189+ :: ?~ cand ~[0] cand
190+ ?& (syl-matches i.search i.cand)
191+ $(search t.search, cand t.cand)
192+ ==
183193::
184194++ opts-in-range
185195 |= [lb=@ rb=@ opts=(list @)]
ui/package-lock.json+3−4
@@ -13,6 +13,7 @@
1313 "@urbit/api": "^2.1.0",
1414 "@urbit/http-api": "^2.1.0",
1515 "bn.js": "^5.2.0",
16+ "lodash": "^4.17.21",
1617 "svelte-copy-to-clipboard": "^0.2.5",
1718 "tippy.js": "^6.3.7",
1819 "urbit-ob": "^5.0.1"
@@ -1606,8 +1607,7 @@
16061607 "node_modules/lodash": {
16071608 "version": "4.17.21",
16081609 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1609− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
1610− "dev": true
1610+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
16111611 },
16121612 "node_modules/lodash.chunk": {
16131613 "version": "4.2.0",
@@ -4049,8 +4049,7 @@
40494049 "lodash": {
40504050 "version": "4.17.21",
40514051 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4052− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
4053− "dev": true
4052+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
40544053 },
40554054 "lodash.chunk": {
40564055 "version": "4.2.0",
ui/package.json+1
@@ -37,6 +37,7 @@
3737 "@urbit/api": "^2.1.0",
3838 "@urbit/http-api": "^2.1.0",
3939 "bn.js": "^5.2.0",
40+ "lodash": "^4.17.21",
4041 "svelte-copy-to-clipboard": "^0.2.5",
4142 "tippy.js": "^6.3.7",
4243 "urbit-ob": "^5.0.1"
ui/src/App.svelte+2−1
@@ -11,6 +11,7 @@
1111 '/help/:path?': HelpPage,
1212 // '/:patp?': ShipViewPage,
1313 '/ship/:patp?': ShipViewPage,
14+ '/search/:patp?': ShipViewPage,
1415 '*': ShipViewPage,
1516 }
1617</script>
@@ -18,4 +19,4 @@
1819<main class="h-full overflow-y-auto">
1920 <HelpModal />
2021 <Router {routes} />
21−</main>
22+</main>
ui/src/components/SearchResults.svelte+79−55
@@ -1,45 +1,65 @@
11<script lang="ts">
2− import { Groups, Rolodex } from '@urbit/api';
2+
3+ import type { Patp, Rolodex } from '@urbit/api';
34 import { searchPoints } from '../lib/api';
45 import store from '../stores/store';
5− import ShipListing from './ShipListing.svelte';
6− import SearchResultNavButtons from './SearchResultNavButtons.svelte';
7− import ShipView from './ShipView.svelte';
6+ import ShipListings from './ShipListings.svelte';
7+
88 export let query: RegExp;
99 export let search: string;
1010 // export let patp: string;
11− let contactsList = [];
11+ let combinedSearchResults = [];
12+ let searchedPoints: Patp[] = [];
1213 let selectedShipIndex: number;
1314
1415 $: searchedPointsP = searchPoints(search)
15− // $: {
16− // // if (!patp) {
17− // store.search(query)
18− // // }
19− // // if (searchQuery) {
20− // // store.search(searchQuery)
21− // // } else {
22− // // store.reset();
23− // // }
24− // }
25− // $: {
26− // contactsList = Object.entries($store?.contacts).map(([patp, ship]) => {
27− // return {
28− // ...ship,
29− // patp,
30− // };
31− // });
32− // if (contactsList.length === 1) {
33− // selectedShipIndex = 0;
34− // } else {
35− // selectedShipIndex = null;
36− // }
37− // }
38− // $: selectedShip = contactsList[selectedShipIndex]
16+ $: {
17+ searchedPointsP.then((points) => {
18+ searchedPoints = points;
19+ });
20+ }
21+
22+ $: {
23+ // if (!patp) {
24+ store.search(query)
25+ // }
26+ // if (searchQuery) {
27+ // store.search(searchQuery)
28+ // } else {
29+ // store.reset();
30+ // }
31+ }
32+ $: {
33+ const contactSearchResults = JSON.parse(JSON.stringify($store.contactSearchResults));
34+ const contacts = [];
35+ const nonContacts = [];
36+ searchedPoints.forEach(patp => {
37+ const contact = contactSearchResults[patp];
38+ if (contact) {
39+ contacts.push({ ...contact, patp });
40+ delete contactSearchResults[patp];
41+ } else {
42+ nonContacts.push({ patp });
43+ }
44+ });
45+ const nonAzimuthContacts = Object.entries(contactSearchResults).map(([patp, ship]) => {
46+ return {
47+ ...ship,
48+ patp,
49+ };
50+ });
51+ combinedSearchResults = [...contacts, ...nonAzimuthContacts, ...nonContacts];
52+ if (combinedSearchResults.length === 1) {
53+ selectedShipIndex = 0;
54+ } else {
55+ selectedShipIndex = null;
56+ }
57+ }
58+ $: selectedShip = combinedSearchResults[selectedShipIndex]
3959
40− // function updateIndex(e) {
41− // selectedShipIndex = e.detail.index;
42− // }
60+ function updateIndex(e) {
61+ selectedShipIndex = e.detail.index;
62+ }
4363</script>
4464
4565<!-- should actually switch on whether field is empty -->
@@ -51,32 +71,36 @@
5171{/if} -->
5272<!-- {#if patp}
5373 <ShipView {patp} />
54−{:else if selectedShip}
55− {#if contactsList.length > 1}
74+{:else if selectedShip}-->
75+ <!-- {#if contactsList.length > 1}
5676 <SearchResultNavButtons
5777 index={selectedShipIndex}
5878 totalResults={contactsList.length}
5979 on:updateIndex={updateIndex}
6080 />
61− {/if}
62− <ShipView patp={selectedShip.patp} />
81+ {/if} -->
82+<!-- <ShipView patp={selectedShip.patp} />
6383{:else} -->
64−{query}
65−{search}
66−{#await searchedPointsP}
67−Loading...
68−{:then { points }}
69− <div class="max-w-md w-full rounded-lg bg-white">
70− <!-- {#each contactsList as contact, i (contact.patp)}
71− <ShipListing ship={contact} on:click={() => selectedShipIndex = i}/>
72− {/each} -->
73− {#each points as patp, i (patp)}
74− <ShipListing ship={{ patp }} linkToShip />
75− {/each}
76− </div>
77−{:catch}
78− <div class="max-w-md w-full rounded-lg bg-white">
79− Search Too Broad
80− </div>
81−{/await}
82−<!-- {/if} -->
84+<div class="max-w-md w-full rounded-lg bg-white overflow-hidden">
85+ {#await searchedPointsP}
86+ <p class="p-8 text-center">
87+ Searching all spawned points...
88+ </p>
89+ {:catch}
90+ <p class="p-8 text-center">
91+ Your search is too broad, please be more specific.
92+ </p>
93+ {/await}
94+ {#if combinedSearchResults.length === 0}
95+ <p class="p-8 text-center">
96+ No spawned ships match your search.
97+ </p>
98+ {:else}
99+ <ShipListings
100+ ships={combinedSearchResults}
101+ on:click={({ detail: i }) => selectedShipIndex = i}
102+ linkToShips differentiateContacts
103+ />
104+ {/if}
105+</div>
106+ <!-- {/if} -->
ui/src/components/ShipListing.svelte+17−11
@@ -6,8 +6,12 @@
66 import { linkToShip as generateLinkToShip } from '../lib/link';
77 export let ship: any;
88 export let linkToShip: boolean = false;
9+ export let differentiateContacts = false;
910
1011 $: patp = normalizeId(ship.patp);
12+ $: borderClass = differentiateContacts ? (
13+ 'border-l-8 ' + (Object.keys(ship).length > 1 ? 'border-gold' : 'border-gray-300')
14+ ) : '';
1115
1216 function handleClick() {
1317 if (linkToShip) {
@@ -16,15 +20,17 @@
1620 }
1721</script>
1822
19−<div class="flex flex-wrap p-4 border-b last:border-none items-center hover:bg-gray-300 cursor-pointer"
20− on:click={handleClick}
21− on:click
22− >
23− <Sigil {patp} size={48} />
24− <!-- {#if ship.avatar}
25− <img src={ship.avatar} class="h-12 w-12 mr-4" />
26− {/if} -->
27− <p class="ml-4 break-words" >
28− {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
29− </p>
23+<div class="hover:bg-gray-200 {borderClass} cursor-pointer"
24+on:click={handleClick}
25+on:click
26+>
27+ <div class="flex flex-wrap p-4 items-center">
28+ <Sigil {patp} size={48} />
29+ {#if ship.avatar}
30+ <img src={ship.avatar} class="h-12 w-12 mr-4" />
31+ {/if}
32+ <p class="ml-4 break-words">
33+ {patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
34+ </p>
35+ </div>
3036</div>
ui/src/components/ShipListings.sveltenew file+19
@@ -0,0 +1,19 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import ShipListing from './ShipListing.svelte';
4+ const dispatch = createEventDispatcher();
5+
6+ export let ships = [];
7+ export let linkToShips = false;
8+ export let differentiateContacts = false;
9+
10+ function onClick(index) {
11+ dispatch('click', index);
12+ }
13+</script>
14+
15+<div class="divide-y">
16+ {#each ships as ship, i (ship.patp)}
17+ <ShipListing {ship} on:click={() => onClick(i)} linkToShip={linkToShips} {differentiateContacts} />
18+ {/each}
19+</div>
ui/src/components/ShipView.svelte+11−15
@@ -10,7 +10,7 @@
1010 import EthAddressLink from './EthAddressLink.svelte';
1111 import CollapsibleContent from './CollapsibleContent.svelte';
1212 import ShipChain from './ShipChain.svelte';
13− import ShipListing from './ShipListing.svelte';
13+ import ShipListings from './ShipListings.svelte';
1414
1515 export let patp: string;
1616
@@ -103,7 +103,7 @@
103103 $: {
104104 spawnedPoints = [];
105105 if (rawSpawnedPoints?.points) {
106− spawnedPoints = rawSpawnedPoints?.points.map(patp => normalizeId(patp));
106+ spawnedPoints = rawSpawnedPoints?.points.map(patp => ({ patp: normalizeId(patp) }));
107107 }
108108 }
109109 $: {
@@ -232,16 +232,11 @@
232232 {:then}
233233 {#if shipClass !== 'galaxy'}
234234 <p>
235− {#if pointInfo.sponsor && shipClass !== 'galaxy'}
236− <!-- Sponsor:
237− <ShipLink patp={pointInfo.sponsor}></ShipLink> -->
238−
239− {#if sponsorChain.length > 0}
240− <p>
241− {sponsorChain.length === 1 ? 'Sponsor:' : 'Sponsor Chain:'}
242− <ShipChain shipChain={sponsorChain}></ShipChain>
243− </p>
244− {/if}
235+ {#if sponsorChain && sponsorChain.length > 0}
236+ <p>
237+ {sponsorChain.length === 1 ? 'Sponsor:' : 'Sponsor Chain:'}
238+ <ShipChain shipChain={sponsorChain}></ShipChain>
239+ </p>
245240 {:else}
246241 Unsponsored
247242 {/if}
@@ -337,9 +332,10 @@
337332 Loading {spawnableClassPlural}...
338333 {:then}
339334 <!-- TODO: pagination -->
340− {#each spawnedPoints as patp, i}
341− <ShipListing ship={{ patp }} linkToShip />
342− {/each}
335+ <ShipListings
336+ ships={spawnedPoints}
337+ linkToShips
338+ />
343339 {/await}
344340 </div>
345341 {/if}
ui/src/lib/api.ts+63−3
@@ -1,6 +1,14 @@
11import UrbitApi from '@urbit/http-api';
2+import type { ContactUpdate, Patp, Group, Contact, Rolodex } from '@urbit/api';
3+import _get from 'lodash/get';
4+import { compose } from 'lodash/fp';
5+
6+import { normalizeId } from './id';
7+
28console.log(`Initializing Urbit API at ${Date()}`);
3−const api: UrbitApi = new UrbitApi('');
9+const api: UrbitApi = new UrbitApi('', '', window.desk);
10+api.ship = window.ship;
11+// api.connect();
412
513function scry<T = any>(path: string): Promise<T> {
614 return api.scry<T>({ app: 'astrolabe', path });
@@ -14,14 +22,65 @@ function getSpawnedPoints(patp: string): Promise<any> {
1422 return scry<any>(`/point/${patp}/spawned`);
1523}
1624
17−function searchPoints(search: string): Promise<any> {
18− return scry<any>(`/search/${search}`);
25+async function searchPoints(search: string): Promise<Patp[]> {
26+ const { points } = await scry<any>(`/search/${search}`);
27+ return points.map(normalizeId);
1928}
2029
2130function getDoc(path: string): Promise<string> {
2231 return scry<string>(`/doc/${path}`);
2332}
2433
34+function initialContacts(json: ContactUpdate, contacts: Rolodex): Rolodex {
35+ const data = _get(json, 'initial', false);
36+ if (data) {
37+ contacts = data.rolodex;
38+ }
39+ return contacts;
40+};
41+
42+function addContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
43+ const data = _get(json, 'add', false);
44+ if (data) {
45+ contacts[data.ship] = data.contact;
46+ }
47+ return contacts;
48+};
49+
50+function removeContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
51+ const data = _get(json, 'remove', false);
52+ if (
53+ data &&
54+ (data.ship in contacts)
55+ ) {
56+ delete contacts[data.ship];
57+ }
58+ return contacts;
59+};
60+
61+function handleContactUpdateEvent(updateContacts) {
62+ return ({ 'contact-update': contactUpdate }) => {
63+ console.log(`received contact-update: ${Object.keys(contactUpdate)}`)
64+ const reducers = [initialContacts, addContact, removeContact];
65+ const reducer = compose(reducers.map(r => sta => r(contactUpdate, sta)));
66+ updateContacts(reducer);
67+ };
68+}
69+
70+function subscribeToContacts(e: (data: any) => void): Promise<any> {
71+ return api.subscribe({
72+ app: 'contact-store',
73+ path: '/all',
74+ event: handleContactUpdateEvent(e),
75+ err: () => {
76+ console.error(`Subscription to contact-store/all just get "err"`);
77+ },
78+ quit: () => {
79+ console.error(`Subscription to contact-store/all just get "quit"`)
80+ }
81+ })
82+}
83+
2584export {
2685 api,
2786 scry,
@@ -29,4 +88,5 @@ export {
2988 getSpawnedPoints,
3089 searchPoints,
3190 getDoc,
91+ subscribeToContacts,
3292};
ui/src/main.ts+3
@@ -1,5 +1,8 @@
11import App from './App.svelte'
22
3+window.desk = window.desk || 'astrolabe';
4+window.ship = window.ship || 'zod';
5+
36const app = new App({
47 target: document.getElementById('app'),
58})
ui/src/routes/ShipViewPage.svelte+12−4
@@ -24,11 +24,19 @@
2424 if (paramChangedMoreRecently) {
2525 search = params.patp;
2626 } else {
27− if (analysis.patpIsValid && analysis.search === search) {
28− if (analysis.patp === search) {
29− push(linkToShip(analysis.patp));
27+ if (analysis.search === search) {
28+ if (!analysis.patpIsValid) {
29+ if (search) {
30+ push(`/search/${search}`);
31+ } else {
32+ push('/');
33+ }
3034 } else {
31− push(linkToShip(analysis.patp, { search }));
35+ if (analysis.patp === search) {
36+ push(linkToShip(analysis.patp));
37+ } else {
38+ push(linkToShip(analysis.patp, { search }));
39+ }
3240 }
3341 }
3442 }
ui/src/stores/store.ts+19−4
@@ -1,6 +1,7 @@
11import { writable } from 'svelte/store';
22
3−import { api } from '../lib/api';
3+import type { ContactUpdate, Patp, Group, Contact, Rolodex } from '@urbit/api';
4+import { subscribeToContacts } from '../lib/api';
45import type { StoreState, Contacts } from '../types/store';
56import { filterObject } from '../lib/utils';
67import fakeContacts from '../fake_data/fake-contacts';
@@ -9,6 +10,7 @@ const initStore: StoreState = {
910 // chats: [],
1011 // groups: {},
1112 contacts: {},
13+ contactSearchResults: {},
1214 connection: 'disconnected',
1315 ship: window.ship,
1416};
@@ -21,16 +23,16 @@ function search(idQuery: RegExp) {
2123 if (!idQuery) {
2224 return {
2325 ...$store,
24− contacts: fakeContacts,
26+ contactSearchResults: $store.contacts,
2527 };
2628 }
2729 // const filteredContacts = filterObject($store.contacts, (id, contact) => {
28− const filteredContacts = filterObject(fakeContacts, (id) => {
30+ const filteredContacts = filterObject($store.contacts, (id) => {
2931 return idQuery.test(id);
3032 }) as Contacts;
3133 return {
3234 ...$store,
33− contacts: filteredContacts,
35+ contactSearchResults: filteredContacts,
3436 }
3537 });
3638}
@@ -39,5 +41,18 @@ function reset() {
3941 set(initStore);
4042}
4143
44+function updateContacts(callback: (contacts: Rolodex) => Rolodex): void {
45+ update($store => {
46+ return {
47+ ...$store,
48+ contacts: callback($store.contacts),
49+ };
50+ });
51+}
52+
53+// setTimeout(() => {
54+ subscribeToContacts(updateContacts);
55+// }, 1000);
56+
4257
4358export default { subscribe, search, reset };
ui/src/styles/tailwind-output.css+33−4
@@ -720,6 +720,11 @@ Ensure the default browser behavior of the `hidden` attribute.
720720 margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
721721 margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
722722}
723+.divide-y > :not([hidden]) ~ :not([hidden]) {
724+ --tw-divide-y-reverse: 0;
725+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
726+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
727+}
723728.justify-self-start {
724729 justify-self: start;
725730}
@@ -729,6 +734,9 @@ Ensure the default browser behavior of the `hidden` attribute.
729734.overflow-auto {
730735 overflow: auto;
731736}
737+.overflow-hidden {
738+ overflow: hidden;
739+}
732740.overflow-scroll {
733741 overflow: scroll;
734742}
@@ -762,12 +770,21 @@ Ensure the default browser behavior of the `hidden` attribute.
762770.border-4 {
763771 border-width: 4px;
764772}
773+.border-8 {
774+ border-width: 8px;
775+}
765776.border-b {
766777 border-bottom-width: 1px;
767778}
768779.border-t {
769780 border-top-width: 1px;
770781}
782+.border-l {
783+ border-left-width: 1px;
784+}
785+.border-l-8 {
786+ border-left-width: 8px;
787+}
771788.border-white {
772789 --tw-border-opacity: 1;
773790 border-color: rgb(255 255 255 / var(--tw-border-opacity));
@@ -784,6 +801,14 @@ Ensure the default browser behavior of the `hidden` attribute.
784801 --tw-border-opacity: 1;
785802 border-color: rgb(252 211 77 / var(--tw-border-opacity));
786803}
804+.border-gray-300 {
805+ --tw-border-opacity: 1;
806+ border-color: rgb(209 213 219 / var(--tw-border-opacity));
807+}
808+.border-gray-400 {
809+ --tw-border-opacity: 1;
810+ border-color: rgb(156 163 175 / var(--tw-border-opacity));
811+}
787812.bg-gold {
788813 --tw-bg-opacity: 1;
789814 background-color: rgb(252 211 77 / var(--tw-bg-opacity));
@@ -1068,6 +1093,14 @@ Ensure the default browser behavior of the `hidden` attribute.
10681093 --tw-bg-opacity: 1;
10691094 background-color: rgb(209 213 219 / var(--tw-bg-opacity));
10701095}
1096+.hover\:bg-gray-400:hover {
1097+ --tw-bg-opacity: 1;
1098+ background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1099+}
1100+.hover\:bg-gray-200:hover {
1101+ --tw-bg-opacity: 1;
1102+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1103+}
10711104.hover\:opacity-40:hover {
10721105 opacity: 0.4;
10731106}
@@ -1106,10 +1139,6 @@ Ensure the default browser behavior of the `hidden` attribute.
11061139 max-width: 42rem;
11071140 }
11081141
1109− .md\:max-w-lg {
1110− max-width: 32rem;
1111− }
1112−
11131142 .md\:flex-row {
11141143 flex-direction: row;
11151144 }
ui/src/types/store.ts+5−2
@@ -1,5 +1,5 @@
11import type { ConnectionStatus } from './connection';
2−import type { Patp, Group, Contact } from '@urbit/api';
2+import type { Patp, Group, Contact, Rolodex } from '@urbit/api';
33export interface Contacts {
44 [contact: string]: Contact;
55};
@@ -12,7 +12,10 @@ export interface StoreState {
1212 // groups: {
1313 // [group: string]: Group;
1414 // };
15− contacts: Contacts;
15+ contacts: Rolodex;
16+ contactSearchResults: Rolodex;
17+ // contacts: Contacts;
18+ // contactSearchResults: Contacts;
1619 // chats: [];
1720 connection: ConnectionStatus;
1821 subscription?: unknown;