on main help sign in
226 files changed+15372−1259
since splitsnapshots
unifiedsplit
.gitignore+2 .vscode/settings.json+58 .vscode/tasks.json+13 README.md+47−21 base-desk/lib/agentio.hoon+4−6 base-desk/lib/azimuth.hoon+9−9 base-desk/lib/azimuthio.hoon+3−3 base-desk/lib/bip/b173.hoon+1−1 base-desk/lib/bip/b174.hoon+2 base-desk/lib/bitcoin-utils.hoon+2−1 base-desk/lib/dbug.hoon+9−3 base-desk/lib/dill.hoon+94 base-desk/lib/ethereum.hoon+28−4 base-desk/lib/ethio.hoon+3−3 base-desk/lib/hark-store.hoon+22 base-desk/lib/jose.hoon+11−3 base-desk/lib/keygen.hoon+1−1 base-desk/lib/language-server/complete.hoon+4−3 base-desk/lib/language-server/easy-print.hoon+3−3 base-desk/lib/language-server/rune-snippet.hoon+2−2 base-desk/lib/naive.hoon+926 base-desk/lib/noun-diff.hoon+213 base-desk/lib/ph/io.hoon+238−4 base-desk/lib/ph/util.hoon+25−12 base-desk/lib/pill.hoon+500−18 base-desk/lib/ring.hoon+46−45 base-desk/lib/server.hoon+2−2 base-desk/lib/shoe.hoon+72−36 base-desk/lib/sink.hoon+26 base-desk/lib/sole.hoon+24 base-desk/lib/sss.hoon+445 base-desk/lib/story.hoon+88 base-desk/lib/strand.hoon+1−188 base-desk/lib/strandio.hoon+171−14 base-desk/lib/test.hoon+55−2 base-desk/lib/tiny.hoon+670 base-desk/lib/verb.hoon+88−11 base-desk/mar/belt.hoon+3−12 base-desk/mar/blit.hoon+3−44 base-desk/mar/css.hoon+25 base-desk/mar/hoon.hoon+285−31 base-desk/mar/htm.hoon+2−11 base-desk/mar/httr.hoon+1−2 base-desk/mar/js.hoon+7−4 base-desk/mar/json.hoon+4−4 base-desk/mar/json/rpc/response.hoon+1−2 base-desk/mar/kelvin.hoon+16−6 base-desk/mar/noun.hoon+3 base-desk/mar/sole/action.hoon+3−1 base-desk/mar/story-diff.hoon+16 base-desk/mar/story.hoon+65 base-desk/mar/tang.hoon+5−5 base-desk/mar/thread-done.hoon+11 base-desk/mar/thread-fail.hoon+11 base-desk/mar/txt.hoon−1 base-desk/mar/udon.hoon−5 base-desk/mar/umd.hoon−4 base-desk/mar/urb.hoon+1−1 base-desk/sources.txt+3 base-desk/sur/aquarium.hoon+18−5 base-desk/sur/bitcoin.hoon+1−1 base-desk/sur/hood.hoon+283−197 base-desk/sur/ring.hoon+1−1 base-desk/sur/sole.hoon+2−1 base-desk/sur/spider.hoon+14−1 base-desk/sur/sss.hoon+39 base-desk/sur/story.hoon+9 base-desk/sur/verb.hoon+37−1 bin/commit.sh+24 bin/get_glob.sh+19 bin/install.sh+82 bin/make_glob.py+208 config/ignore_files_on_install.txt+4 desk/app/astrolabe.hoon+345 desk/desk.bill+2−1 desk/desk.docket-0+6−5 desk/desk.seal+7 desk/doc.toc+15 desk/doc/overview.udon+26 desk/doc/usr/astrolabe.udon+107 desk/doc/usr/azimuth.udon+51 desk/doc/usr/comet.udon+10 desk/doc/usr/galaxy.udon+36 desk/doc/usr/keys.udon+63 desk/doc/usr/l2.udon+60 desk/doc/usr/moon.udon+8 desk/doc/usr/patpee.udon+104 desk/doc/usr/phonemes.udon+45 desk/doc/usr/planet.udon+9 desk/doc/usr/sigil-input.udon+27 desk/doc/usr/sigil.udon+22 desk/doc/usr/star.udon+21 desk/doc/usr/test.udon+24 desk/doc/usr/urbit-id.udon+118 desk/lib/astrolabe.hoon+234 desk/lib/search.hoon+373 desk/lib/state.hoon+21 desk/mar/astrolabe/point-set.hoon+13 desk/mar/astrolabe/point.hoon+14 desk/mar/ico.hoon+12 desk/mar/seal.hoon+12 desk/mar/webmanifest.hoon+12 desk/sur/astrolabe.hoon+58 desk/sys.kelvin+1−1 desk/tests/lib/state.hoon+59 docs-dev-desk/lib/docs.hoon+205 docs-dev-desk/lib/gemtext.hoon+190 docs-dev-desk/mar/clue.hoon+28 docs-dev-desk/mar/docu.hoon+23 docs-dev-desk/mar/elem.hoon+18 docs-dev-desk/mar/gmi-diff.hoon+13 docs-dev-desk/mar/gmi.hoon+37 docs-dev-desk/mar/toc.hoon+16 docs-dev-desk/mar/x-htm.hoon+12 docs-dev-desk/sur/docs.hoon+9 docs-dev-desk/sur/gemtext.hoon+10 flake.lock+27 flake.nix+73 install.sh−39 ui/.env+3 ui/index.html+7−3 ui/package-lock.json+401−55 ui/package.json+28−7 ui/public/android-chrome-192x192.png ui/public/android-chrome-512x512.png ui/public/apple-touch-icon.png ui/public/favicon-16x16.png ui/public/favicon-32x32.png ui/public/favicon.ico ui/public/sigil-example.png ui/public/site.webmanifest+1 ui/scraps/tirrel-graph-main.js+198 ui/src/App.svelte+63−13 ui/src/actions/docLinks.ts+26 ui/src/actions/symbolPartClick.ts+27 ui/src/actions/tooltip.ts+73 ui/src/assets/astrolabe-tile.svg+23 ui/src/assets/astrolabe.svg+206 ui/src/assets/up-arrow.svg+1 ui/src/assets/x.svg+1 ui/src/components/CollapsibleContent.svelte+18 ui/src/components/ExtensionWindow.svelte+25 ui/src/components/HomeScreenTiles.svelte+42 ui/src/components/SearchBar.svelte−54 ui/src/components/SearchResults.svelte−68 ui/src/components/ShipListing.svelte+37−11 ui/src/components/ShipListings.svelte+19 ui/src/components/ShipView.svelte−58 ui/src/components/Sigil.svelte−36 ui/src/components/StarChart.svelte+233 ui/src/components/ValidationProblems.svelte+4−2 ui/src/components/buttons/CloseButton.svelte+4 ui/src/components/buttons/Hamburger.svelte+5 ui/src/components/buttons/InlineSelect.svelte+16 ui/src/components/buttons/ToggleButton.svelte+11 ui/src/components/common/ChartLink.svelte+11 ui/src/components/common/EthAddressLink.svelte+37 ui/src/components/common/GoldBadge.svelte+10 ui/src/components/common/Legil.svelte+47 ui/src/components/common/LoadingSpinner.svelte+8 ui/src/components/common/ShipLink.svelte+10 ui/src/components/common/Sigil.svelte+65 ui/src/components/common/Tooltip.svelte+37 ui/src/components/common/TooltipAndDocLink.svelte+14 ui/src/components/docs/Doc.svelte+50 ui/src/components/docs/DocContent.svelte+37 ui/src/components/docs/DocHome.svelte+76 ui/src/components/docs/DocLink.svelte+26 ui/src/components/docs/HelpModal.svelte+25 ui/src/components/extensions/AppIcon.svelte+61 ui/src/components/extensions/AppsShipViewExtension.svelte+56 ui/src/components/extensions/GroupsShipViewExtension.svelte+15 ui/src/components/extensions/ProfileShipViewExtension.svelte+52 ui/src/components/pals/PalStatusIndicator.svelte+191 ui/src/components/pals/PalTagger.svelte+40 ui/src/components/pals/PalsShipViewExtension.svelte+34 ui/src/components/search/SearchBar.svelte+152 ui/src/components/SearchResultNavButtons.svelte → ui/src/components/search/SearchResultNavButtons.svelte ui/src/components/search/SearchResults.svelte+147 ui/src/components/search/SearchResultsNavButtons.svelte+54 ui/src/components/search/SearchSettings.svelte+25 ui/src/components/search/sigil/ClanSelect.svelte+44 ui/src/components/search/sigil/ClanSelectOption.svelte+17 ui/src/components/search/sigil/GeonSelector.svelte+30 ui/src/components/search/sigil/MiniFakeSigil.svelte+25 ui/src/components/search/sigil/SigilInput.svelte+104 ui/src/components/search/sigil/Symbol.svelte+79 ui/src/components/search/sigil/SymbolInput.svelte+105 ui/src/components/search/sigil/SymbolInputModeSelector.svelte+168 ui/src/components/search/sigil/SymbolInputModeSelectorButton.svelte+33 ui/src/components/search/sigil/SymbolInputNavButtons.svelte+30 ui/src/components/ship_view/AzimuthDetails.svelte+90 ui/src/components/ship_view/ShipChain.svelte+11 ui/src/components/ship_view/ShipChildrenTabs.svelte+136 ui/src/components/ship_view/ShipView.svelte+130 ui/src/fake_data/fake-contacts.ts+6−1 ui/src/lib/api.ts+102 ui/src/lib/apps.ts+177 ui/src/lib/colors.ts+17 ui/src/lib/graph.ts+422 ui/src/lib/id.ts+101−14 ui/src/lib/link.ts+15−2 ui/src/lib/pals.ts+91 ui/src/lib/ship.ts+114 ui/src/lib/sigil.ts+387 ui/src/lib/utils.ts+52−6 ui/src/main.ts+5−2 ui/src/routes/HelpPage.svelte+26 ui/src/routes/Home.svelte+191−7 ui/src/routes/ShipViewPage.svelte−25 ui/src/routes/SigilPage.svelte+61 ui/src/stores/docs.ts+44 ui/src/stores/keys.ts+68 ui/src/stores/pageName.ts+12 ui/src/stores/searchStores.ts+66 ui/src/stores/store.ts+66−24 ui/src/styles/docs.scss+49 ui/src/styles/styles.scss+13 ui/src/styles/tailwind-output.css+1553−79 ui/src/styles/tippy.css+5 ui/src/types/sigil.ts+148 ui/src/types/store.ts+66−4 ui/svelte.config.js+2−2 ui/tailwind.config.cjs+38−3 ui/tsconfig.json+9 ui/vite.config.ts+22−2
.gitignore+2
@@ -1,2 +1,4 @@
11# OS X
22.DS_Store
3+extras
4+globs
.vscode/settings.jsonnew file+58
@@ -0,0 +1,58 @@
1+{
2+ "saveEditorLayout.list": [
3+ {
4+ "name": "gall agent",
5+ "orientation": 0,
6+ "documents": [
7+ {
8+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/desk/sur/astrolabe.hoon",
9+ "column": 1
10+ },
11+ {
12+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/desk/app/astrolabe.hoon",
13+ "column": 1
14+ },
15+ {
16+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/extras/scraps/json-lib.hoon",
17+ "column": 2
18+ },
19+ {
20+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/base-desk/lib/naive.hoon",
21+ "column": 3
22+ },
23+ {
24+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/extras/scraps/scry thoughts.txt",
25+ "column": 3
26+ },
27+ {
28+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/desk/mar/astrolabe/point.hoon",
29+ "column": 4
30+ },
31+ {
32+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/install.sh",
33+ "column": 5
34+ },
35+ {
36+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/desk/lib/astrolabe.hoon",
37+ "column": 5
38+ }
39+ ]
40+ },
41+ {
42+ "name": "ui",
43+ "orientation": 0,
44+ "documents": [
45+ {
46+ "fsPath": "/Users/john/ws/urbit-apps/astrolabe/ui/src/components/ShipView.svelte",
47+ "column": 1
48+ }
49+ ]
50+ }
51+ ],
52+ "grammarly.selectors": [
53+ {
54+ "language": "markdown",
55+ "scheme": "file"
56+ }
57+ ]
58+}
.vscode/tasks.jsonnew file+13
@@ -0,0 +1,13 @@
1+{
2+ "version": "2.0.0",
3+ "tasks": [
4+ {
5+ "type": "npm",
6+ "script": "install:desk",
7+ "path": "ui",
8+ "problemMatcher": [],
9+ "label": "npm: install:desk - ui",
10+ "detail": "dotenv -c local npm run _install:desk"
11+ }
12+ ]
13+}
README.md+47−21
@@ -21,30 +21,56 @@ Astrolabe is built primarily using [Svelte], [Typescript], and [Tailwind CSS]. [
2121
2222To get started using Astrolabe first you need to run `npm install` inside the `ui` directory.
2323
24−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 `ui` 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, allowing you to see live data.
24+To develop you'll need a running ship to point to. If it's running somewhere other than `http://localhost:8081`, make a copy of `.env` in the `ui` directory and name it `.env.local`. This file will not be committed. Change `VITE_SHIP_URL` to the URL of the ship you would like to point to.
2525
26−Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
27−
28−### Deploying
29−
30−To deploy, run `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder. This can then be made into a glob by doing the following:
26+Once your URL is correctly configured, you can run `npm run dev`. This will proxy all requests to the ship except for those powering the interface, allowing you to see live data.
3127
32−1. Create or launch an urbit using the -F flag
33−2. On that urbit, if you don't already have a desk to run from, run `|merge %work our %base` to create a new desk and mount it with `|mount %work`.
34−3. Now the `%work` desk is accessible through the host OS's filesystem as a directory of that urbit's pier ie `~/zod/work`.
35−4. From the `ui` directory you can run `rsync -avL --delete dist/ ~/zod/work/astrolabe` where `~/zod` is your fake urbit's pier.
36−5. Once completed you can then run `|commit %work` on your urbit and you should see your files logged back out from the dojo.
37−6. Now run `=dir /=garden` to switch to the garden desk directory
38−7. You can now run `-make-glob %work /astrolabe` which will take the folder where you just added files and create a glob which can be thought of as a sort of bundle. It will be output to `~/zod/.urb/put`.
39−8. If you navigate to `~/zod/.urb/put` you should see a file that looks like this `glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob`. The characters between `glob-` and `.glob` are a hash of the glob's contents.
40−9. Now that we have the glob it can be uploaded to any publicly available HTTP endpoint that can serve files. This allows the glob to distributed over HTTP.
41−10. Once you've uploaded the glob, you should then update the corresponding entry in the docket file at `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
42−
43−```hoon
44− glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
45−```
28+Regardless of what you run to develop, Vite will hot-reload code changes as you work so you don't have to constantly refresh.
4629
47−11. This can now be safely committed and deployed.
30+# Deploying
31+
32+1. Spin up a comet or distribution ship.
33+2. Mount an `%astrolabe` desk. In Dojo:
34+ 1. `|merge %astrolabe our %base` to create a new desk.
35+ 2. `|mount %astrolabe` to mount it.
36+ 3. Set `ASTROLABE_DESK` in `.env.local` if the path to your desk is different.
37+3. Create and upload `glob-http`, if applicable (see below)
38+4. Bump the `version` in `desk.docket-0`.
39+3. Install the desk
40+ 1. From the `ui` folder: `npm run install:desk`
41+ 2. (first time only, on dev ship): `|install our %astrolabe`
42+ 2. From the distribution ship's dojo: `:treaty|unpublish %astrolabe`
43+ 2. From middleman or distribution ship's dojo: `|commit %astrolabe`
44+ 3. (if using a comet as a middleman) `|public %astrolabe`
45+ 4. (if using a comet as a middleman) from the actual distribution ship: `|install ~comet-name %astrolabe` or `|resume %astrolabe`
46+ 5. (if using a comet as a middleman) from the actual distribution ship: `|pause %astrolabe`
47+4. Build UI and upload `glob-ames`
48+5. Publish the desk from dojo: `:treaty|publish %astrolabe`
49+
50+## Deploying with `glob-ames` glob
51+Initial Setup:
52+1. Put the line `glob-ames+[~dister-midlev-mindyr 0v0]` in `desk.docket-0` if it's not there, where `~dister-midlevi-mindyr` is the `@p` of the distribution ship.
53+
54+Every Time:
55+4. Build the UI with `npm run build` in the `ui` directory which will bundle all the code and assets into the `dist/` folder.
56+5. Upload the `dist/` folder as a glob to your distribution ship. Example url: https://distribution.urbit-ship.com/docket/upload
57+
58+## Deploying with `glob-http` glob
59+Initial Setup:
60+1. Spin up a globber ship (comet or fakezod) (or just reuse your dev ship for this).
61+2. Mount an `%globber` desk. In Dojo:
62+ 1. `|merge %globber our %base` to create a new desk.
63+ 2. `|mount %globber` to mount it.
64+ 3. Set `GLOBBER_DESK` in `.env.local` if the path to your globber desk is different.
65+
66+Every Time:
67+3. From the `ui` directory, run `npm run release` to build the ui, make the glob, and copy it to the `globs` folder. The command will also print out the glob hash/id.
68+4. Copy the string printed by the last step, and paste it into `desk.docket-0`.
69+5. Upload the glob in `/globs/` to any publicly available HTTP endpoint that can serve files. This allows the glob to be distributed over HTTP.
70+6. Once you've uploaded the glob, paste the url into `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
71+ ```hoon
72+ glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
73+ ```
4874
4975[svelte]: https://svelte.dev/
5076[typescript]: https://www.typescriptlang.org/
base-desk/lib/agentio.hoon+4−6
@@ -66,7 +66,7 @@
6666 ::
6767 ++ rest
6868 |= p=@da
69− (arvo %b %wait p)
69+ (arvo %b %rest p)
7070 ::
7171 ++ warp
7272 |= [wer=ship =riff:clay]
@@ -85,16 +85,14 @@
8585 ?:(?=(%sing genre) [genre mood] [genre mood])
8686 (warp-our q.byk.bowl `rave)
8787 ::
88+ ++ tire
89+ (arvo %c %tire `~)
90+ ::
8891 ++ connect
8992 |= [=binding:eyre app=term]
9093 (arvo %e %connect binding app)
9194 --
9295::
93−++ fact-curry
94− |* [=mark =mold]
95− |= [paths=(list path) fac=mold]
96− (fact mark^!>(fac) paths)
97−::
9896++ fact-kick
9997 |= [=path =cage]
10098 ^- (list card)
base-desk/lib/azimuth.hoon+9−9
@@ -114,29 +114,29 @@
114114 ::
115115 :: Testnet contract addresses
116116 ::
117− ++ ropsten-contracts
117+ ++ goerli-contracts
118118 |%
119119 ++ azimuth
120− 0x308a.b6a6.024c.f198.b57e.008d.0ac9.ad02.1988.6579
120+ 0xbb61.fa68.3e4b.9104.18e2.7b00.a143.8a93.6234.df52
121121 ::
122122 ++ ecliptic
123− 0x8b9f.86a2.8921.d9c7.05b3.113a.755f.b979.e1bd.1bce
123+ 0xe129.0a32.9014.5e63.e6a8.ec1e.f661.6906.856d.0c8f
124124 ::
125125 ++ linear-star-release
126− 0x1f8e.dd03.1ee4.1474.0aed.b39b.84fb.8f2f.66ca.422f
126+ 0x0
127127 ::
128128 ++ conditional-star-release
129129 0x0
130130 ::
131131 ++ delegated-sending
132− 0x3e8c.a510.354b.c2fd.bbd6.1502.52d9.3105.c9c2.7bbe
132+ 0x0
133133 ::
134134 ++ naive
135− 0xe7cf.4b83.06d3.11ba.ca15.585f.e3f0.7cd0.441c.21d1
135+ 0x56e3.7137.cdaf.c026.a732.e8e8.40cd.621e.d50b.d210
136136 ::
137− ++ launch 4.601.630
138− ++ public launch
139− ++ chain-id 3
137+ ++ launch 7.834.742
138+ ++ public 7.834.800
139+ ++ chain-id 5
140140 --
141141 ::
142142 :: Local contract addresses
base-desk/lib/azimuthio.hoon+3−3
@@ -43,8 +43,8 @@
4343 url=url
4444 header-list=['Content-Type'^'application/json' ~]
4545 ^= body
46− %- some %- as-octt:mimes:html
47− %- en-json:html
46+ %- some %- as-octs:mimes:html
47+ %- en:json:html
4848 (request-to-json:rpc:ethereum id req)
4949 ==
5050 ;< ~ bind:m (send-request:strandio request)
@@ -62,7 +62,7 @@
6262 ?~ full-file.client-response
6363 (pure:m ~)
6464 =/ body=@t q.data.u.full-file.client-response
65− =/ jon=(unit json) (de-json:html body)
65+ =/ jon=(unit json) (de:json:html body)
6666 ?~ jon
6767 (pure:m ~)
6868 =, dejs-soft:format
base-desk/lib/bip/b173.hoon+1−1
@@ -11,7 +11,7 @@
1111|%
1212++ prefixes
1313 ^- (map network tape)
14− (my [[%main "bc"] [%testnet "tb"] ~])
14+ (my [[%main "bc"] [%testnet "tb"] [%regtest "bcrt"] ~])
1515++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
1616+$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
1717:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js
base-desk/lib/bip/b174.hoon+2
@@ -56,6 +56,8 @@
5656 1^0x0
5757 %testnet
5858 1^0x1
59+ %regtest
60+ 1^0x1
5961 ==
6062 :- (cat:byt ~[1^typ pubkey.h])
6163 %- cat:byt
base-desk/lib/bitcoin-utils.hoon+2−1
@@ -143,6 +143,7 @@
143143 |= a=@
144144 ^- hexb
145145 =/ l=@ (met 3 a)
146+ ?: =(l 0) 1^a
146147 ?: =(l 1) 1^a
147148 ?: =(l 2) (cat:byt ~[1^0xfd (flip:byt 2^a)])
148149 ?: (lte l 4) (cat:byt ~[1^0xfe (flip:byt 4^a)])
@@ -162,7 +163,7 @@
162163 %0xfe 2
163164 %0xff 3
164165 ==
165− :_ (drop:byt (add 1 len) h)
166+ :_ (drop:byt (add 1 (bex len)) h)
166167 %- flip:byt
167168 (take:byt (bex len) (drop:byt 1 h))
168169 :: +dea: atom instead of hexb for parsed CompactSize
base-desk/lib/dbug.hoon+9−3
@@ -4,7 +4,8 @@
44::
55|%
66+$ poke
7− $% [%bowl ~]
7+ $% [%skip depth=@ud =poke]
8+ [%bowl ~]
89 [%state grab=cord]
910 [%incoming =about]
1011 [%outgoing =about]
@@ -32,8 +33,13 @@
3233 ?. ?=(%dbug mark)
3334 =^ cards agent (on-poke:ag mark vase)
3435 [cards this]
35− =/ dbug
36− !<(poke vase)
36+ =/ dbug !<(poke vase)
37+ =? dbug ?=([%skip %0 *] dbug) poke.dbug
38+ ?: ?=(%skip -.dbug)
39+ =^ cards agent
40+ %+ on-poke:ag %dbug
41+ !>(`poke`dbug(depth (dec depth.dbug)))
42+ [cards this]
3743 =; =tang
3844 ((%*(. slog pri 1) tang) [~ this])
3945 ?- -.dbug
base-desk/lib/dill.hoonnew file+94
@@ -0,0 +1,94 @@
1+:: dill: utilities for dill's data structures
2+::
3+=, dill
4+|%
5+++ enjs
6+ |%
7+ ++ blit
8+ |= =blit:dill
9+ ^- json
10+ =, enjs:format
11+ %+ frond -.blit
12+ ?- -.blit
13+ %bel b+&
14+ %clr b+&
15+ %hop ?@ p.blit (numb p.blit)
16+ (pairs 'x'^(numb x.p.blit) 'y'^(numb y.p.blit) ~)
17+ %put a+(turn p.blit |=(c=@c s+(tuft c)))
18+ %nel b+&
19+ %url s+p.blit
20+ %wyp b+&
21+ %mor a+(turn p.blit ^blit)
22+ ::
23+ %sag
24+ %- pairs
25+ :~ 'path'^(path p.blit)
26+ 'file'^s+(en:base64:mimes:html (as-octs:mimes:html (jam q.blit)))
27+ ==
28+ ::
29+ %sav
30+ %- pairs
31+ :~ 'path'^(path p.blit)
32+ 'file'^s+(en:base64:mimes:html (as-octs:mimes:html q.blit))
33+ ==
34+ ::
35+ %klr
36+ :- %a
37+ %+ turn p.blit
38+ |= [=stye text=(list @c)]
39+ %- pairs
40+ :~ 'text'^a+(turn text |=(c=@c s+(tuft c)))
41+ ::
42+ :- 'stye'
43+ %- pairs
44+ |^ :~ 'back'^(color p.q.stye)
45+ 'fore'^(color q.q.stye)
46+ 'deco'^a+(turn ~(tap in p.stye) |=(d=deco ?~(d ~ s+d)))
47+ ==
48+ ++ color
49+ |= =tint
50+ ?@ tint ?~(tint ~ s+tint)
51+ =, tint
52+ (pairs r+(numb r) g+(numb g) b+(numb b) ~)
53+ --
54+ ==
55+ ==
56+ --
57+::
58+++ dejs
59+ |%
60+ ++ belt
61+ |= jon=json
62+ ^- belt:dill
63+ ?: ?=([%s *] jon)
64+ (taft p.jon)
65+ =, dejs:format
66+ %. jon
67+ %- of
68+ |^ :* mod+(ot 'mod'^mod 'key'^bot ~)
69+ txt+(ar (cu taft so))
70+ bol
71+ ==
72+ ::
73+ ++ bol
74+ :~ aro+(su (perk %d %l %r %u ~))
75+ bac+ul
76+ del+ul
77+ hit+(ot 'x'^ni 'y'^ni ~)
78+ ret+ul
79+ ==
80+ ::
81+ ++ bot
82+ |= j=json
83+ ^- bolt:dill
84+ ?+ j !!
85+ [%s *] (taft p.j)
86+ [%o *] ((of bol) j)
87+ ==
88+ ::
89+ ++ mod
90+ |= j=json
91+ ((su (perk %ctl %met %hyp ~)) j)
92+ --
93+ --
94+--
base-desk/lib/ethereum.hoon+28−4
@@ -652,7 +652,7 @@
652652 :^ url %post
653653 %- ~(gas in *math)
654654 ~['Content-Type'^['application/json']~]
655− (some (as-octt (en-json:html jon)))
655+ (some (as-octs (en:json:html jon)))
656656 :: +light-json-request: like json-request, but for %l
657657 ::
658658 :: TODO: Exorcising +purl from our system is a much longer term effort;
@@ -665,7 +665,7 @@
665665 :* %'POST'
666666 (crip (en-purl:html url))
667667 ~[['content-type' 'application/json']]
668− (some (as-octt (en-json:html jon)))
668+ (some (as-octs (en:json:html jon)))
669669 ==
670670 ::
671671 ++ batch-read-request
@@ -733,7 +733,7 @@
733733 ::
734734 %eth-get-block-by-number
735735 :- 'eth_getBlockByNumber'
736− :~ (tape (num-to-hex bon.req))
736+ :~ (tape (num-to-hex-minimal bon.req))
737737 b+txs.req
738738 ==
739739 ::
@@ -942,12 +942,33 @@
942942 %- render-hex-bytes
943943 (as-octs:mimes:html n)
944944::
945+++ num-to-hex-minimal
946+ |= n=@
947+ ^- tape
948+ %- prefix-hex
949+ ((x-co:co 1) n)
950+::
945951++ address-to-hex
946952 |= a=address
947953 ^- tape
948954 %- prefix-hex
949955 (render-hex-bytes 20 `@`a)
950956::
957+++ address-to-checksum
958+ |= a=address
959+ ^- tape
960+ =/ hexed (render-hex-bytes 20 `@`a)
961+ =/ hash (keccak-256:keccak:crypto (as-octs:mimes:html (crip hexed)))
962+ =| ret=tape
963+ =/ pos 63
964+ |-
965+ ?~ hexed (prefix-hex (flop ret))
966+ =/ char i.hexed
967+ ?: (lth char 58) $(pos (dec pos), ret [char ret], hexed t.hexed)
968+ =/ nib (cut 2 [pos 1] hash)
969+ ?: (lth 7 nib) $(pos (dec pos), ret [(sub char 32) ret], hexed t.hexed)
970+ $(pos (dec pos), ret [char ret], hexed t.hexed)
971+::
951972++ transaction-to-hex
952973 |= h=@
953974 ^- tape
@@ -978,5 +999,8 @@
978999::
9791000++ hex-to-num
9801001 |= a=@t
981− (rash (rsh [3 2] a) hex)
1002+ ~| %non-hex-cord
1003+ ?> =((end [3 2] a) '0x')
1004+ =< ?<(=(0 p) q) %- need
1005+ (de:base16:mimes:html (rsh [3 2] a))
9821006--
base-desk/lib/ethio.hoon+3−3
@@ -76,8 +76,8 @@
7676 header-list=['Content-Type'^'application/json' ~]
7777 ::
7878 ^= body
79− %- some %- as-octt:mimes:html
80− %- en-json:html
79+ %- some %- as-octs:mimes:html
80+ %- en:json:html
8181 a+(turn reqs request-to-json:rpc:ethereum)
8282 ==
8383 ;< ~ bind:m
@@ -96,7 +96,7 @@
9696 ?~ full-file.client-response
9797 (pure:m ~)
9898 =/ body=@t q.data.u.full-file.client-response
99− =/ jon=(unit json) (de-json:html body)
99+ =/ jon=(unit json) (de:json:html body)
100100 ?~ jon
101101 (pure:m ~)
102102 =/ array=(unit (list response:rpc))
base-desk/lib/hark-store.hoon+22
@@ -171,6 +171,7 @@
171171++ dejs
172172 =, dejs:format
173173 |%
174+ ++ ship (su ;~(pfix sig fed:ag))
174175 :: TODO: fix +stab
175176 ::
176177 ++ pa
@@ -218,6 +219,26 @@
218219 :~ lid+lid
219220 bin+bin
220221 ==
222+ ++ content
223+ %- of
224+ :~ text+so
225+ ship+ship
226+ ==
227+ ::
228+ ++ body
229+ %- ot
230+ :~ title+(ar content)
231+ content+(ar content)
232+ time+di
233+ binned+pa
234+ link+pa
235+ ==
236+ ::
237+ ++ add-note
238+ %- ot
239+ :~ bin+bin
240+ body+body
241+ ==
221242 ::
222243 ++ action
223244 ^- $-(json ^action)
@@ -228,6 +249,7 @@
228249 read-count+place
229250 read-each+read-each
230251 read-note+bin
252+ add-note+add-note
231253 ==
232254 --
233255--
base-desk/lib/jose.hoon+11−3
@@ -33,7 +33,6 @@
3333 |^ |=([sor=$-(^ ?) val=json] (apex val sor ""))
3434 :: :: ++apex:en-json:html
3535 ++ apex
36− =, en-json:html
3736 |= [val=json sor=$-(^ ?) rez=tape]
3837 ^- tape
3938 ?~ val (weld "null" rez)
@@ -46,7 +45,7 @@
4645 |-
4746 ?~ t.p.val ^$(val i.p.val)
4847 ^$(val i.p.val, rez [',' $(p.val t.p.val)])
49− ::
48+ ::
5049 %b (weld ?:(p.val "true" "false") rez)
5150 %n (weld (trip p.val) rez)
5251 %s
@@ -60,7 +59,7 @@
6059 ?: ?=([@ ~] hed)
6160 [i.hed $(viz t.viz)]
6261 (weld hed $(viz t.viz))
63− ::
62+ ::
6463 %o
6564 :- '{'
6665 =. rez ['}' rez]
@@ -74,6 +73,15 @@
7473 =. rez [',' $(viz t.viz)]
7574 ^$(val [%s p.i.viz], rez [':' ^$(val q.i.viz)])
7675 ==
76+ ::
77+ ++ jesc
78+ =+ utf=|=(a=@ ['\\' 'u' ((x-co 4):co a)])
79+ |= a=@ ^- tape
80+ ?+ a ?:(&((gth a 0x1f) !=(a 0x7f)) [a ~] (utf a))
81+ %10 "\\n"
82+ %34 "\\\""
83+ %92 "\\\\"
84+ ==
7785 --
7886:: %/lib/jose
7987::
base-desk/lib/keygen.hoon+1−1
@@ -94,7 +94,7 @@
9494 |= seed=@
9595 ^- edkeys
9696 =+ =< [pub=pub:ex sec=sec:ex]
97− (pit:nu:crub:crypto 256 seed)
97+ (pit:nu:cric:crypto 256 seed %b ~)
9898 :- ^= auth
9999 :- (rsh 3 (end [3 33] pub))
100100 (rsh 3 (end [3 33] sec))
base-desk/lib/language-server/complete.hoon+4−3
@@ -3,7 +3,8 @@
33::
44=/ debug |
55|%
6−+* option [item]
6+++ option
7+ |$ [item]
78 [term=cord detail=item]
89::
910:: Like +rose except also produces line number
@@ -42,7 +43,7 @@
4243 %+ turn ~(tap by q.r.q.ty)
4344 |= [term =tome]
4445 %+ turn
45− ~(tap by q.tome)
46+ ~(tap by tome)
4647 |= [name=term =hoon]
4748 ^- (pair term type)
4849 ~| term=term
@@ -226,7 +227,7 @@
226227 =* outer-loop $
227228 ?~ tomes
228229 ~
229− =/ arms ~(tap by q.q.i.tomes)
230+ =/ arms ~(tap by q.i.tomes)
230231 |- ^- (unit [term type])
231232 =* inner-loop $
232233 ?~ arms
base-desk/lib/language-server/easy-print.hoon+3−3
@@ -421,11 +421,11 @@
421421 ?:(?=([%plot *] q.yon) [q.hin p.q.yon] [q.hin q.yon ~])
422422 ::
423423 [%core *]
424− ?: ?=([[%$ * [[%$ @ *] ~ ~]] ~ ~] q.r.q.sut)
424+ ?: ?=([[%$ [[%$ @ *] ~ ~]] ~ ~] q.r.q.sut)
425425 =/ dad $(sut p.sut)
426426 :- p.dad
427427 ~! q.r.q.sut
428− [%gate q.n.q.q.n.q.r.q.sut sut(r.p.q %gold) q.dad]
428+ [%gate q.n.q.n.q.r.q.sut sut(r.p.q %gold) q.dad]
429429 =+ yad=$(sut p.sut)
430430 :- p.yad
431431 =+ ^= doy ^- [p=(list @ta) q=wine]
@@ -438,7 +438,7 @@
438438 %^ cat 3
439439 %~ rent co
440440 :+ %$ %ud
441− %- ~(rep by (~(run by q.r.q.sut) |=(tome ~(wyt by q.+<))))
441+ %- ~(rep by (~(run by q.r.q.sut) |=(tome ~(wyt by +<))))
442442 |=([[@ a=@u] b=@u] (add a b))
443443 %^ cat 3
444444 ?-(r.p.q.sut %gold '.', %iron '|', %lead '?', %zinc '&')
base-desk/lib/language-server/rune-snippet.hoon+2−2
@@ -509,8 +509,8 @@
509509 :- '!@'
510510 """
511511 $\{1:wing}
512− $\{2:if-exists}
513− $\{3:if-not-exists}
512+ $\{2:if-not-exists}
513+ $\{3:if-exists}
514514 """
515515 :- '!?'
516516 """
base-desk/lib/naive.hoonnew file+926
@@ -0,0 +1,926 @@
1+/+ tiny
2+!.
3+=> => tiny
4+:: Laconic bit
5+::
6+=| lac=?
7+:: Constants
8+::
9+|%
10+:: Transfers on L1 to this address count as depositing to L2
11+::
12+++ deposit-address 0x1111.1111.1111.1111.1111.1111.1111.1111.1111.1111
13+++ log-names
14+ |%
15+ :: Generated with (keccak-256:keccak:crypto (as-octs:mimes:html name))
16+ ::
17+ :: OwnerChanged(uint32,address)
18+ ++ owner-changed
19+ 0x16d0.f539.d49c.6cad.822b.767a.9445.bfb1.
20+ cf7e.a6f2.a6c2.b120.a7ea.4cc7.660d.8fda
21+ ::
22+ :: Activated(uint32)
23+ ++ activated
24+ 0xe74c.0380.9d07.69e1.b1f7.06cc.8414.258c.
25+ d1f3.b6fe.020c.d15d.0165.c210.ba50.3a0f
26+ ::
27+ :: Spawned(uint32,uint32)
28+ ++ spawned
29+ 0xb2d3.a6e7.a339.f5c8.ff96.265e.2f03.a010.
30+ a854.1070.f374.4a24.7090.9644.1508.1546
31+ ::
32+ :: OwnershipTransferred(address,address)
33+ ++ ownership-transferred
34+ 0x8be0.079c.5316.5914.1344.cd1f.d0a4.f284.
35+ 1949.7f97.22a3.daaf.e3b4.186f.6b64.57e0
36+ ::
37+ :: EscapeRequested(uint32,uint32)
38+ ++ escape-requested
39+ 0xb4d4.850b.8f21.8218.141c.5665.cba3.79e5.
40+ 3e9b.b015.b51e.8d93.4be7.0210.aead.874a
41+ ::
42+ :: EscapeCanceled(uint32,uint32)
43+ ++ escape-canceled
44+ 0xd653.bb0e.0bb7.ce83.93e6.24d9.8fbf.17cd.
45+ a590.2c83.28ed.0cd0.9988.f368.90d9.932a
46+ ::
47+ :: EscapeAccepted(uint32,uint32)
48+ ++ escape-accepted
49+ 0x7e44.7c9b.1bda.4b17.4b07.96e1.00bf.7f34.
50+ ebf3.6dbb.7fe6.6549.0b1b.fce6.246a.9da5
51+ ::
52+ :: LostSponsor(uint32,uint32)
53+ ++ lost-sponsor
54+ 0xd770.4f9a.2519.3dbd.0b0c.b4a8.09fe.ffff.
55+ a7f1.9d1a.ae88.17a7.1346.c194.4482.10d5
56+ ::
57+ :: ChangedKeys(uint32,bytes32,bytes32,uint32,uint32)
58+ ++ changed-keys
59+ 0xaa10.e7a0.117d.4323.f1d9.9d63.0ec1.69be.
60+ bb3a.988e.8957.70e3.5198.7e01.ff54.23d5
61+ ::
62+ :: BrokeContinuity(uint32,uint32)
63+ ++ broke-continuity
64+ 0x2929.4799.f1c2.1a37.ef83.8e15.f79d.d91b.
65+ cee2.df99.d63c.d1c1.8ac9.68b1.2951.4e6e
66+ ::
67+ :: ChangedSpawnProxy(uint32,address)
68+ ++ changed-spawn-proxy
69+ 0x9027.36af.7b3c.efe1.0d9e.840a.ed0d.687e.
70+ 35c8.4095.122b.2505.1a20.ead8.866f.006d
71+ ::
72+ :: ChangedTransferProxy(uint32,address)
73+ ++ changed-transfer-proxy
74+ 0xcfe3.69b7.197e.7f0c.f067.93ae.2472.a9b1.
75+ 3583.fecb.ed2f.78df.a14d.1f10.796b.847c
76+ ::
77+ :: ChangedManagementProxy(uint32,address)
78+ ++ changed-management-proxy
79+ 0xab9c.9327.cffd.2acc.168f.afed.be06.139f.
80+ 5f55.cb84.c761.df05.e051.1c25.1e2e.e9bf
81+ ::
82+ :: ChangedVotingProxy(uint32,address)
83+ ++ changed-voting-proxy
84+ 0xcbd6.269e.c714.57f2.c7b1.a227.74f2.46f6.
85+ c5a2.eae3.795e.d730.0db5.1768.0c61.c805
86+ ::
87+ :: ChangedDns(string,string,string)
88+ ++ changed-dns
89+ 0xfafd.04ad.e1da.ae2e.1fdb.0fc1.cc6a.899f.
90+ d424.063e.d5c9.2120.e67e.0730.53b9.4898
91+ ::
92+ :: ApprovalForAll(address,address,bool)
93+ ++ approval-for-all
94+ 0x1730.7eab.39ab.6107.e889.9845.ad3d.59bd.
95+ 9653.f200.f220.9204.89ca.2b59.3769.6c31
96+ --
97+-- =>
98+:: Types
99+|%
100+:: ethereum address, 20 bytes.
101+::
102++$ address @ux
103++$ nonce @ud
104++$ dominion ?(%l1 %l2 %spawn)
105++$ keys [=life suite=@ud auth=@ crypt=@]
106+++ orm ((on ship point) por)
107+++ point
108+ $: :: domain
109+ ::
110+ =dominion
111+ ::
112+ :: ownership
113+ ::
114+ $= own
115+ $: owner=[=address =nonce]
116+ spawn-proxy=[=address =nonce]
117+ management-proxy=[=address =nonce]
118+ voting-proxy=[=address =nonce]
119+ transfer-proxy=[=address =nonce]
120+ ==
121+ ::
122+ :: networking
123+ ::
124+ $= net
125+ $: rift=@ud
126+ =keys
127+ sponsor=[has=? who=@p]
128+ escape=(unit @p)
129+ ==
130+ ==
131+::
132+++ diff
133+ $% [%nonce =ship =proxy =nonce]
134+ [%tx =raw-tx err=(unit @tas)]
135+ [%operator owner=address operator=address approved=?]
136+ [%dns domains=(list @t)]
137+ $: %point =ship
138+ $% [%rift =rift]
139+ [%keys =keys]
140+ [%sponsor sponsor=(unit @p)]
141+ [%escape to=(unit @p)]
142+ [%owner =address]
143+ [%spawn-proxy =address]
144+ [%management-proxy =address]
145+ [%voting-proxy =address]
146+ [%transfer-proxy =address]
147+ [%dominion =dominion]
148+ == == ==
149+::
150++$ state
151+ $: %0
152+ =points
153+ =operators
154+ dns=(list @t)
155+ ==
156++$ points (tree [ship point])
157++$ operators (jug address address)
158++$ effects (list diff)
159++$ proxy ?(%own %spawn %manage %vote %transfer)
160++$ roll (list raw-tx)
161++$ raw-tx [sig=@ raw=octs =tx]
162++$ tx [from=[=ship =proxy] skim-tx]
163++$ skim-tx
164+ $% [%transfer-point =address reset=?]
165+ [%spawn =ship =address]
166+ [%configure-keys encrypt=@ auth=@ crypto-suite=@ breach=?]
167+ [%escape parent=ship]
168+ [%cancel-escape parent=ship]
169+ [%adopt =ship]
170+ [%reject =ship]
171+ [%detach =ship]
172+ [%set-management-proxy =address]
173+ [%set-spawn-proxy =address]
174+ [%set-transfer-proxy =address]
175+ ==
176+::
177++$ event-log
178+ $: address=@ux
179+ data=@ux
180+ topics=(lest @ux)
181+ ==
182++$ input
183+ $: block=@ud
184+ $% [%bat batch=@]
185+ [%log =event-log]
186+ == ==
187+:: ECDSA verifier.
188+::
189+:: Must keccak `dat` and recover the ethereum address which signed.
190+:: Must not crash. `v` will normally be between 0 and 3; if it is not,
191+:: should produce null.
192+::
193++$ verifier $-([dat=octs v=@ r=@ s=@] (unit address))
194+-- =>
195+::
196+|%
197+++ debug
198+ |* [meg=@t *]
199+ ?: lac
200+ +<+
201+ ~> %slog.[0 meg]
202+ +<+
203+::
204+++ parse-roll
205+ |= batch=@
206+ =| =roll
207+ =| pos=@ud
208+ =/ las (met 0 batch)
209+ |- ^+ roll
210+ ?: (gte pos las)
211+ (flop roll)
212+ =/ parse-result (parse-raw-tx pos batch)
213+ :: Parsing failed, abort batch
214+ ::
215+ ?~ parse-result
216+ (debug %parse-failed ~)
217+ =^ =raw-tx pos u.parse-result
218+ $(roll [raw-tx roll])
219+::
220+++ parse-raw-tx
221+ |= [pos=@ud batch=@]
222+ ^- (unit [raw-tx pos=@ud])
223+ |^
224+ =^ sig pos (take 3 65)
225+ =/ res=(unit [=tx pos=@ud]) parse-tx
226+ ?~ res ~
227+ =/ dif (sub pos.u.res pos)
228+ =/ len =>((dvr dif 8) ?>(=(0 q) p))
229+ :- ~ :_ pos.u.res
230+ [sig [len (cut 0 [pos dif] batch)] tx.u.res]
231+ ::
232+ ++ parse-tx
233+ ^- (unit [tx pos=@ud])
234+ =^ from-proxy=@ pos (take 0 3)
235+ ?. ?=(?(%0 %1 %2 %3 %4) from-proxy) (debug %bad-proxy ~)
236+ =/ =proxy
237+ ?- from-proxy
238+ %0 %own
239+ %1 %spawn
240+ %2 %manage
241+ %3 %vote
242+ %4 %transfer
243+ ==
244+ =^ pad pos (take 0 5)
245+ =^ from-ship=ship pos (take 3 4)
246+ =- ?~ res
247+ ~
248+ `[[[from-ship proxy] skim-tx.u.res] pos.u.res]
249+ ^- res=(unit [=skim-tx pos=@ud])
250+ =^ op pos (take 0 7)
251+ ?+ op (debug %strange-opcode ~)
252+ %0
253+ =^ reset=@ pos (take 0)
254+ =^ =address pos (take 3 20)
255+ `[[%transfer-point address =(0 reset)] pos]
256+ ::
257+ %1
258+ =^ pad=@ pos (take 0)
259+ =^ =ship pos (take 3 4)
260+ =^ =address pos (take 3 20)
261+ `[[%spawn ship address] pos]
262+ ::
263+ %2
264+ =^ breach=@ pos (take 0)
265+ =^ encrypt=@ pos (take 3 32)
266+ =^ auth=@ pos (take 3 32)
267+ =^ crypto-suite=@ pos (take 3 4)
268+ `[[%configure-keys encrypt auth crypto-suite =(0 breach)] pos]
269+ ::
270+ %3 =^(res pos take-ship `[[%escape res] pos])
271+ %4 =^(res pos take-ship `[[%cancel-escape res] pos])
272+ %5 =^(res pos take-ship `[[%adopt res] pos])
273+ %6 =^(res pos take-ship `[[%reject res] pos])
274+ %7 =^(res pos take-ship `[[%detach res] pos])
275+ %8 =^(res pos take-address `[[%set-management-proxy res] pos])
276+ %9 =^(res pos take-address `[[%set-spawn-proxy res] pos])
277+ %10 =^(res pos take-address `[[%set-transfer-proxy res] pos])
278+ ==
279+ ::
280+ :: Take a bite
281+ ::
282+ ++ take
283+ |= =bite
284+ ^- [@ @ud]
285+ =/ =step
286+ ?@ bite (bex bite)
287+ (mul step.bite (bex bloq.bite))
288+ [(cut 0 [pos step] batch) (add pos step)]
289+ :: Encode ship and address
290+ ::
291+ ++ take-address
292+ ^- [address @ud]
293+ =^ pad=@ pos (take 0)
294+ =^ =address pos (take 3 20)
295+ [address pos]
296+ :: Encode escape-related txs
297+ ::
298+ ++ take-ship
299+ ^- [ship @ud]
300+ =^ pad=@ pos (take 0)
301+ =^ other=ship pos (take 3 4)
302+ [other pos]
303+ --
304+::
305+++ proxy-from-point
306+ |= [=proxy point]
307+ ^- [=address =nonce]
308+ ?- proxy
309+ %own owner.own
310+ %spawn spawn-proxy.own
311+ %manage management-proxy.own
312+ %vote voting-proxy.own
313+ %transfer transfer-proxy.own
314+ ==
315+::
316+++ verify-sig-and-nonce
317+ |= [=verifier chain-t=@t =state =raw-tx]
318+ ^- ?
319+ |^
320+ =/ point (get-point state ship.from.tx.raw-tx)
321+ ?> ?=(^ point) :: we never parse more than four bytes for a ship
322+ =/ need=[=address =nonce]
323+ (proxy-from-point proxy.from.tx.raw-tx u.point)
324+ :: We include a domain separator to avoid letting signatures be
325+ :: accidentally reused with other applications. We include the name
326+ :: UrbitID, a signature format version number, and the EIP-155 chain
327+ :: ID.
328+ ::
329+ :: We also include a nonce so that a transaction cannot be
330+ :: rebroadcast.
331+ ::
332+ =/ prepared-data=octs
333+ %: cad 3
334+ 14^'UrbitIDV1Chain'
335+ (met 3 chain-t)^chain-t
336+ 1^':'
337+ 4^nonce.need
338+ raw.raw-tx
339+ ~
340+ ==
341+ :: Wallets which support personal_sign include this preamble to avoid
342+ :: letting personal_sign be used to sign ethereum transactions
343+ ::
344+ =/ signed-data=octs
345+ =/ len (ud-to-ascii p.prepared-data)
346+ %: cad 3
347+ 26^'\19Ethereum Signed Message:\0a'
348+ (met 3 len)^len
349+ prepared-data
350+ ~
351+ ==
352+ =/ dress (verify-sig sig.raw-tx signed-data)
353+ ?~ dress
354+ |
355+ =(address.need u.dress)
356+ :: Verify signature and produce signer address
357+ ::
358+ ++ verify-sig
359+ |= [sig=@ txdata=octs]
360+ ^- (unit address)
361+ |^
362+ :: Reversed of the usual r-s-v order because Ethereum integers are
363+ :: big-endian
364+ ::
365+ =^ v sig (take 3)
366+ =^ s sig (take 3 32)
367+ =^ r sig (take 3 32)
368+ :: In Ethereum, v is generally 27 + recid, and verifier expects a
369+ :: recid. Old versions of geth used 0 + recid, so most software
370+ :: now supports either format. See:
371+ ::
372+ :: https://github.com/ethereum/go-ethereum/issues/2053
373+ ::
374+ =? v (gte v 27) (sub v 27)
375+ (verifier txdata v r s)
376+ ::
377+ ++ take
378+ |= =bite
379+ [(end bite sig) (rsh bite sig)]
380+ --
381+ --
382+:: ASCII-decimal encode
383+::
384+++ ud-to-ascii
385+ |= n=@ud
386+ ?~ n '0'
387+ =| l=(list @)
388+ |- ^- @t
389+ ?~ n (rep 3 l)
390+ =+ (dvr n 10)
391+ $(n p, l [(add '0' q) l])
392+::
393+++ ship-rank
394+ |= =ship
395+ ^- ?(%0 %1 %2 %3 %4)
396+ ?: (lth ship 0x100) %0
397+ ?: (lth ship 0x1.0000) %1
398+ ?: (lth ship 0x1.0000.0000) %2
399+ ?: (lth ship 0x1.0000.0000.0000.0000) %3
400+ %4
401+::
402+++ sein :: autoboss
403+ |= who=ship
404+ ^- ship
405+ =/ mir (ship-rank who)
406+ ?- mir
407+ %0 who
408+ %1 (end 3 who)
409+ %2 (end 4 who)
410+ %3 (end 5 who)
411+ %4 (end 4 who)
412+ ==
413+::
414+:: Produces null only if ship is not a galaxy, star, or planet
415+::
416+++ get-point
417+ |= [=state =ship]
418+ ^- (unit point)
419+ =/ existing (get:orm points.state ship)
420+ ?^ existing
421+ `u.existing
422+ =| =point
423+ =. who.sponsor.net.point (sein ship)
424+ ?+ (ship-rank ship) (debug %strange-point ~)
425+ %0 `point(dominion %l1)
426+ ?(%1 %2)
427+ =/ existing-parent $(ship (sein ship))
428+ ?~ existing-parent ~
429+ :- ~
430+ %= point
431+ dominion
432+ ?- dominion.u.existing-parent
433+ %l1 %l1
434+ %l2 %l2
435+ %spawn %l2
436+ ==
437+ ==
438+ ==
439+-- =>
440+|%
441+:: Receive log from L1 transaction
442+::
443+++ receive-log
444+ |= [=state log=event-log]
445+ ^- [effects ^state]
446+ =* log-name i.topics.log
447+ ?: =(log-name activated:log-names) `state
448+ ?: =(log-name spawned:log-names) `state
449+ ?: =(log-name ownership-transferred:log-names) `state
450+ ?: =(log-name changed-dns:log-names)
451+ ?> ?=(~ t.topics.log)
452+ =/ words (rip 8 data.log)
453+ :: This is only true if each domain is <= 32 bytes
454+ ::
455+ ?. ?=([c=@ @ b=@ @ a=@ @ @ @ @ ~] words) `state
456+ =* one &5.words
457+ =* two &3.words
458+ =* tri &1.words
459+ =/ domains ~[(swp 3 one) (swp 3 two) (swp 3 tri)]
460+ :- [%dns domains]~
461+ state(dns domains)
462+ ::
463+ ?: =(log-name approval-for-all:log-names)
464+ ?> ?=([@ @ ~] t.topics.log)
465+ =* owner i.t.topics.log
466+ =* operator i.t.t.topics.log
467+ =/ approved !=(0 data.log)
468+ :- [%operator owner operator approved]~
469+ =- state(operators -)
470+ ?: approved
471+ (~(put ju operators.state) owner operator)
472+ (~(del ju operators.state) owner operator)
473+ ::
474+ :: The rest of the logs modify a particular ship, specified in the
475+ :: second topic. We fetch it, and insert the modification back into
476+ :: our state.
477+ ::
478+ ?> ?=([@ *] t.topics.log)
479+ =* ship=@ i.t.topics.log
480+ =/ the-point (get-point state ship)
481+ ?> ?=(^ the-point)
482+ =* point u.the-point
483+ ::
484+ :: Important to fully no-op on failure so we don't insert an entry
485+ :: into points.state
486+ ::
487+ =- ?~ res
488+ `state
489+ [effects.u.res state(points (put:orm points.state ship new-point.u.res))]
490+ ^- res=(unit [=effects new-point=^point])
491+ ::
492+ ?: =(log-name changed-spawn-proxy:log-names)
493+ ?. ?=(%l1 -.point) ~
494+ ?> ?=([@ ~] t.t.topics.log)
495+ =* to i.t.t.topics.log
496+ :: Depositing to L2 is represented by a spawn proxy change on L1,
497+ :: but it doesn't change the actual spawn proxy.
498+ ::
499+ ?: =(deposit-address to)
500+ :+ ~ [%point ship %dominion %spawn]~
501+ point(dominion %spawn)
502+ :+ ~ [%point ship %spawn-proxy to]~
503+ point(address.spawn-proxy.own to)
504+ ::
505+ ?: =(log-name escape-accepted:log-names)
506+ ?> ?=([@ ~] t.t.topics.log)
507+ =* parent=@ i.t.t.topics.log
508+ =/ parent-point (get-point state parent)
509+ ?> ?=(^ parent-point)
510+ ?: ?=(%l2 -.u.parent-point) ~
511+ :+ ~ [%point ship %sponsor `parent]~
512+ point(escape.net ~, sponsor.net [%& parent])
513+ ::
514+ ?: =(log-name lost-sponsor:log-names)
515+ ?> ?=([@ ~] t.t.topics.log)
516+ =* parent=@ i.t.t.topics.log
517+ :: If the sponsor we lost was not our actual sponsor, we didn't
518+ :: actually lose anything.
519+ ::
520+ ?. =(parent who.sponsor.net.point) ~
521+ ::
522+ =/ parent-point (get-point state parent)
523+ ?> ?=(^ parent-point)
524+ ::
525+ :: We can detach even if the child is on L2, as long as the parent
526+ :: is on L1.
527+ ::
528+ ?: ?=(%l2 -.u.parent-point) ~
529+ :+ ~ [%point ship %sponsor ~]~
530+ point(has.sponsor.net %|)
531+ ::
532+ :: The rest can be done by any ship on L1, even if their spawn proxy
533+ :: is set to L2
534+ ::
535+ ?: ?=(%l2 -.point) ~
536+ ::
537+ ?: =(log-name escape-requested:log-names)
538+ ?> ?=([@ ~] t.t.topics.log)
539+ =* parent=@ i.t.t.topics.log
540+ =/ parent-point (get-point state parent)
541+ ?> ?=(^ parent-point)
542+ :+ ~ [%point ship %escape `parent]~
543+ point(escape.net `parent)
544+ ::
545+ ?: =(log-name escape-canceled:log-names)
546+ ?> ?=([@ ~] t.t.topics.log)
547+ =* parent=@ i.t.t.topics.log
548+ =/ parent-point (get-point state parent)
549+ ?> ?=(^ parent-point)
550+ :+ ~ [%point ship %escape ~]~
551+ point(escape.net ~)
552+ ::
553+ ?: =(log-name broke-continuity:log-names)
554+ ?> ?=(~ t.t.topics.log)
555+ =* rift=@ data.log
556+ :+ ~ [%point ship %rift rift]~
557+ point(rift.net rift)
558+ ::
559+ ?: =(log-name changed-keys:log-names)
560+ ?> ?=(~ t.t.topics.log)
561+ =/ =keys
562+ :* life=(cut 8 [0 1] data.log)
563+ suite=(cut 8 [1 1] data.log)
564+ auth=(cut 8 [2 1] data.log)
565+ crypt=(cut 8 [3 1] data.log)
566+ ==
567+ :+ ~ [%point ship %keys keys]~
568+ point(keys.net keys)
569+ ::
570+ ?: =(log-name owner-changed:log-names)
571+ ?> ?=([@ ~] t.t.topics.log)
572+ =* to i.t.t.topics.log
573+ :: Depositing to L2 is represented by an ownership change on L1,
574+ :: but it doesn't change who actually owns the ship.
575+ ::
576+ ?: =(deposit-address to)
577+ :+ ~ [%point ship %dominion %l2]~
578+ point(dominion %l2)
579+ :+ ~ [%point ship %owner to]~
580+ point(address.owner.own to)
581+ ::
582+ ?: =(log-name changed-transfer-proxy:log-names)
583+ ?> ?=([@ ~] t.t.topics.log)
584+ =* to i.t.t.topics.log
585+ :+ ~ [%point ship %transfer-proxy to]~
586+ point(address.transfer-proxy.own to)
587+ ::
588+ ?: =(log-name changed-management-proxy:log-names)
589+ ?> ?=([@ ~] t.t.topics.log)
590+ =* to i.t.t.topics.log
591+ :+ ~ [%point ship %management-proxy to]~
592+ point(address.management-proxy.own to)
593+ ::
594+ ?: =(log-name changed-voting-proxy:log-names)
595+ ?> ?=([@ ~] t.t.topics.log)
596+ =* to i.t.t.topics.log
597+ :+ ~ [%point ship %voting-proxy to]~
598+ point(address.voting-proxy.own to)
599+ ::
600+ (debug %unknown-log ~)
601+::
602+:: Receive batch of L2 transactions
603+::
604+++ receive-batch
605+ |= [=verifier chain-id=@ud =state batch=@]
606+ =/ chain-t (ud-to-ascii chain-id)
607+ =/ =roll (parse-roll batch)
608+ |- ^- [effects ^state]
609+ ?~ roll
610+ [~ state]
611+ :: Verify signature, else skip tx
612+ ::
613+ ?. (verify-sig-and-nonce verifier chain-t state i.roll)
614+ %+ debug %l2-sig-failed
615+ =^ effects state $(roll t.roll)
616+ :_ state
617+ [[%tx i.roll `%sig-or-nonce-failed] effects]
618+ :: Increment nonce, even if it later fails
619+ ::
620+ =^ effects-1 points.state (increment-nonce state from.tx.i.roll)
621+ :: Process tx
622+ ::
623+ =^ effects-2 state
624+ =/ tx-result=(unit [=effects =^state]) (receive-tx state tx.i.roll)
625+ ?~ tx-result
626+ %+ debug %l2-tx-failed
627+ [[%tx i.roll `%tx-failed]~ state]
628+ [[[%tx i.roll ~] effects.u.tx-result] state.u.tx-result]
629+ =^ effects-3 state $(roll t.roll)
630+ [:(welp effects-1 effects-2 effects-3) state]
631+::
632+++ increment-nonce
633+ |= [=state =ship =proxy]
634+ =/ point (get-point state ship)
635+ ?> ?=(^ point) :: we only parsed 4 bytes
636+ =* own own.u.point
637+ =^ nonce u.point
638+ ?- proxy
639+ %own
640+ :- nonce.owner.own
641+ u.point(nonce.owner.own +(nonce.owner.own))
642+ ::
643+ %spawn
644+ :- nonce.spawn-proxy.own
645+ u.point(nonce.spawn-proxy.own +(nonce.spawn-proxy.own))
646+ ::
647+ %manage
648+ :- nonce.management-proxy.own
649+ u.point(nonce.management-proxy.own +(nonce.management-proxy.own))
650+ ::
651+ %vote
652+ :- nonce.voting-proxy.own
653+ u.point(nonce.voting-proxy.own +(nonce.voting-proxy.own))
654+ ::
655+ %transfer
656+ :- nonce.transfer-proxy.own
657+ u.point(nonce.transfer-proxy.own +(nonce.transfer-proxy.own))
658+ ==
659+ ::
660+ :- [%nonce ship proxy nonce]~
661+ (put:orm points.state ship u.point)
662+::
663+:: Receive an individual L2 transaction
664+::
665+++ receive-tx
666+ |= [=state =tx]
667+ |^
668+ ^- (unit [effects ^state])
669+ ?- +<.tx
670+ %spawn (process-spawn +>.tx)
671+ %transfer-point (w-point process-transfer-point ship.from.tx +>.tx)
672+ %configure-keys (w-point process-configure-keys ship.from.tx +>.tx)
673+ %escape (w-point-esc process-escape ship.from.tx +>.tx)
674+ %cancel-escape (w-point-esc process-cancel-escape ship.from.tx +>.tx)
675+ %adopt (w-point-esc process-adopt ship.tx +>.tx)
676+ %reject (w-point-esc process-reject ship.tx +>.tx)
677+ %detach (w-point-esc process-detach ship.tx +>.tx)
678+ %set-spawn-proxy
679+ (w-point-spawn process-set-spawn-proxy ship.from.tx +>.tx)
680+ ::
681+ %set-transfer-proxy
682+ (w-point process-set-transfer-proxy ship.from.tx +>.tx)
683+ ::
684+ %set-management-proxy
685+ (w-point process-set-management-proxy ship.from.tx +>.tx)
686+ ==
687+ ::
688+ ++ w-point
689+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
690+ ^- (unit [effects ^state])
691+ =/ point (get-point state ship)
692+ ?~ point (debug %strange-ship ~)
693+ ?. ?=(%l2 -.u.point) (debug %ship-not-on-l2 ~)
694+ :: Important to fully no-op on failure so we don't insert an entry
695+ :: into points.state
696+ ::
697+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
698+ ?~ res
699+ ~
700+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
701+ ::
702+ ++ w-point-esc
703+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
704+ ^- (unit [effects ^state])
705+ =/ point (get-point state ship)
706+ ?~ point (debug %strange-ship ~)
707+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
708+ ?~ res
709+ ~
710+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
711+ ::
712+ ++ w-point-spawn
713+ |* [fun=$-([ship point *] (unit [effects point])) =ship rest=*]
714+ ^- (unit [effects ^state])
715+ =/ point (get-point state ship)
716+ ?~ point (debug %strange-ship ~)
717+ ?: ?=(%l1 -.u.point) (debug %ship-on-l2 ~)
718+ =/ res=(unit [=effects new-point=^point]) (fun u.point rest)
719+ ?~ res
720+ ~
721+ `[effects.u.res state(points (put:orm points.state ship new-point.u.res))]
722+ ::
723+ ++ process-transfer-point
724+ |= [=point to=address reset=?]
725+ =* ship ship.from.tx
726+ :: Assert from owner or transfer prxoy
727+ ::
728+ ?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
729+ (debug %bad-permission ~)
730+ :: Execute transfer
731+ ::
732+ =/ effects-1
733+ ~[[%point ship %owner to] [%point ship %transfer-proxy *address]]
734+ =: address.owner.own.point to
735+ address.transfer-proxy.own.point *address
736+ ==
737+ :: Execute reset if requested
738+ ::
739+ ?. reset
740+ `[effects-1 point]
741+ ::
742+ =^ effects-2 net.point
743+ ?: =([0 0 0] +.keys.net.point)
744+ `net.point
745+ =/ =keys [+(life.keys.net.point) 0 0 0]
746+ :- [%point ship %keys keys]~
747+ [rift.net.point keys sponsor.net.point escape.net.point]
748+ =^ effects-3 rift.net.point
749+ ?: =(0 life.keys.net.point)
750+ `rift.net.point
751+ :- [%point ship %rift +(rift.net.point)]~
752+ +(rift.net.point)
753+ =/ effects-4
754+ :~ [%point ship %spawn-proxy *address]
755+ [%point ship %management-proxy *address]
756+ [%point ship %voting-proxy *address]
757+ [%point ship %transfer-proxy *address]
758+ ==
759+ =: address.spawn-proxy.own.point *address
760+ address.management-proxy.own.point *address
761+ address.voting-proxy.own.point *address
762+ address.transfer-proxy.own.point *address
763+ ==
764+ `[:(welp effects-1 effects-2 effects-3 effects-4) point]
765+ ::
766+ ++ process-spawn
767+ |= [=ship to=address]
768+ ^- (unit [effects ^state])
769+ =/ parent=^ship (sein ship)
770+ :: Assert parent is on L2
771+ ::
772+ =/ parent-point (get-point state parent)
773+ ?~ parent-point ~
774+ ?. ?=(?(%l2 %spawn) -.u.parent-point) ~
775+ :: Assert from owner or spawn proxy
776+ ::
777+ ?. ?& =(parent ship.from.tx)
778+ |(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
779+ ==
780+ (debug %bad-permission ~)
781+ :: Assert child not already spawned
782+ ::
783+ ?^ (get:orm points.state ship) (debug %spawn-exists ~)
784+ :: Assert one-level-down
785+ ::
786+ ?. =(+((ship-rank parent)) (ship-rank ship)) (debug %bad-rank ~)
787+ ::
788+ =/ [=effects new-point=point]
789+ =/ point=(unit point) (get-point state ship)
790+ ?> ?=(^ point) :: only parsed 4 bytes
791+ :: If spawning to self, just do it
792+ ::
793+ ?: ?| ?& =(%own proxy.from.tx)
794+ =(to address.owner.own.u.parent-point)
795+ ==
796+ ?& =(%spawn proxy.from.tx)
797+ =(to address.spawn-proxy.own.u.parent-point)
798+ ==
799+ ==
800+ :- ~[[%point ship %dominion %l2] [%point ship %owner to]]
801+ u.point(address.owner.own to)
802+ :: Else spawn to parent and set transfer proxy
803+ ::
804+ :- :~ [%point ship %dominion %l2]
805+ [%point ship %owner address.owner.own.u.parent-point]
806+ [%point ship %transfer-proxy to]
807+ ==
808+ %= u.point
809+ address.owner.own address.owner.own.u.parent-point
810+ address.transfer-proxy.own to
811+ ==
812+ `[effects state(points (put:orm points.state ship new-point))]
813+ ::
814+ ++ process-configure-keys
815+ |= [=point crypt=@ auth=@ suite=@ breach=?]
816+ =* ship ship.from.tx
817+ ::
818+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
819+ (debug %bad-permission ~)
820+ ::
821+ =^ rift-effects rift.net.point
822+ ?. breach
823+ `rift.net.point
824+ [[%point ship %rift +(rift.net.point)]~ +(rift.net.point)]
825+ ::
826+ =^ keys-effects keys.net.point
827+ ?: =(+.keys.net.point [suite auth crypt])
828+ `keys.net.point
829+ =/ =keys
830+ [+(life.keys.net.point) suite auth crypt]
831+ [[%point ship %keys keys]~ keys]
832+ ::
833+ `[(welp rift-effects keys-effects) point]
834+ ::
835+ ++ process-escape
836+ |= [=point parent=ship]
837+ =* ship ship.from.tx
838+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
839+ (debug %bad-permission ~)
840+ ::
841+ ?. =(+((ship-rank parent)) (ship-rank ship))
842+ (debug %bad-rank ~)
843+ ::
844+ :+ ~ [%point ship %escape `parent]~
845+ point(escape.net `parent)
846+ ::
847+ ++ process-cancel-escape
848+ |= [=point parent=ship]
849+ =* ship ship.from.tx
850+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
851+ (debug %bad-permission ~)
852+ ::
853+ :+ ~ [%point ship %escape ~]~
854+ point(escape.net ~)
855+ ::
856+ ++ process-adopt
857+ |= [=point =ship]
858+ =* parent ship.from.tx
859+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
860+ (debug %bad-permission ~)
861+ ::
862+ ?. =(escape.net.point `parent) (debug %no-adopt ~)
863+ :+ ~ [%point ship %sponsor `parent]~
864+ point(escape.net ~, sponsor.net [%& parent])
865+ ::
866+ ++ process-reject
867+ |= [=point =ship]
868+ =* parent ship.from.tx
869+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
870+ (debug %bad-permission ~)
871+ ::
872+ ?. =(escape.net.point `parent) (debug %no-reject ~)
873+ :+ ~ [%point ship %escape ~]~
874+ point(escape.net ~)
875+ ::
876+ ++ process-detach
877+ |= [=point =ship]
878+ =* parent ship.from.tx
879+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
880+ (debug %bad-permission ~)
881+ ::
882+ ?. =(who.sponsor.net.point parent) (debug %no-detach ~)
883+ :+ ~ [%point ship %sponsor ~]~
884+ point(has.sponsor.net %|)
885+ ::
886+ ++ process-set-management-proxy
887+ |= [=point =address]
888+ ?. |(=(%own proxy.from.tx) =(%manage proxy.from.tx))
889+ (debug %bad-permission ~)
890+ ::
891+ :+ ~ [%point ship.from.tx %management-proxy address]~
892+ point(address.management-proxy.own address)
893+ ::
894+ ++ process-set-spawn-proxy
895+ |= [=point =address]
896+ ?. |(=(%own proxy.from.tx) =(%spawn proxy.from.tx))
897+ (debug %bad-permission ~)
898+ ::
899+ ?: (gte (ship-rank ship.from.tx) 2)
900+ (debug %spawn-proxy-planet ~)
901+ ::
902+ :+ ~ [%point ship.from.tx %spawn-proxy address]~
903+ point(address.spawn-proxy.own address)
904+ ::
905+ ++ process-set-transfer-proxy
906+ |= [=point =address]
907+ ?. |(=(%own proxy.from.tx) =(%transfer proxy.from.tx))
908+ (debug %bad-permission ~)
909+ ::
910+ :+ ~ [%point ship.from.tx %transfer-proxy address]~
911+ point(address.transfer-proxy.own address)
912+ --
913+--
914+::
915+:: State transition function
916+::
917+|= [=verifier chain-id=@ud =state =input]
918+^- [effects ^state]
919+?: ?=(%log +<.input)
920+ :: Received log from L1 transaction
921+ ::
922+ (receive-log state event-log.input)
923+:: Received L2 batch
924+::
925+:: %+ debug %batch
926+(receive-batch verifier chain-id state batch.input)
base-desk/lib/noun-diff.hoonnew file+213
@@ -0,0 +1,213 @@
1+=<
2+|%
3+++ diff
4+ |= [old=* new=*]
5+ ^- patch
6+ =/ del (extract-del (oracle old new) old)
7+ =/ ins (extract-ins (oracle old new) new)
8+ =/ allowed-holes (~(int in (find-del-holes del)) (find-ins-holes ins))
9+ =. del (filter-del-holes allowed-holes del)
10+ =/ ins (filter-ins-holes allowed-holes ins)
11+ =/ closed-patch (closure (gcp [del ins]))
12+ ?> =(& +.closed-patch)
13+ -.closed-patch
14+++ apply
15+ |= [patch=_id noun=*]
16+ ?- -.patch
17+ %diff
18+ =/ var-map (del del.patch noun)
19+ (ins ins.patch var-map)
20+ ::
21+ %cell
22+ ?> ?=(^ noun)
23+ [$(patch lhs.patch, noun -.noun) $(patch rhs.patch, noun +.noun)]
24+ ==
25+++ id
26+ ^- patch
27+ [%diff [%hole ~] [%hole ~]]
28++$ patch
29+ $% [%cell lhs=patch rhs=patch]
30+ [%diff ^diff]
31+ ==
32+--
33+::
34+|%
35++$ del-diff
36+ $% [%hole @]
37+ [%cell lhs=del-diff rhs=del-diff]
38+ [%ignore ~]
39+ ==
40++$ ins-diff
41+ $% [%hole @ original=*]
42+ [%cell lhs=ins-diff rhs=ins-diff]
43+ [%atom @]
44+ ==
45++$ final-ins-diff
46+ $% [%hole @]
47+ [%cell lhs=final-ins-diff rhs=final-ins-diff]
48+ [%atom @]
49+ ==
50++$ diff [del=del-diff ins=final-ins-diff]
51++$ patch
52+ $% [%cell lhs=patch rhs=patch]
53+ [%diff diff]
54+ ==
55+++ insify-noun
56+ |= noun=*
57+ ^- final-ins-diff
58+ ?- noun
59+ ^ [%cell $(noun -.noun) $(noun +.noun)]
60+ @ [%atom noun]
61+ ==
62+++ empty-set (silt `(list @)`~)
63+++ find-del-holes
64+ |= diff=del-diff
65+ ~+
66+ ^- (set @)
67+ ?- -.diff
68+ %hole (silt ~[+.diff])
69+ %cell (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
70+ %ignore empty-set
71+ ==
72+++ find-final-ins-holes
73+ |= diff=final-ins-diff
74+ ~+
75+ ^- (set @)
76+ ?- -.diff
77+ %hole (silt ~[+.diff])
78+ %cell (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
79+ %atom empty-set
80+ ==
81+++ find-ins-holes
82+ |= diff=ins-diff
83+ ~+
84+ ^- (set @)
85+ ?- -.diff
86+ %hole (silt ~[+<.diff])
87+ %cell (~(uni in $(diff lhs.diff)) $(diff rhs.diff))
88+ %atom empty-set
89+ ==
90+++ filter-del-holes
91+ |= [allowed-holes=(set @) diff=del-diff]
92+ ^- del-diff
93+ ?: ?=(%ignore -.diff) diff
94+ ?- -.diff
95+ %hole ?:((~(has in allowed-holes) +.diff) diff [%ignore ~])
96+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
97+ ==
98+++ filter-ins-holes
99+ |= [allowed-holes=(set @) diff=ins-diff]
100+ ^- final-ins-diff
101+ ?- -.diff
102+ %cell [%cell $(diff +<.diff) $(diff +>.diff)]
103+ %atom diff
104+ %hole
105+ ?: (~(has in allowed-holes) +<.diff) [%hole +<.diff]
106+ (insify-noun original:diff)
107+ ==
108+++ gcp
109+ |= diff=diff
110+ ^- patch
111+ ?- -.ins.diff
112+ %atom [%diff diff]
113+ %hole [%diff diff]
114+ %cell
115+ ?. ?=(%cell -.del.diff)
116+ [%diff diff]
117+ [%cell $(diff [+<.del.diff +<.ins.diff]) $(diff [+>.del.diff +>.ins.diff])]
118+ ==
119+++ closure
120+ |= =patch
121+ ^- [^patch ?]
122+ ?- -.patch
123+ %diff
124+ =/ del-holes (find-del-holes del:patch)
125+ =/ ins-holes (find-final-ins-holes ins:patch)
126+ =/ difference (~(dif in ins-holes) del-holes)
127+ [patch =(difference empty-set)]
128+ ::
129+ %cell
130+ =/ lhs $(patch lhs:patch)
131+ =/ rhs $(patch rhs:patch)
132+ ?: ?&(+.lhs +.rhs) [[%cell -.lhs -.rhs] &]
133+ $(patch (pull-diff [%cell -.lhs -.rhs]))
134+ ==
135+++ pull-diff
136+ |= =patch
137+ ^- [%diff del=del-diff ins=final-ins-diff]
138+ ?- -.patch
139+ %diff patch
140+ %cell
141+ =/ pulled-lhs $(patch lhs:patch)
142+ =/ pulled-rhs $(patch rhs:patch)
143+ :+ %diff
144+ [%cell del:pulled-lhs del:pulled-rhs]
145+ [%cell ins:pulled-lhs ins:pulled-rhs]
146+ ==
147+++ is-subtree
148+ |= [tree=* subtree=*]
149+ ~+
150+ ^- ?
151+ ?: =(tree subtree) &
152+ ?@ tree |
153+ ?| (is-subtree -.tree subtree)
154+ (is-subtree +.tree subtree)
155+ ==
156+++ oracle
157+ |= [a=* b=*]
158+ |= subtree=*
159+ ^- (unit @)
160+ ?: ?& (is-subtree a subtree)
161+ (is-subtree b subtree)
162+ ==
163+ `(mug subtree)
164+ ~
165+++ extract-del
166+ |= [oracle=$-(* (unit @)) subtree=*]
167+ ~+
168+ ^- del-diff
169+ =/ hash (oracle subtree)
170+ ?^ hash [%hole +.hash]
171+ ?@ subtree [%ignore ~]
172+ [%cell (extract-del oracle -.subtree) (extract-del oracle +.subtree)]
173+++ extract-ins
174+ |= [oracle=$-(* (unit @)) subtree=*]
175+ ~+
176+ ^- ins-diff
177+ =/ hash (oracle subtree)
178+ ?^ hash [%hole +.hash subtree]
179+ ?@ subtree [%atom subtree]
180+ [%cell (extract-ins oracle -.subtree) (extract-ins oracle +.subtree)]
181+++ ins
182+ |= [diff=final-ins-diff var-map=(map @ *)]
183+ ^- *
184+ ?- -.diff
185+ %atom +.diff
186+ %cell [$(diff +<.diff) $(diff +>.diff)]
187+ %hole (~(got by var-map) +.diff)
188+ ==
189+++ del
190+ |= [diff=del-diff noun=*]
191+ ^- (map @ *)
192+ |^ (go diff noun ((map @ *) ~))
193+ ++ go
194+ |= [diff=del-diff noun=* var-map=(map @ *)]
195+ ^- (map @ *)
196+ ?- -.diff
197+ %ignore
198+ var-map
199+ ::
200+ %hole
201+ =/ subtree (~(get by var-map) +.diff)
202+ ?~ subtree (~(put by var-map) +.diff noun)
203+ ?> =(+.subtree noun)
204+ var-map
205+ ::
206+ %cell
207+ ?> ?=(^ noun)
208+ =/ lhs-var-map $(diff +<.diff, noun -.noun)
209+ =/ rhs-var-map $(diff +>.diff, noun +.noun, var-map lhs-var-map)
210+ rhs-var-map
211+ ==
212+ --
213+--
base-desk/lib/ph/io.hoon+238−4
@@ -1,5 +1,5 @@
11/- *aquarium, spider
2−/+ libstrand=strand, *strandio, util=ph-util
2+/+ libstrand=strand, *strandio, util=ph-util, aqua-azimuth, vere
33=, strand=strand:libstrand
44|%
55++ send-events
@@ -88,7 +88,6 @@
8888 ^- form:m
8989 (pure:m ~)
9090::
91−::
9291++ init
9392 =/ m (strand ,~)
9493 ^- form:m
@@ -125,9 +124,11 @@
125124 1
126125 +(+.old-rut)
127126 ;< ~ bind:m (send-azimuth-action %breach who)
127+ ;< ~ bind:m ?~ old-rut (sleep ~s20) :: XX don't wait if possible
128+ (wait-for-sunk her who) :: wait only if previously talked
128129 |- ^- form:m
129130 =* loop $
130− ;< ~ bind:m (sleep ~s1)
131+ ;< ~ bind:m (sleep ~s10)
131132 ;< =bowl:spider bind:m get-bowl
132133 =/ aqua-pax
133134 :- %i
@@ -137,14 +138,58 @@
137138 (pure:m ~)
138139 loop
139140::
141+++ init-moon ::NOTE real moon always have the same keys
142+ |= [moon=ship fake=?]
143+ ?> ?=(%earl (clan:title moon))
144+ ?: fake (init-ship moon &)
145+ =/ m (strand ,~)
146+ ;< ~ bind:m
147+ %+ dojo (^sein:title moon)
148+ =/ =pass pub:ex:(get-keys:aqua-azimuth moon 1)
149+ "|moon {(scow %p moon)}, =public-key {(scow %uw pass)}"
150+ (init-ship moon |)
151+::
140152++ init-ship
153+ =| core=?(%mesa %ames) :: XX make %mesa the default core
141154 |= [=ship fake=?]
142155 =/ m (strand ,~)
143156 ^- form:m
144157 ~& > "starting {<ship>}"
145− ;< ~ bind:m (send-events (init:util ship fake))
158+ ;< ~ bind:m (send-events (init:util ship fake ~ core))
146159 (check-ship-booted ship)
147160::
161+++ init-comet
162+ =| core=?(%mesa %ames) :: XX make %mesa the default core
163+ |= comet=ship
164+ =/ m (strand ,~)
165+ ^- form:m
166+ :: hardcoded for:
167+ :: ~londeg-tirlys-somlyd-poltus--pintyn-tarbyl-bicnux-marbud
168+ ::
169+ =/ =feed:jael
170+ :* [%2 ~]
171+ who=comet
172+ ryf=0
173+ :_ ~
174+ :- lyf=1
175+ key=0wfm.lBEWM.08gfy.AxYjy.8-tBQ.uq-aa.LZt9c.CVQqd.XBJIs.
176+ CoG90.BNNGV.1ZmVi.ZbAhY.LuhwC.idNnU.lCVkt.Z4qug.7iY92
177+ ==
178+ ::
179+ ?> ?=(^ (veri:dawn:vere comet feed *point:azimuth-types ~))
180+ ~& > "mining comet under {<(^sein:title comet)>}"
181+ ;< ~ bind:m (send-events (init:util comet fake=%.n `feed core))
182+ (check-ship-booted comet)
183+::
184+:: Load network core protocol
185+::
186+++ load
187+ |= [who=ship ore=?(%mesa %ames)]
188+ =/ m (strand ,~)
189+ ^- form:m
190+ ;< ~ bind:m (send-events [%event who [/a/aqua/load %load ore]]~)
191+ (pure:m ~)
192+::
148193++ check-ship-booted
149194 |= =ship
150195 =/ m (strand ,~)
@@ -181,6 +226,129 @@
181226 (pure:m ~)
182227 loop
183228::
229+++ wait-for-flub
230+ |= [our=ship her=ship dap=term]
231+ =/ m (strand ,~)
232+ ^- form:m
233+ ;< =bowl:spider bind:m get-bowl
234+ |- ^- form:m
235+ =* loop $
236+ ;< [from=^ship =unix-effect] bind:m take-unix-effect
237+ ;< now=@da bind:m get-time
238+ ?. =(from our)
239+ :: our sends the $boon with the halted flow number, at this point
240+ :: gall has updated its state adding the app to gall's .halts map
241+ :: XX search deeper in the .unix-effect?
242+ ::
243+ loop
244+ =/ aqua-pax
245+ :- %i
246+ /(scot %p our)/gg/(scot %p our)//(scot %da now)/[%$]/noun
247+ =+ ;; flubs=(unit (jug ship term))
248+ (scry-aqua:util noun our.bowl now aqua-pax)
249+ ?~ flubs loop
250+ ?. (~(has ju u.flubs) her dap)
251+ loop
252+ (pure:m ~)
253+::
254+++ wait-for-spur
255+ |= [our=ship her=ship dap=term]
256+ =/ m (strand ,~)
257+ ^- form:m
258+ ;< =bowl:spider bind:m get-bowl
259+ |- ^- form:m
260+ =* loop $
261+ ;< [from=ship =unix-effect] bind:m take-unix-effect
262+ ;< now=@da bind:m get-time
263+ ?. =(from our)
264+ :: our ack to the %spur $boon is sent, at this point
265+ :: gall has updated its state adding the app to gall's .flubs map
266+ :: XX search deeper in the .unix-effect?
267+ ::
268+ loop
269+ =/ aqua-pax
270+ :- %i
271+ /(scot %p our)/gg/(scot %p our)//(scot %da now)/[%$]/noun
272+ =+ ;; flubs=(unit (jug ship term))
273+ (scry-aqua:util noun our.bowl now aqua-pax)
274+ ?~ flubs loop
275+ ?: (~(has ju u.flubs) her dap)
276+ loop
277+ (pure:m ~)
278+::
279+++ wait-for-has-halt
280+ |= [our=ship her=ship dap=term]
281+ =/ m (strand ,~)
282+ ^- form:m
283+ ;< =bowl:spider bind:m get-bowl
284+ |- ^- form:m
285+ =* loop $
286+ ;< [from=ship =unix-effect] bind:m take-unix-effect
287+ ;< now=@da bind:m get-time
288+ :: only %send or %push effects
289+ ::
290+ ?. ?=(?(%send %push) -.q.unix-effect)
291+ loop
292+ ~& >> from^unix-effect
293+ ?. =(from our)
294+ :: wait until our ack for the %flub $boon is sent, at this point
295+ :: gall has updated its state adding the app to gall's .flubs map
296+ :: XX search deeper in the .unix-effect?
297+ ::
298+ loop
299+ =/ aqua-pax
300+ :- %i
301+ /(scot %p our)/gh/(scot %p our)//(scot %da now)/[%$]/noun
302+ =+ ;; halts=(unit (jug app=term [ship =duct]))
303+ (scry-aqua:util noun our.bowl now aqua-pax)
304+ ?~ halts loop
305+ ?. (~(has by u.halts) dap) :: XX check .her as well
306+ loop
307+ (pure:m ~)
308+::
309+++ wait-for-del-halt
310+ |= [our=ship her=ship dap=term]
311+ =/ m (strand ,~)
312+ ^- form:m
313+ ;< =bowl:spider bind:m get-bowl
314+ |- ^- form:m
315+ =* loop $
316+ ;< [from=ship =unix-effect] bind:m take-unix-effect
317+ ;< now=@da bind:m get-time
318+ ?. =(from our)
319+ :: when the %spur $boon has been sent, gall has updated its state deleting
320+ :: the app from gall's .flubs map
321+ :: XX search deeper in the .unix-effect?
322+ ::
323+ loop
324+ =/ aqua-pax
325+ :- %i
326+ /(scot %p our)/gh/(scot %p our)//(scot %da now)/[%$]/noun
327+ =+ ;; halts=(unit (jug app=term [ship =duct]))
328+ (scry-aqua:util noun our.bowl now aqua-pax)
329+ ?~ halts loop
330+ ?: (~(has by u.halts) dap) :: XX check .her as well
331+ loop
332+ (pure:m ~)
333+::
334+++ wait-for-pac
335+ |= [our=ship to=ship]
336+ =/ m (strand ,~)
337+ ^- form:m
338+ ~& > "waiting for ack: {<[from=our to=to]>}"
339+ |- ^- form:m
340+ =* loop $
341+ ;< [from=ship =unix-effect] bind:m take-unix-effect
342+ ?. =(our from)
343+ loop
344+ :: only %send or %push effects
345+ ::
346+ ?. ?=(?(%send %push) -.q.unix-effect)
347+ loop
348+ :: XX check that this is an %ack?
349+ :: XX check that this is for .to?
350+ ::
351+ (pure:m ~)
184352:: Send "|hi" from one ship to another
185353::
186354++ send-hi
@@ -192,6 +360,64 @@
192360::
193361:: Send "|hi" and wait for "not responding" message
194362::
363+++ wait-for-sunk
364+ |= [from=@p to=@p]
365+ =/ m (strand ,~)
366+ (wait-for-output from "{(scow %p to)} has sunk")
367+::
368+++ wait-for-fact
369+ |= [=ship =mark =wire gate=$-([mark noun] ?)]
370+ =/ m (strand ,noun)
371+ ^- form:m
372+ ~& > "waiting for fact: {<mark>}"
373+ |- ^- form:m
374+ =* loop $
375+ ;< [her=^ship =unix-effect] bind:m take-unix-effect
376+ ?. =(her ship)
377+ loop
378+ ?. ?& =(wire p.unix-effect)
379+ ?=([%unto %raw-fact *] q.unix-effect)
380+ =(mark mark.unto.q.unix-effect)
381+ (gate mark noun.unto.q.unix-effect)
382+ ==
383+ loop
384+ (pure:m noun.unto.q.unix-effect)
385+::
386+++ wait-for-cork
387+ |= [our=ship her=ship flow=(each bone:ames side:ames)]
388+ =/ m (strand ,~)
389+ ^- form:m
390+ ;< =bowl:spider bind:m get-bowl
391+ |- ^- form:m
392+ =* loop $
393+ ;< [from=ship =unix-effect] bind:m take-unix-effect
394+ ;< now=@da bind:m get-time
395+ :: only %send or %push effects
396+ ::
397+ ?. ?=(?(%send %push) -.q.unix-effect)
398+ loop
399+ :: XX check that this is an %ack?
400+ ::
401+ ?. =(from our)
402+ :: wait until the ack for the %cork $plea is sent, at this point
403+ :: the publisher has already corked the flow
404+ ::
405+ loop
406+ =/ aqua-pax
407+ %+ weld /i/(scot %p our)/ax/(scot %p our)//(scot %da now)
408+ ?: ?=(%& -.flow)
409+ /corked/(scot %p her)/(scot %ud +.flow)/noun
410+ =/ [=bone:ames =dire:ames] +.flow
411+ /corked/(scot %p her)/[dire]/(scot %ud bone)/noun
412+ =+ ;; corked=(unit ?) (scry-aqua:util noun our.bowl now aqua-pax)
413+ ?~ corked loop
414+ ?. u.corked :: XX check .her as well
415+ loop
416+ ~& >> flow-is-corked/flow
417+ (pure:m ~)
418+::
419+:: Send "|hi" and wait for "not responding" message
420+::
195421++ send-hi-not-responding
196422 |= [from=@p to=@p]
197423 ~& > 'sending hi not responding'
@@ -244,6 +470,14 @@
244470 ;< ~ bind:m (send-events (insert-files:util her desk [pax warped] ~))
245471 (pure:m warped)
246472::
473+++ copy-file
474+ =/ m (strand ,~)
475+ |= [her=ship pax=path file=@t]
476+ ^- form:m
477+ ;< ~ bind:m
478+ (send-events (insert-files:util her %base [pax file] ~))
479+ (sleep ~s1)
480+::
247481:: Check /sur/aquarium/hoon on the given has the given contents.
248482::
249483++ check-file-touched
base-desk/lib/ph/util.hoon+25−12
@@ -16,9 +16,11 @@
1616:: Start a ship (low-level; prefer +raw-ship)
1717::
1818++ init
19− |= [who=ship fake=?]
19+ |= [who=ship ake=? eed=(unit feed:jael) ore=?(%mesa %ames)]
2020 ^- (list aqua-event)
21− [%init-ship who fake]~
21+ :~ [%init-ship who ake eed]
22+ [%event who [/a/aqua/load %load ore]]
23+ ==
2224::
2325:: Send dojo command
2426::
@@ -28,8 +30,8 @@
2830 %+ send-events-to who
2931 ^- (list unix-event)
3032 :~
31− [/d/term/1 %belt %ctl `@c`%e]
32− [/d/term/1 %belt %ctl `@c`%u]
33+ [/d/term/1 %belt %mod %ctl `@c`%e]
34+ [/d/term/1 %belt %mod %ctl `@c`%u]
3335 [/d/term/1 %belt %txt ((list @c) what)]
3436 [/d/term/1 %belt %ret ~]
3537 ==
@@ -40,7 +42,7 @@
4042 |= [who=ship what=term]
4143 ^- (list ph-event)
4244 %+ send-events-to who
43− :~ [/d/term/1 %belt %ctl (,@c what)]
45+ :~ [/d/term/1 %belt %mod %ctl (,@c what)]
4446 ==
4547::
4648:: Inject a file into a ship
@@ -59,18 +61,29 @@
5961::
6062:: Checks whether the given event is a dojo output blit containing the
6163:: given tape
62−::
64+::TODO should be rename -dill-output
6365++ is-dojo-output
6466 |= [who=ship her=ship uf=unix-effect what=tape]
67+ |^
6568 ?& =(who her)
6669 ?=(%blit -.q.uf)
67− ::
68− %+ lien p.q.uf
69− |= =blit:dill
70− ?. ?=(%lin -.blit)
71− |
72− !=(~ (find what p.blit))
70+ (lien p.q.uf handle-blit)
7371 ==
72+ ::
73+ ++ handle-blit
74+ |= =blit:dill
75+ ^- ?
76+ ?: ?=(%mor -.blit)
77+ (lien p.blit handle-blit)
78+ ?+ -.blit |
79+ %put !=(~ (find what p.blit))
80+ ::
81+ %klr
82+ %+ lien p.blit
83+ |= [* q=(list @c)]
84+ !=(~ (find what q))
85+ ==
86+ --
7487::
7588:: Test is successful if +is-dojo-output
7689::
base-desk/lib/pill.hoon+500−18
@@ -2,6 +2,7 @@
22::
33/- dice
44^?
5+=<
56|%
67::
78+$ pill
@@ -11,6 +12,14 @@
1112 boot-ova=(list)
1213 kernel-ova=(list unix-event)
1314 userspace-ova=(list unix-event)
15+ ==
16+ $: %cash
17+ $: nam=term
18+ boot-ova=(list)
19+ kernel-ova=(list unix-event)
20+ userspace-ova=(list unix-event)
21+ ==
22+ cache=(list)
1423 == ==
1524::
1625+$ unix-event
@@ -32,25 +41,23 @@
3241 [/sys/hoon hoon/hoon]
3342 [/sys/arvo hoon/arvo]
3443 ==
44+:: +dirs: filter for userspace directories to include
45+::
46+++ dirs
47+ |= [bas=path exc=(list spur)]
48+ ^- (list spur)
49+ ?~ exc ~(tap in dir:.^(arch %cy bas))
50+ %+ skim
51+ ~(tap in dir:.^(arch %cy bas))
52+ |=(=spur =(~ (find ~[spur] exc)))
3553:: +file-ovum: userspace filesystem load
3654::
3755:: bas: full path to / directory
3856::
3957++ file-ovum
40− =/ directories=(list path)
41− :~ /app :: %gall applications
42− /gen :: :dojo generators
43− /lib :: libraries
44− /mar :: mark definitions
45− /sur :: structures
46− /sys :: system files
47− /ted :: :spider strands
48− /web :: %eyre web content
49− /desk :: desk manifest
50− ==
51− |= [des=desk bas=path]
58+ |= [des=desk bas=path exc=(list spur)]
5259 ^- unix-event
53− %. directories
60+ %. (dirs bas exc)
5461 |= :: sal: all spurs to load from
5562 ::
5663 sal=(list spur)
@@ -61,9 +68,13 @@
6168 =; hav ~& user-files+(lent hav)
6269 =/ =yuki:clay
6370 :- *(list tako:clay)
64− %- ~(gas by *(map path (each page:clay lobe:clay)))
65− (turn hav |=([=path =page:clay] [path &+page]))
71+ %- ~(gas by *(map path (each page lobe:clay)))
72+ (turn hav |=([=path =page] [path &+page]))
6673 [/c/sync [%park des &+yuki *rang:clay]]
74+ (file-pages bas sal)
75+::
76+++ file-pages
77+ |= [bas=path sal=(list spur)]
6778 =| hav=(list [path page:clay])
6879 |- ^+ hav
6980 ?~ sal ~
@@ -95,13 +106,43 @@
95106::
96107:: +file-ovum2: electric boogaloo
97108::
98−++ file-ovum2 |=(p=path `unix-event`[//arvo what/(user-files p)])
109+++ file-ovum2 |=(z=[path (list spur)] `unix-event`[//arvo what/(user-files z)])
110+::
111+++ get-wynn
112+ |= [sys=path new-wynn=?]
113+ ^- wynn
114+ ?. new-wynn
115+ :~ zuse+zuse
116+ lull+lull
117+ arvo+arvo
118+ hoon+hoon-version
119+ ==
120+ ~> %bout.[0 %get-wynn]
121+ =+ .^(zus=vase %ca (welp sys /zuse/hoon))
122+ =+ .^(lul=vase %ca (welp sys /lull/hoon))
123+ =+ .^(arv=vase %ca (welp sys /arvo/hoon))
124+ =+ .^(hun=vase %ca (welp sys /hoon/hoon))
125+ :~ [%zuse ;;(@ud q:(slap zus limb+%zuse))]
126+ [%lull ;;(@ud q:(slap lul limb+%lull))]
127+ [%arvo ;;(@ud q:(slap arv limb+%arvo))]
128+ [%hoon ;;(@ud q:(slap hun limb+%hoon-version))]
129+ ==
130+::
131+++ prep-ovum
132+ |= dez=(list path)
133+ ^- unix-event
134+ =- ~& clay-blobs+~(wyt by -)
135+ [/c/inflate [%prep -]]
136+ %+ roll dez
137+ |= [bas=path out=(map lobe:clay page:clay)]
138+ %- ~(uni by out)
139+ .^(_out %cs (snoc bas %bloc))
99140::
100141:: +user-files: all userspace hoon files
101142::
102143++ user-files
103− |= bas=path
104− %. directories:file-ovum
144+ |= [bas=path exc=(list spur)]
145+ %. (dirs bas exc)
105146 |= sal=(list spur)
106147 ^- (list (pair path (cask)))
107148 ::
@@ -135,4 +176,445 @@
135176 |- ^+ hav
136177 ?~ all hav
137178 $(all t.all, hav ^$(tyl [p.i.all tyl]))
179+::
180+::TODO include %prep task in solid and brass?
181+++ solid
182+ :: sys: root path to boot system, `/~me/[desk]/now/sys`
183+ :: dez: secondary desks and their root paths
184+ :: exc: list of desk folders to exclude
185+ ::
186+ |= [sys=path dez=(list [desk path]) dub=? now=@da prime=? exc=(list spur)]
187+ ^- pill
188+ =/ bas=path (scag 3 sys)
189+ =/ compiler-path (weld sys /hoon)
190+ =/ arvo-path (weld sys /arvo)
191+ ~& %solid-start
192+ =/ compiler-src .^(@t %cx (weld compiler-path /hoon))
193+ =/ arvo-src .^(@t %cx (weld arvo-path /hoon))
194+ =/ arvo-formula
195+ ~& %solid-loaded
196+ =/ compiler-hoon (rain compiler-path compiler-src)
197+ ?. dub
198+ :: compile arvo against hoon, with our current compiler
199+ ::
200+ =/ whole-hoon=hoon
201+ [%tsgr compiler-hoon [%tsgr [%$ 7] (rain arvo-path arvo-src)]]
202+ ~& %solid-parsed
203+ =/ whole-formula q:(~(mint ut %noun) %noun whole-hoon)
204+ ~& %solid-arvo
205+ whole-formula
206+ :: compile arvo against hoon, with a freshly compiled hoon (via +ride)
207+ ::
208+ ~& %solid-parsed
209+ =/ compiler-formula q:(~(mint ut %noun) %noun compiler-hoon)
210+ ~& %solid-compiled
211+ =/ whole-src
212+ (rap 3 ['=> ' compiler-src '=> +7 ' arvo-src ~])
213+ ~& %solid-double-loaded
214+ =/ whole-formula
215+ =< +
216+ .* [%noun whole-src]
217+ [%8 compiler-formula [%9 2 %10 [6 %0 3] [%0 2]]]
218+ ~& %solid-double-compiled
219+ whole-formula
220+ ::
221+ ~& [%solid-kernel `@ux`(mug arvo-formula)]
222+ ::
223+ :: installed: Arvo gate (formal interface) with %zuse and vanes installed
224+ ::
225+ =/ installed
226+ =< q
227+ %^ spin
228+ ^- (list ovum)
229+ :- (boot-ovum:pill compiler-src arvo-src)
230+ %+ turn
231+ (snoc (turn dez tail) bas)
232+ |= =path
233+ (file-ovum2 [path exc])
234+ .*(0 arvo-formula)
235+ |= [ovo=ovum ken=*]
236+ [~ (slum ken [now ovo])]
237+ ::
238+ :: kernel-formula
239+ ::
240+ :: We evaluate :arvo-formula (for jet registration),
241+ :: then ignore the result and produce .installed
242+ ::
243+ =/ kernel-formula
244+ [%7 arvo-formula %1 installed]
245+ ::
246+ :: boot-two: startup formula
247+ ::
248+ =/ boot-two
249+ => *[kernel-formula=^ tale=*]
250+ != [.*(0 kernel-formula) tale]
251+ ::
252+ :: boot-ova
253+ ::
254+ =/ boot-ova=(list)
255+ [aeon:eden:part boot-two kernel-formula ~]
256+ ::
257+ :: a pill is a 3-tuple of event-lists: [boot kernel userspace]
258+ ::
259+ :: Our kernel event-list is ~, as we've already installed them.
260+ :: Our userspace event-list is a list containing a full %clay
261+ :: filesystem sync event.
262+ ::
263+ :+ %pill %solid
264+ :+ boot-ova ~
265+ =. dez (snoc dez [%base bas])
266+ ;: weld
267+ %+ turn dez
268+ |= [dek=desk bas=path]
269+ (file-ovum [dek bas exc])
270+ ::
271+ ?. prime ~
272+ [(prep-ovum (turn dez tail))]~
273+ ::
274+ %+ turn dez
275+ |= [dek=desk *]
276+ ^- unix-event
277+ [/c/essential/[dek] %esse dek %.y]
278+ ==
279+::
280+++ brass
281+ :: sys: root path to boot system, `/~me/[desk]/now/sys`
282+ :: dez: secondary desks and their root paths
283+ :: exc: list of desk folders to exclude
284+ ::
285+ |= [sys=path dez=(list [desk path]) cache=? prime=? new-wynn=? exc=(list spur)]
286+ ^- pill
287+ =/ bas=path (scag 3 sys)
288+ :: compiler-source: hoon source file producing compiler, `sys/hoon`
289+ ::
290+ =+ compiler-source=.^(@t %cx (welp sys /hoon/hoon))
291+ ::
292+ :: compiler-twig: compiler as hoon expression
293+ ::
294+ ~& %brass-parsing
295+ =+ compiler-twig=(rain /sys/hoon/hoon compiler-source)
296+ ~& %brass-parsed
297+ ::
298+ :: compiler-formula: compiler as nock formula
299+ ::
300+ ~& %brass-compiling
301+ =+ compiler-formula=q:(~(mint ut %noun) %noun compiler-twig)
302+ ~& %brass-compiled
303+ ::
304+ :: arvo-source: hoon source file producing arvo kernel, `sys/arvo`
305+ ::
306+ =+ arvo-source=.^(@t %cx (welp sys /arvo/hoon))
307+ ::
308+ :: boot-ova: startup events
309+ ::
310+ =/ boot-ova=(list)
311+ :~ aeon:eden:part
312+ boot:eden:part
313+ compiler-formula
314+ compiler-source
315+ arvo-source
316+ ==
317+ ::
318+ =/ kernel-ova=(list unix-event)
319+ :~ (boot-ovum compiler-source arvo-source)
320+ (file-ovum2 [bas exc])
321+ ==
322+ ::
323+ =/ userspace-ova
324+ =. dez (snoc dez [%base bas])
325+ ;: weld
326+ %+ turn dez
327+ |= [dek=desk bas=path]
328+ (file-ovum [dek bas exc])
329+ ::
330+ ?. prime ~
331+ [(prep-ovum (turn dez tail))]~
332+ ::
333+ %+ turn dez
334+ |= [dek=desk *]
335+ ^- unix-event
336+ [/c/essential/[dek] %esse dek %.y]
337+ ==
338+ ::
339+ =/ =wynn (get-wynn sys new-wynn)
340+ ::
341+ =/ cache=(list)
342+ ?. cache ~
343+ %~ tap by
344+ =< +
345+ %- mice
346+ :_ [%2 [%0 3] %0 2]
347+ ;: weld
348+ boot-ova
349+ ^- (list)
350+ :~ [*@da //arvo %wack *@uvJ]
351+ [*@da //arvo %whom *@p]
352+ [*@da //arvo %wyrd [~.nonce /] wynn]
353+ ==
354+ `(list)`(turn kernel-ova (lead *@da))
355+ `(list)`[*@da [/d/term/1 %boot & %fake *@p]]^~
356+ `(list)`(turn userspace-ova (lead *@da))
357+ `(list)`[*@da [/c/init-zest %zeal (turn dez |=([d=desk *] [d %live]))]]^~
358+ ==
359+ ::
360+ ~> %slog.[0 leaf+"brass: cache entries: {<(lent cache)>}"]
361+ ::
362+ :- %cash
363+ :_ cache
364+ [%brass boot-ova kernel-ova userspace-ova]
365+::
366+++ ivory
367+ |= sys=path
368+ ^- pill
369+ =/ lib (snoc (scag 3 sys) %lib)
370+ |^ =/ ver
371+ =/ sub *(trap vase)
372+ =. sub (build-sys sub %hoon)
373+ =. sub (build-sys sub %arvo)
374+ =. sub (build-sys sub %lull)
375+ =. sub (build-sys sub %zuse)
376+ =. sub (build-lib sub & %ethereum)
377+ =. sub (build-lib sub & %azimuth)
378+ (build-lib sub | %vere)
379+ =/ nok !.
380+ => *[ver=(trap vase) ~]
381+ != q:$:ver
382+ ivory/[nok ver ~]
383+ ::
384+ ++ build-sys
385+ |= [sub=(trap vase) nam=term] ^- (trap vase)
386+ ~> %slog.[0 leaf+"ivory: building /sys/{(trip nam)}"]
387+ (swat sub (rain /sys/[nam]/hoon .^(@t cx+(welp sys /[nam]/hoon))))
388+ ::
389+ ++ build-lib
390+ |= [sub=(trap vase) imp=? nam=term] ^- (trap vase)
391+ ~> %slog.[0 leaf+"ivory: building /lib/{(trip nam)}"]
392+ =/ hun=hoon
393+ %+ mist /lib/[nam]/hoon
394+ .^(@t cx+(welp lib /[nam]/hoon))
395+ ?. imp (swat sub hun)
396+ (swel sub [%ktts nam hun])
397+ :: +mist: +rain but skipping past ford runes
398+ ::
399+ ++ mist
400+ |= [bon=path txt=@]
401+ ^- hoon
402+ =+ vas=vast
403+ ~| bon
404+ %+ scan (trip txt)
405+ %- full
406+ =; fud
407+ (ifix [;~(plug gay fud) gay] tall:vas(wer bon))
408+ %- star
409+ ;~ pose vul
410+ %+ ifix [fas (just `@`10)]
411+ (star ;~(less (just `@`10) next))
412+ ==
413+ :: +swel: +swat but with +slop
414+ ::
415+ ++ swel
416+ |= [tap=(trap vase) gen=hoon]
417+ ^- (trap vase)
418+ =/ gun (~(mint ut p:$:tap) %noun gen)
419+ => [tap=tap gun=gun]
420+ |. ~+
421+ =/ pro q:$:tap
422+ [[%cell p.gun p:$:tap] [.*(pro q.gun) pro]]
423+ --
424+::
425+++ events
426+ |%
427+ +$ prop [%prop meta tier (list ovum)]
428+ +$ meta term
429+ +$ tier ?(%fore %hind) :: before or after userspace
430+ ::
431+ ++ install
432+ |= [as=desk =beak pri=?]
433+ ^- prop
434+ :^ %prop (rap 3 %install '-' as ~) %hind
435+ ::TODO will exclude non-:directories files, such as /changelog/txt
436+ =- (murn - same)
437+ ^- (list (unit ovum))
438+ :~ `(file-ovum as (en-beam beak /) ~)
439+ ::
440+ ?. pri ~
441+ `(prep-ovum (en-beam beak /) ~)
442+ ::
443+ `[/d/install/[as] [%seat as]]
444+ ==
445+ --
446+--
447+::
448+~% %non ..part ~
449+:: +mice: %memo hint caching version of mink
450+::
451+|%
452+++ mice !.
453+ ~/ %mice
454+ |= [subject=* formula=*]
455+ =| trace=(list [@ta *])
456+ =| memo=(map)
457+ =; [ton=tone mem=_memo]
458+ ?. ?=(%0 -.ton)
459+ [ton ~]
460+ [ton mem]
461+ |^ ^- [tone _memo]
462+ ?+ formula [[%2 trace] memo]
463+ [^ *]
464+ =^ head memo $(formula -.formula)
465+ ?. ?=(%0 -.head) [head memo]
466+ =^ tail memo $(formula +.formula)
467+ :_ memo
468+ ?. ?=(%0 -.tail) tail
469+ [%0 product.head product.tail]
470+ ::
471+ [%0 axis=@]
472+ :_ memo
473+ =/ part (frag axis.formula subject)
474+ ?~ part [%2 trace]
475+ [%0 u.part]
476+ ::
477+ [%1 constant=*]
478+ [[%0 constant.formula] memo]
479+ ::
480+ [%2 subject=* formula=*]
481+ =^ subject memo $(formula subject.formula)
482+ ?. ?=(%0 -.subject) [subject memo]
483+ =^ formula memo $(formula formula.formula)
484+ ?. ?=(%0 -.formula) [formula memo]
485+ %= $
486+ subject product.subject
487+ formula product.formula
488+ ==
489+ ::
490+ [%3 argument=*]
491+ =^ argument memo $(formula argument.formula)
492+ :_ memo
493+ ?. ?=(%0 -.argument) argument
494+ [%0 .?(product.argument)]
495+ ::
496+ [%4 argument=*]
497+ =^ argument memo $(formula argument.formula)
498+ :_ memo
499+ ?. ?=(%0 -.argument) argument
500+ ?^ product.argument [%2 trace]
501+ [%0 .+(product.argument)]
502+ ::
503+ [%5 a=* b=*]
504+ =^ a memo $(formula a.formula)
505+ ?. ?=(%0 -.a) [a memo]
506+ =^ b memo $(formula b.formula)
507+ :_ memo
508+ ?. ?=(%0 -.b) b
509+ [%0 =(product.a product.b)]
510+ ::
511+ [%6 test=* yes=* no=*]
512+ =^ result memo $(formula test.formula)
513+ ?. ?=(%0 -.result) [result memo]
514+ ?+ product.result [[%2 trace] memo]
515+ %& $(formula yes.formula)
516+ %| $(formula no.formula)
517+ ==
518+ ::
519+ [%7 subject=* next=*]
520+ =^ subject memo $(formula subject.formula)
521+ ?. ?=(%0 -.subject) [subject memo]
522+ %= $
523+ subject product.subject
524+ formula next.formula
525+ ==
526+ ::
527+ [%8 head=* next=*]
528+ =^ head memo $(formula head.formula)
529+ ?. ?=(%0 -.head) [head memo]
530+ %= $
531+ subject [product.head subject]
532+ formula next.formula
533+ ==
534+ ::
535+ [%9 axis=@ core=*]
536+ =^ core memo
537+ $(formula core.formula)
538+ ?. ?=(%0 -.core) [core memo]
539+ =/ arm (frag axis.formula product.core)
540+ ?~ arm [[%2 trace] memo]
541+ %= $
542+ subject product.core
543+ formula u.arm
544+ ==
545+ ::
546+ [%10 [axis=@ value=*] target=*]
547+ ?: =(0 axis.formula) [[%2 trace] memo]
548+ =^ target memo $(formula target.formula)
549+ ?. ?=(%0 -.target) [target memo]
550+ =^ value memo $(formula value.formula)
551+ :_ memo
552+ ?. ?=(%0 -.value) value
553+ =/ mutant=(unit *)
554+ (edit axis.formula product.target product.value)
555+ ?~ mutant [%2 trace]
556+ [%0 u.mutant]
557+ ::
558+ [%11 tag=@ next=*]
559+ =^ next memo $(formula next.formula)
560+ :_ memo
561+ ?. ?=(%0 -.next) next
562+ :- %0
563+ .* subject
564+ [11 tag.formula 1 product.next]
565+ ::
566+ [%11 [tag=@ clue=*] next=*]
567+ =^ clue memo $(formula clue.formula)
568+ ?. ?=(%0 -.clue) [clue memo]
569+ ?: ?=(%memo tag.formula)
570+ ?^ hit=(~(get by memo) subject next.formula)
571+ [[%0 u.hit] memo]
572+ =^ ton memo $(formula next.formula)
573+ ?. ?=(%0 -.ton)
574+ [ton memo]
575+ ?: ?=(?(@ [%clay %ford *]) product.clue)
576+ [ton memo]
577+ [ton (~(put by memo) [subject next.formula] product.ton)]
578+ =^ next memo
579+ =? trace
580+ ?=(?(%hunk %hand %lose %mean %spot) tag.formula)
581+ [[tag.formula product.clue] trace]
582+ $(formula next.formula)
583+ :_ memo
584+ ?. ?=(%0 -.next) next
585+ :- %0
586+ .* subject
587+ [11 [tag.formula 1 product.clue] 1 product.next]
588+ ==
589+ ::
590+ ++ frag
591+ |= [axis=@ noun=*]
592+ ^- (unit)
593+ ?: =(0 axis) ~
594+ |- ^- (unit)
595+ ?: =(1 axis) `noun
596+ ?@ noun ~
597+ =/ pick (cap axis)
598+ %= $
599+ axis (mas axis)
600+ noun ?-(pick %2 -.noun, %3 +.noun)
601+ ==
602+ ::
603+ ++ edit
604+ |= [axis=@ target=* value=*]
605+ ^- (unit)
606+ ?: =(1 axis) `value
607+ ?@ target ~
608+ =/ pick (cap axis)
609+ =/ mutant
610+ %= $
611+ axis (mas axis)
612+ target ?-(pick %2 -.target, %3 +.target)
613+ ==
614+ ?~ mutant ~
615+ ?- pick
616+ %2 `[u.mutant +.target]
617+ %3 `[-.target u.mutant]
618+ ==
619+ --
138620--
base-desk/lib/ring.hoon+46−45
@@ -18,10 +18,10 @@
1818 ::
1919 ++ generate-public-linkage
2020 |= link-scope=@
21− ^- [data=@ h=@udpoint]
21+ ^- [data=@ h=@uxpoint]
2222 ::
2323 =/ data=@ (mod link-scope l:ed:crypto)
24− =/ h=@udpoint (scalarmult-base:ed:crypto data)
24+ =/ h=@uxpoint (scalarmult-base:ed:crypto data)
2525 [data h]
2626 :: +generate-linkage: linkage information from scope and private key
2727 ::
@@ -30,13 +30,13 @@
3030 :: y: y = [x] * h
3131 ++ generate-linkage
3232 |= [link-scope=(unit @) my-private-key=@]
33− ^- (unit [data=@ h=@udpoint y=@udpoint])
33+ ^- (unit [data=@ h=@uxpoint y=@uxpoint])
3434 ::
3535 ?~ link-scope
3636 ~
3737 ::
38− =+ [data=@ h=@udpoint]=(generate-public-linkage u.link-scope)
39− =/ y=@udpoint (scalarmult:ed:crypto my-private-key h)
38+ =+ [data=@ h=@uxpoint]=(generate-public-linkage u.link-scope)
39+ =/ y=@uxpoint (scalarmult:ed:crypto my-private-key h)
4040 [~ data h y]
4141 :: +generate-challenge: generate challenge from a given message
4242 ::
@@ -47,13 +47,13 @@
4747 ++ generate-challenge
4848 |= $: :: common to both linked and unlinked
4949 message=@
50− g=@udpoint
50+ g=@uxpoint
5151 :: high level universal state
5252 ::
53− link-state=(unit [data=@ h=@udpoint y=@udpoint])
53+ link-state=(unit [data=@ h=@uxpoint y=@uxpoint])
5454 :: point to include in challenge when link-state isn't ~
5555 ::
56− h=(unit @udpoint)
56+ h=(unit @uxpoint)
5757 ==
5858 ^- @
5959 :: concatenate and reduce our message down to a 512-bit hash
@@ -74,9 +74,9 @@
7474 :: +generate-challenges: generates the full list of challenges
7575 ::
7676 ++ generate-challenges
77− |= $: link-state=(unit [data=@ h=@udpoint y=@udpoint])
77+ |= $: link-state=(unit [data=@ h=@uxpoint y=@uxpoint])
7878 message=@
79− public-keys=(list @udpoint)
79+ public-keys=(list @uxpoint)
8080 ss=(list @)
8181 ::
8282 prev-k=@u
@@ -86,14 +86,14 @@
8686 ==
8787 ^- (list @)
8888 ::
89− =/ gs=@udpoint
89+ =/ gs=@uxpoint
9090 %- add-scalarmult-scalarmult-base:ed:crypto :*
9191 prev-ch
9292 (snag prev-k public-keys)
9393 prev-s
9494 ==
9595 ::
96− =/ hs=(unit @udpoint)
96+ =/ hs=(unit @uxpoint)
9797 ?~ link-state
9898 ~
9999 ::
@@ -125,8 +125,8 @@
125125 :: used to generate the (unit point) consumed by +generate-challenge.
126126 ::
127127 ++ scalarmult-h
128− |= [u=@ linkage=(unit [data=@ h=@udpoint y=@udpoint])]
129− ^- (unit @udpoint)
128+ |= [u=@ linkage=(unit [data=@ h=@uxpoint y=@uxpoint])]
129+ ^- (unit @uxpoint)
130130 ?~ linkage
131131 ~
132132 [~ (scalarmult:ed:crypto u h.u.linkage)]
@@ -143,9 +143,9 @@
143143 |= $: message=@
144144 link-scope=(unit @)
145145 ::
146− anonymity-set=(set @udpoint)
147− my-public-key=@udpoint
148− my-private-key=@udscalar
146+ anonymity-set=(set @uxpoint)
147+ my-public-key=@uxpoint
148+ my-private-key=@uxscalar
149149 ::
150150 eny=@uvJ
151151 ==
@@ -157,7 +157,7 @@
157157 (need (find [my-public-key ~] anonymity-list))
158158 :: Generate linkage information if given
159159 ::
160− =/ linkage=(unit [data=@ h=@udpoint y=@udpoint])
160+ =/ linkage=(unit [data=@ h=@uxpoint y=@uxpoint])
161161 (generate-linkage link-scope my-private-key)
162162 :: initialize our random number generator from entropy
163163 ::
@@ -237,7 +237,7 @@
237237 |= $: message=@
238238 link-scope=(unit @)
239239 ::
240− anonymity-set=(set @udpoint)
240+ anonymity-set=(set @uxpoint)
241241 signature=raw-ring-signature
242242 ==
243243 ^- ?
@@ -259,14 +259,14 @@
259259 =/ s2-to-end=(list @) t.t.s.signature
260260 :: anonymity-list: set of public keys listified in ring order
261261 ::
262− =/ anonymity-list=(list @udpoint)
262+ =/ anonymity-list=(list @uxpoint)
263263 ~(tap in anonymity-set)
264264 :: participants: length of :anonymity-list
265265 ::
266266 =/ participants=@u
267267 (lent anonymity-list)
268268 ::
269− =/ z0p=@udpoint
269+ =/ z0p=@uxpoint
270270 %- add-scalarmult-scalarmult-base:ed:crypto :*
271271 ch0.signature
272272 (head anonymity-list)
@@ -275,14 +275,14 @@
275275 :: generate the linkage using public data, and the y point from the
276276 :: signature
277277 ::
278− =/ linkage=(unit [data=@ h=@udpoint y=@udpoint])
278+ =/ linkage=(unit [data=@ h=@uxpoint y=@uxpoint])
279279 ?~ link-scope
280280 ~
281− =+ [data=@ h=@udpoint]=(generate-public-linkage u.link-scope)
281+ =+ [data=@ h=@uxpoint]=(generate-public-linkage u.link-scope)
282282 :- ~
283283 [data h (need y.signature)]
284284 ::
285− =/ z0pp=(unit @udpoint)
285+ =/ z0pp=(unit @uxpoint)
286286 ?~ linkage
287287 ~
288288 :- ~
@@ -319,7 +319,7 @@
319319 :: +seed-to-private-key-scalar: keyfile form to scalar we can multiply with
320320 ::
321321 ++ seed-to-private-key-scalar
322− |= sk=@I ^- @udscalar
322+ |= sk=@I ^- @uxscalar
323323 ?: (gth (met 3 sk) 32) !!
324324 =+ h=(shal (rsh [0 3] b:ed:crypto) sk)
325325 %+ add
@@ -347,44 +347,45 @@
347347 ::
348348 ++ ship-life-to-pubid
349349 |= [our=@p now=@da ship=@p =life]
350− ^- @udpoint
350+ ^- @uxpoint
351351 ::
352− =/ d=[=^life =pass]
352+ =/ d=[=^life =pass *]
353353 =/ scry-path=path
354− :~ %k
354+ :~ %j
355355 (scot %p our)
356+ %deed
356357 (scot %da now)
357358 (scot %p ship)
358359 (scot %ud life)
359360 ==
360− .^([^life pass] scry-path)
361+ .^([^life pass *] scry-path)
361362 :: we have the deed which has pass, which is several numbers +cat-ed
362363 :: together; pull out the keys
363364 ::
364365 =/ x=[crypt=@ auth=@] (get-public-key-from-pass pass.d)
365366 ::
366− `@udpoint`auth.x
367+ `@uxpoint`auth.x
367368 ::
368369 ++ build-signing-participants
369370 |= [our=@p now=@da invited=(list @p)]
370− ^- [(set [@p life]) (set @udpoint)]
371+ ^- [(set [@p life]) (set @uxpoint)]
371372 ::
372373 =| participants=(set [@p life])
373− =| keys=(set @udpoint)
374+ =| keys=(set @uxpoint)
374375 ::
375376 |-
376377 ?~ invited
377378 [participants keys]
378379 ::
379− =/ =life
380− .^(life k+/(scot %p our)/life/(scot %da now)/(scot %p i.invited))
380+ =/ lyfe=(unit @ud)
381+ .^((unit @ud) j+/(scot %p our)/lyfe/(scot %da now)/(scot %p i.invited))
381382 ::
382− ?: =(life 0)
383+ ?~ lyfe
383384 $(invited t.invited)
384385 ::
385− =/ pubkey=@udpoint (ship-life-to-pubid our now i.invited life)
386+ =/ pubkey=@uxpoint (ship-life-to-pubid our now i.invited u.lyfe)
386387 ::
387− =. participants (~(put in participants) [i.invited life])
388+ =. participants (~(put in participants) [i.invited u.lyfe])
388389 =. keys (~(put in keys) pubkey)
389390 ::
390391 $(invited t.invited)
@@ -392,15 +393,15 @@
392393 ::
393394 ++ build-verifying-participants
394395 |= [our=@p now=@da invited=(list [ship=@p =life])]
395− ^- (set @udpoint)
396+ ^- (set @uxpoint)
396397 ::
397− =| keys=(set @udpoint)
398+ =| keys=(set @uxpoint)
398399 ::
399400 |-
400401 ?~ invited
401402 keys
402403 ::
403− =/ pubkey=@udpoint
404+ =/ pubkey=@uxpoint
404405 (ship-life-to-pubid our now ship.i.invited life.i.invited)
405406 =. keys
406407 (~(put in keys) pubkey)
@@ -431,23 +432,23 @@
431432 =/ link-hash=(unit @) (bind link-scope |=(a=* (shaz (jam a))))
432433 :: get everyone's public keys
433434 ::
434− =/ p=[participants=(set [ship=@p =life]) keys=(set @udpoint)]
435+ =/ p=[participants=(set [ship=@p =life]) keys=(set @uxpoint)]
435436 (build-signing-participants:detail our now ~(tap in anonymity-set))
436437 :: get our ships' current life
437438 ::
438439 =/ our-life=life
439− .^(life %k /(scot %p our)/life/(scot %da now)/(scot %p our))
440+ .^(life %j /(scot %p our)/life/(scot %da now)/(scot %p our))
440441 :: get our ships' secret keyfile ring
441442 ::
442443 =/ secret-ring=ring
443− .^(ring %k /(scot %p our)/vein/(scot %da now)/(scot %ud our-life))
444+ .^(ring %j /(scot %p our)/vein/(scot %da now)/(scot %ud our-life))
444445 :: fetch the encoded auth seed from the ring
445446 ::
446447 =/ secret-auth-seed=@
447448 +:(get-private-key-from-ring:detail secret-ring)
448449 :: get our ships' public key
449450 ::
450− =/ public-key=@udpoint
451+ =/ public-key=@uxpoint
451452 (ship-life-to-pubid:detail our now our our-life)
452453 ::
453454 :- participants.p
@@ -466,7 +467,7 @@
466467 |= [our=@p now=@da message=* =ring-signature]
467468 ^- ?
468469 ::
469− =/ keys=(set @udpoint)
470+ =/ keys=(set @uxpoint)
470471 %^ build-verifying-participants:detail our now
471472 ~(tap in participants.ring-signature)
472473 ::
base-desk/lib/server.hoon+2−2
@@ -19,7 +19,7 @@
1919++ json-to-octs
2020 |= jon=json
2121 ^- octs
22− (as-octt:mimes:html (en-json:html jon))
22+ (as-octs:mimes:html (en:json:html jon))
2323::
2424++ app
2525 |%
@@ -114,7 +114,7 @@
114114 :_ `octs
115115 [200 [['content-type' 'image/svg+xml'] ?:(cache [max-1-wk ~] ~)]]
116116 ::
117− ++ ico-response
117+ ++ ico-response
118118 |= =octs
119119 ^- simple-payload:http
120120 [[200 [['content-type' 'image/x-icon'] max-1-wk ~]] `octs]
base-desk/lib/shoe.hoon+72−36
@@ -13,15 +13,15 @@
1313/- *sole
1414/+ sole, auto=language-server-complete
1515|%
16−+$ state-0
17− $: %0
18− soles=(map @ta sole-share)
16++$ state-2
17+ $: %2
18+ soles=(map sole-id sole-share)
1919 ==
2020:: $card: standard gall cards plus shoe effects
2121::
2222+$ card
2323 $% card:agent:gall
24− [%shoe sole-ids=(list @ta) effect=shoe-effect] :: ~ sends to all soles
24+ [%shoe sole-ids=(list sole-id) effect=shoe-effect] :: ~ sends to all
2525 ==
2626:: $shoe-effect: easier sole-effects
2727::
@@ -47,30 +47,30 @@
4747 :: if the head of the result is true, instantly run the command
4848 ::
4949 ++ command-parser
50− |~ sole-id=@ta
50+ |~ =sole-id
5151 |~(nail *(like [? command-type]))
5252 :: +tab-list: autocomplete options for the session (to match +command-parser)
5353 ::
5454 ++ tab-list
55− |~ sole-id=@ta
55+ |~ =sole-id
5656 :: (list [@t tank])
5757 *(list (option:auto tank))
5858 :: +on-command: called when a valid command is run
5959 ::
6060 ++ on-command
61− |~ [sole-id=@ta command=command-type]
61+ |~ [=sole-id command=command-type]
6262 *(quip card _^|(..on-init))
6363 ::
6464 ++ can-connect
65− |~ sole-id=@ta
65+ |~ =sole-id
6666 *?
6767 ::
6868 ++ on-connect
69− |~ sole-id=@ta
69+ |~ =sole-id
7070 *(quip card _^|(..on-init))
7171 ::
7272 ++ on-disconnect
73− |~ sole-id=@ta
73+ |~ =sole-id
7474 *(quip card _^|(..on-init))
7575 ::
7676 ::NOTE standard gall agent arms below, though they may produce %shoe cards
@@ -119,27 +119,27 @@
119119 |* [shoe=* command-type=mold]
120120 |_ =bowl:gall
121121 ++ command-parser
122− |= sole-id=@ta
122+ |= =sole-id
123123 (easy *[? command-type])
124124 ::
125125 ++ tab-list
126− |= sole-id=@ta
126+ |= =sole-id
127127 ~
128128 ::
129129 ++ on-command
130− |= [sole-id=@ta command=command-type]
130+ |= [=sole-id command=command-type]
131131 [~ shoe]
132132 ::
133133 ++ can-connect
134− |= sole-id=@ta
134+ |= =sole-id
135135 (team:title [our src]:bowl)
136136 ::
137137 ++ on-connect
138− |= sole-id=@ta
138+ |= =sole-id
139139 [~ shoe]
140140 ::
141141 ++ on-disconnect
142− |= sole-id=@ta
142+ |= =sole-id
143143 [~ shoe]
144144 --
145145:: +agent: creates wrapper core that handles sole events and calls shoe arms
@@ -147,7 +147,7 @@
147147++ agent
148148 |* command-type=mold
149149 |= =(shoe command-type)
150− =| state-0
150+ =| state-2
151151 =* state -
152152 ^- agent:gall
153153 =>
@@ -164,8 +164,7 @@
164164 %+ turn
165165 ?^ sole-ids.card sole-ids.card
166166 ~(tap in ~(key by soles))
167− |= sole-id=@ta
168− /sole/[sole-id]
167+ id-to-path:sole
169168 ::
170169 %table
171170 =; fez=(list sole-effect)
@@ -189,7 +188,7 @@
189188 =^ cards shoe on-init:og
190189 [(deal cards) this]
191190 ::
192− ++ on-save !>([%shoe-app on-save:og state])
191+ ++ on-save :(slop !>(%shoe-app) on-save:og !>(state))
193192 ::
194193 ++ on-load
195194 |= old-state=vase
@@ -202,9 +201,45 @@
202201 ?. ?=([%shoe-app ^] q.old-state)
203202 =^ cards shoe (on-load:og old-state)
204203 [(deal cards) this]
205− =^ old-inner state +:!<([%shoe-app vase state-0] old-state)
206− =^ cards shoe (on-load:og old-inner)
207− [(deal cards) this]
204+ |^ :: versions %0 and %1 stored the inner state double-vased.
205+ :: fixing this coincided with the 408 type-of-type migration.
206+ ::
207+ ?: ?=([%shoe-app * ?(%0 %1) *] q.old-state)
208+ =| old-outer=state-any
209+ =^ old-inner old-outer
210+ +:!<([%shoe-app vase:h136 state-any] old-state)
211+ =^ caz shoe (on-load:og (next-vase:h136 old-inner))
212+ =^ cuz old-outer
213+ ?. ?=(%0 -.old-outer) [~ old-outer]
214+ (state-0-to-1 old-outer)
215+ =? old-outer ?=(%1 -.old-outer) (state-1-to-2 old-outer)
216+ ?> ?=(%2 -.old-outer)
217+ [(weld cuz (deal caz)) this(state old-outer)]
218+ ::
219+ =+ !<(old=state-any (slot 7 old-state))
220+ ?> ?=(%2 -.old)
221+ =. state old
222+ ::
223+ =^ caz shoe (on-load:og (slot 6 old-state))
224+ [(deal caz) this]
225+ ::
226+ +$ state-any $%(state-2 state-1 state-0)
227+ +$ state-1 _%*(. *state-2 - %1)
228+ +$ state-0 [%0 soles=(map @ta sole-share)]
229+ ++ state-1-to-2 |=(old=state-1 old(- %2))
230+ ++ state-0-to-1
231+ |= old=state-0
232+ ^- (quip card:agent:gall state-1)
233+ :- %+ turn ~(tap in ~(key by soles.old))
234+ |= id=@ta
235+ ^- card:agent:gall
236+ [%give %kick ~[/sole/[id]] ~]
237+ :- %1
238+ %- ~(gas by *(map sole-id sole-share))
239+ %+ murn ~(tap by soles.old)
240+ |= [id=@ta s=sole-share]
241+ (bind (upgrade-id:sole id) (late s))
242+ --
208243 ::
209244 ++ on-poke
210245 |= [=mark =vase]
@@ -326,19 +361,18 @@
326361 ++ on-watch
327362 |= =path
328363 ^- (quip card:agent:gall agent:gall)
329− ?. ?=([%sole @ ~] path)
364+ ?~ sole-id=(path-to-id:sole path)
330365 =^ cards shoe
331366 (on-watch:og path)
332367 [(deal cards) this]
333− =* sole-id i.t.path
334− ?> (can-connect:og sole-id)
335− =. soles (~(put by soles) sole-id *sole-share)
368+ ?> (can-connect:og u.sole-id)
369+ =. soles (~(put by soles) u.sole-id *sole-share)
336370 =^ cards shoe
337− (on-connect:og sole-id)
371+ (on-connect:og u.sole-id)
338372 :_ this
339373 %- deal
340374 :_ cards
341− [%shoe [sole-id]~ %sole %pro & dap.bowl "> "]
375+ [%shoe [u.sole-id]~ %sole %pro & dap.bowl "> "]
342376 ::
343377 ++ on-leave
344378 |= =path
@@ -388,23 +422,25 @@
388422 =/ cobs=(list [wid=@ud (list tape)])
389423 (turn cows col-as-lines)
390424 =+ [lin=0 any=|]
425+ =+ len=(lent cobs)
391426 =| fez=(list sole-effect)
392427 |- ^+ fez
393− =; out=tape
428+ =; [line=(list tape) end=@ud]
394429 :: done when we're past the end of all columns
395430 ::
396− ?: (levy out (cury test ' '))
431+ ?: =(len end)
397432 (flop fez)
398433 =; fec=sole-effect
399434 $(lin +(lin), fez [fec fez])
435+ =/ out=tape
436+ (zing (join " " line))
400437 ?. bold txt+out
401438 klr+[[`%br ~ ~]^[(crip out)]~]~
402− %+ roll cobs
403− |= [[wid=@ud lines=(list tape)] out=tape]
404− %+ weld out
405− %+ weld ?~(out "" " ")
439+ %^ spin cobs 0
440+ |= [[wid=@ud lines=(list tape)] empty=@ud]
406441 =+ l=(swag [lin 1] lines)
407− ?^(l i.l (reap wid ' '))
442+ ?^ l [i.l empty]
443+ [(reap wid ' ') +(empty)]
408444 ::
409445 ++ col-as-lines
410446 |= [wid=@ud col=dime]
base-desk/lib/sink.hoonnew file+26
@@ -0,0 +1,26 @@
1+/+ noun-diff
2+|%
3+++ clog
4+ |$ [stut]
5+ $%
6+ [%flush stut]
7+ [%drain patch:noun-diff]
8+ ==
9+++ sink
10+ |* pats=(list path)
11+ |* stat=*
12+ |@
13+ ++ sync
14+ |= [stat=_stat]
15+ ^- [card:agent:gall _..sync]
16+ =/ dif
17+ %+ diff:noun-diff ^stat stat
18+ :-
19+ [%give %fact pats %noun !>(^-((clog) [%drain dif]))]
20+ ..sync(stat stat)
21+ ++ paths pats
22+ ++ flush
23+ ^- card:agent:gall
24+ [%give %fact pats %noun !>(^-((clog) [%flush stat]))]
25+ --
26+--
base-desk/lib/sole.hoon+24
@@ -136,4 +136,28 @@
136136 =+ dat=(transmute [%mor leg] [%ins pos `@c`0])
137137 ?> ?=(%ins -.dat)
138138 p.dat
139+::
140+::
141+++ path-to-id
142+ |= =path
143+ ^- (unit sole-id)
144+ ?. ?=([%sole @ ?(~ [@ ~])] path) ~
145+ ?~ who=(slaw %p i.t.path) ~
146+ `[u.who ?~(t.t.path %$ i.t.t.path)]
147+::
148+++ id-to-path
149+ |= sole-id
150+ ^- path
151+ ::TODO this whole "no empty path ending" business feels icky.
152+ :: do we want default session to be ~.~ ?
153+ :: concern here is that outsiders cannot subscribe to the default
154+ :: session, because /sole/~zod/ isn't a valid path...
155+ [%sole (scot %p who) ?~(ses ~ /[ses])]
156+::
157+++ upgrade-id
158+ |= old=@ta
159+ ^- (unit sole-id)
160+ %+ rush old
161+ %+ cook (late %$)
162+ ;~(pfix (jest 'drum_~') fed:ag)
139163--
base-desk/lib/sss.hoonnew file+445
@@ -0,0 +1,445 @@
1+/- *sss
2+/+ *mip
3+::
4+|%
5+++ mk-subs :: Create sub-map.
6+ |* [=(lake) paths=mold]
7+ -:+6:(da lake paths)
8+::
9+++ mk-pubs :: Create pub-map.
10+ |* [=(lake) paths=mold]
11+ -:+6:(du lake paths)
12+::
13+++ mk-mar :: Create mar.
14+ |* =(lake)
15+ |_ =(response:poke lake *)
16+ ++ grow
17+ |%
18+ ++ noun response
19+ --
20+ ++ grab
21+ |%
22+ ++ noun (response:poke lake *)
23+ --
24+ ++ grad %noun
25+ --
26+++ fled :: Like +sped but head is a path.
27+ |= vax=vase
28+ ^- vase
29+ :_ q.vax
30+ %- ~(play ut p.vax)
31+ =- [%wtgr [%wtts - [%& 2]~] [%$ 1]]
32+ =/ pax ~| %path-none ;;(path -.q.vax)
33+ |- ^- spec
34+ ?~ pax [%base %null]
35+ [%bccl ~[[%leaf %ta -.pax] $(pax +.pax)]]
36+::
37+++ zoom |= =noun ~| %need-path $/sss/;;(path noun)
38+::
39+++ da :: Manage subscriptions.
40+ |* [=(lake) paths=mold]
41+ =>
42+ |%
43+ +$ from (on-rock:poke lake paths)
44+ +$ into (response:poke lake paths)
45+ +$ result (request:poke paths)
46+ +$ fail [paths ship dude]
47+ +$ flow [=aeon stale=_| fail=_| =rock:lake]
48+ +$ subs [%0 (map [ship dude paths] (unit flow))]
49+ --
50+ |= $: sub=subs
51+ =bowl:gall
52+ result-type=type
53+ on-rock-type=type
54+ fail-type=type
55+ ==
56+ => .(sub +.sub)
57+ |%
58+ ++ surf :: Subscribe to [ship dude path].
59+ |= which=[ship dude paths]
60+ ^- (quip card:agent:gall subs)
61+ ?+ flow=(~(get by sub) which) `0/sub
62+ ~ [~[(pine which)] 0/(~(put by sub) which ~)]
63+ [~ ~] [~[(pine which)] 0/sub]
64+ [~ ~ [* %& * *]] [~[(pine which)] 0/sub]
65+ ==
66+ ++ quit (corl (lead %0) ~(del by sub)) :: Unsub from [ship dude path].
67+ ++ read :: See current subscribed states.
68+ ^- (map [ship dude paths] [stale=? fail=? =rock:lake])
69+ %- malt %+ murn ~(tap by sub)
70+ |= [key=[ship dude paths] val=(unit flow)]
71+ ?~ val ~
72+ `[key +.u.val]
73+ :: :: Check poke-ack for errors.
74+ :: :: If an %sss-on-rock poke nacks,
75+ ++ chit :: that state is flagged as failed.
76+ |= [[aeon=term ship=term dude=term path=paths] =sign:agent:gall]
77+ ^- subs
78+ :- %0
79+ ?> ?=(%poke-ack -.sign)
80+ ?~ p.sign sub
81+ %+ ~(jab by sub) [(slav %p ship) dude path]
82+ |= (unit flow)
83+ =/ =flow (need +<)
84+ ?> =(aeon.flow (slav %ud aeon))
85+ `flow(fail &)
86+ :: :: Check poke-ack for errors.
87+ :: :: If a scry request nacks,
88+ ++ tell :: that state is flagged as stale.
89+ |= [[ship=term =dude aeon=term path=paths] =sign:agent:gall]
90+ ^- (quip card:agent:gall subs)
91+ ?> ?=(%poke-ack -.sign)
92+ ?~ p.sign `0/sub
93+ =/ current [ship=(slav %p ship) dude=dude path=path]
94+ ?+ flow=(~(get by sub) current) `0/sub
95+ [~ ~ *]
96+ =. stale.u.u.flow &
97+ :_ 0/(~(put by sub) current u.flow)
98+ ~[(on-rock-poke fake=& current u.u.flow ~)]
99+ ::
100+ [~ ~]
101+ :_ 0/(~(del by sub) current) :_ ~
102+ :* %pass (zoom surf-fail/aeon/ship/dude/path)
103+ %agent [our dap]:bowl
104+ %poke %sss-surf-fail fail-type ^- fail
105+ [path ship dude]:current
106+ ==
107+ ==
108+ ++ apply :: Handle response from publisher.
109+ |= res=(response:poke lake paths)
110+ ^- (quip card:agent:gall subs)
111+ =* current [src.bowl dude.res path.res]
112+ =/ old=flow (fall (~(got by sub) current) *flow)
113+ ?: ?=(%tomb what.res)
114+ =/ =flow old(stale &)
115+ :_ 0/(~(put by sub) current `flow) :_ ~
116+ (on-rock-poke fake=& current flow ~)
117+ ::
118+ =/ [wave=(unit wave:lake) new=(unit flow)]
119+ ?- what.res
120+ %rock ?: (lte aeon.res aeon.old) [~ ~]
121+ [~ `[aeon.res | | rock.res]]
122+ %wave ?: (lte aeon.res aeon.old) [~ ~]
123+ ?> =(aeon.res +(aeon.old))
124+ [`wave.res `[aeon.res | | (wash:lake rock.old wave.res)]]
125+ ==
126+ ?~ new `0/sub
127+ :_ 0/(~(put by sub) current new) :_ ~
128+ (on-rock-poke fake=& current u.new wave)
129+ ::
130+ ++ handle-fake-on-rock
131+ |= =(on-rock:poke lake paths)
132+ ^- (list card:agent:gall)
133+ ?~ flow=(~(get by sub) [src from path]:on-rock) ~
134+ ?~ u.flow ~
135+ ?. =([stale fail rock]:u.u.flow [stale fail rock]:on-rock) ~
136+ ~[(on-rock-poke fake=| [src from path]:on-rock u.u.flow wave.on-rock)]
137+ ::
138+ :: Non-public facing arms below
139+ ::
140+ ++ pine
141+ |= [who=ship which=dude where=paths]
142+ ^- card:agent:gall
143+ :* %pass (zoom scry-request/(scot %p who)^which^where)
144+ %agent [who which]
145+ %poke sss-to-pub/[result-type `result`[where dap.bowl]]
146+ ==
147+ ++ on-rock-poke
148+ |= [fake=? [=ship =dude path=paths] flow wave=(unit wave:lake)]
149+ ^- card:agent:gall
150+ :* %pass %+ zoom ?:(fake %fake %on-rock)
151+ (scot %ud aeon)^(scot %p ship)^dude^path
152+ %agent [our dap]:bowl
153+ %poke ?:(fake %sss-fake-on-rock %sss-on-rock)
154+ on-rock-type `from`[path ship dude stale fail rock wave]
155+ ==
156+ --
157+++ du :: Manage publications.
158+ |* [=(lake) paths=mold]
159+ =>
160+ |%
161+ +$ into (request:poke paths)
162+ +$ result (response:poke lake paths)
163+ +$ rule $~ [`5 5]
164+ [horizon=(unit @ud) frequency=@ud] :: Retention policy
165+ +$ tide
166+ $: rok=((mop aeon rock:lake) gte)
167+ wav=((mop aeon wave:lake) lte)
168+ rul=rule
169+ mem=(jug ship dude)
170+ ==
171+ +$ buoy
172+ $: tid=$~(*tide $@(aeon tide))
173+ alo=(unit (set ship))
174+ ==
175+ ++ pubs
176+ =< $>(%1 versioned)
177+ |%
178+ ++ update
179+ |= =versioned
180+ ^- pubs
181+ ?- -.versioned
182+ %1 versioned
183+ %0
184+ :- %1
185+ %- ~(run by +.versioned)
186+ |= =buoy-0:^versioned
187+ ^- buoy
188+ %= buoy-0
189+ tid
190+ ?@ tid.buoy-0 tid.buoy-0
191+ ^- tide
192+ %= tid.buoy-0
193+ rocks.rul
194+ ?: =(waves.rul.tid.buoy-0 0) ~
195+ `(mul [+(rocks) waves]:rul.tid.buoy-0)
196+ ::
197+ mem
198+ ^- (jug ship dude)
199+ %- ~(run by mem.tid.buoy-0)
200+ |= =(map dude @da)
201+ ^- (set dude)
202+ ~(key by map)
203+ ==
204+ ==
205+ ==
206+ ++ versioned
207+ =< $% [%0 (map paths buoy-0)]
208+ [%1 (map paths buoy)]
209+ ==
210+ |%
211+ +$ buoy-0
212+ $: tid=$~(*tide-0 $@(aeon tide-0))
213+ alo=(unit (set ship))
214+ ==
215+ +$ tide-0
216+ $: rok=((mop aeon rock:lake) gte)
217+ wav=((mop aeon wave:lake) lte)
218+ rul=[rocks=@ud waves=@ud]
219+ mem=(mip ship dude @da)
220+ ==
221+ --
222+ --
223+ --
224+ |= [pub=versioned:pubs =bowl:gall result-type=type]
225+ => .(pub +:(update:pubs pub))
226+ =* rok ((on aeon rock:lake) gte)
227+ =* wav ((on aeon wave:lake) lte)
228+ |%
229+ ++ rule :: Set new retention policy.
230+ |= [path=paths =^rule]
231+ ^- pubs
232+ :- %1
233+ %- fall :_ (~(put by pub) path %*(. *$<(aeon buoy) rul.tid rule))
234+ %- mole |.
235+ %+ ~(jab by pub) path
236+ |= =buoy
237+ ?@ tid.buoy buoy
238+ buoy(tid (form tid.buoy(rul rule)))
239+ ::
240+ ++ wipe :: Create new rock and wipe rest.
241+ |= path=paths
242+ ^- pubs
243+ :- %1
244+ %+ ~(jab by pub) path
245+ |= =buoy
246+ ?@ tid.buoy buoy
247+ %* . buoy(tid (form tid.buoy(rul [`0 1])))
248+ rul.tid rul.tid.buoy
249+ wav.tid ~
250+ ==
251+ ++ give :: Give a wave on a path.
252+ |= [path=paths =wave:lake]
253+ ^- (quip card:agent:gall pubs)
254+ ?~ ((soft ^path) path) ~| %need-path !!
255+ =/ buoy (~(gut by pub) path *buoy)
256+ =? buoy ?=(@ tid.buoy)
257+ %*(. buoy(tid *tide) rok.tid (put:rok ~ +(tid.buoy) *rock:lake))
258+ ?> ?=(^ tid.buoy)
259+ =* tide tid.buoy
260+ =/ next=aeon +((latest tide))
261+ :- %- zing
262+ %+ turn ~(tap by mem.tide)
263+ |= [=ship =(set dude)]
264+ %+ turn ~(tap in set)
265+ |= =dude
266+ (send wave/[next wave] ship dude path)
267+ :- %1
268+ %+ ~(put by pub) path
269+ =/ last=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
270+ =. wav.tide (put:wav wav.tide next wave)
271+ =. mem.tide ~
272+ ?. =(next (add aeon.last frequency.rul.tide)) buoy
273+ buoy(tid (form tide))
274+ ::
275+ ++ fork :: Fork a pub into an empty path.
276+ |= [from=paths to=paths]
277+ ^- pubs
278+ :- %1
279+ ?< (~(has by pub) to)
280+ (~(put by pub) to (~(got by pub) from))
281+ ::
282+ ++ copy :: Fork a sub into an empty path.
283+ |= [sub=_(mk-subs lake *) from=[ship dude *] to=paths]
284+ ^- pubs
285+ :- %1
286+ ?< (~(has by pub) to)
287+ %+ ~(put by pub) to
288+ %* . *$<(aeon buoy)
289+ rok.tid (put:rok ~ [aeon rock]:(need (~(got by +:sub) from)))
290+ ==
291+ ::
292+ ++ perm :: Change permissions with gate.
293+ |= [where=(list paths) diff=$-((unit (set ship)) (unit (set ship)))]
294+ ^- (quip card:agent:gall pubs)
295+ %+ edit where
296+ |= =buoy
297+ =/ new=_alo.buoy (diff alo.buoy)
298+ ?@ tid.buoy buoy(alo new)
299+ %= buoy
300+ alo new
301+ mem.tid ?~ new mem.tid.buoy
302+ %. mem.tid.buoy
303+ ~(int by (malt (turn ~(tap in u.new) (late *(set @)))))
304+ ==
305+ ++ public (curr perm _~) :: Make list of paths public.
306+ ++ secret (curr perm _`~) :: Make list of paths secret.
307+ :: :: Block ships from paths.
308+ ++ block :: No-ops on public paths.
309+ |= [who=(list ship) whence=(list paths)]
310+ ^- (quip card:agent:gall pubs)
311+ %+ perm whence
312+ |= old=(unit (set ship))
313+ ?~ old ~ `(~(dif in u.old) (sy who))
314+ :: :: Allow ships to paths.
315+ ++ allow :: Any public paths will no-op.
316+ |= [who=(list ship) where=(list paths)]
317+ ^- (quip card:agent:gall pubs)
318+ %+ perm where
319+ |= old=(unit (set ship))
320+ ?~ old ~ `(~(gas in u.old) who)
321+ :: :: Kill a list of paths, i.e. tell
322+ ++ kill :: subs to not expect updates.
323+ (curr edit |=(=buoy buoy(tid (latest tid.buoy))))
324+ :: :: Reopen list of killed paths.
325+ ++ read :: See current published states.
326+ ^- (map paths [allowed=(unit (set ship)) =rock:lake])
327+ %- malt %+ murn ~(tap by pub)
328+ |= [path=paths =buoy]
329+ ^- (unit [paths (unit (set ship)) rock:lake])
330+ ?@ tide=tid.buoy ~
331+ :^ ~ path alo.buoy =< rock
332+ =/ snap=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
333+ %+ roll (tap:wav (lot:wav wav.tide `aeon.snap ~))
334+ |= [[=aeon =wave:lake] =_snap]
335+ ?. =(aeon +(aeon.snap)) snap
336+ [aeon (wash:lake rock.snap wave)]
337+ ::
338+ ++ apply :: Handle request from subscriber.
339+ |= req=(request:poke paths)
340+ ^- (quip card:agent:gall pubs)
341+ =/ =buoy (~(gut by pub) path.req *buoy)
342+ ?< &(?=(^ alo.buoy) !(~(has in u.alo.buoy) src.bowl))
343+ ?@ tid.buoy
344+ :_ 1/pub :_ ~
345+ (send tomb/~ src.bowl dude.req path.req)
346+ :_ 1/pub :_ ~
347+ =/ last (fall (pry:rok rok.tid.buoy) *[=key =val]:rok)
348+ (send rock/last src.bowl dude.req path.req)
349+ ::
350+ ++ tell
351+ |= [[ship=term =dude aeon=term path=paths] =sign:agent:gall]
352+ ^- (quip card:agent:gall pubs)
353+ ?> ?=(%poke-ack -.sign)
354+ ?^ p.sign `1/pub
355+ =/ =buoy (~(gut by pub) path *buoy)
356+ ?< &(?=(^ alo.buoy) !(~(has in u.alo.buoy) src.bowl))
357+ ?@ tid.buoy
358+ :_ 1/pub :_ ~
359+ (send tomb/~ src.bowl dude path)
360+ ::
361+ => .(aeon +((slav %ud aeon)))
362+ ?^ dat=(get:wav wav.tid.buoy aeon)
363+ :_ 1/pub :_ ~
364+ (send wave/[aeon u.dat] src.bowl dude path)
365+ =/ last (fall (pry:rok rok.tid.buoy) [=key =val]:rok)
366+ ?: (lte aeon key.last)
367+ :_ 1/pub :_ ~
368+ (send rock/last src.bowl dude path)
369+ :- ~
370+ :- %1
371+ %+ ~(put by pub) path
372+ %= buoy
373+ mem.tid (~(put ju mem.tid.buoy) src.bowl dude)
374+ ==
375+ ::
376+ :: Non-public facing arms below
377+ ::
378+ ++ send
379+ |= [payload=_|2:*(response:poke lake paths) =ship =dude path=paths]
380+ ^- card:agent:gall
381+ =* mark (cat 3 %sss- name:lake)
382+ =/ callback=^path
383+ ?: ?=(%tomb what.payload) (zoom tomb-response/(scot %p ship)^dude^path)
384+ (zoom scry-response/(scot %p ship)^dude^(scot %ud aeon.payload)^path)
385+ :* %pass callback
386+ %agent [ship dude]
387+ %poke mark result-type ^- (response:poke lake paths)
388+ [path dap.bowl payload]
389+ ==
390+ ++ latest
391+ |= =$@(aeon tide)
392+ ^- aeon
393+ ?@ tide tide
394+ %+ max (fall (bind (pry:rok rok.tide) head) 0)
395+ (fall (bind (ram:wav wav.tide) head) 0)
396+ ::
397+ ++ edit
398+ |= [ps=(list paths) edit=$-(buoy buoy)]
399+ ^- (quip card:agent:gall pubs)
400+ %- ~(rep in (sy ps))
401+ |= [path=paths caz=(list card:agent:gall) %1 =_pub]
402+ ?~ old=(~(get by pub) path) [caz 1/pub]
403+ =/ new=buoy (edit u.old)
404+ :_ 1/(~(put by pub) path new)
405+ %- weld :_ caz
406+ ^- (list card:agent:gall)
407+ ?@ tid.u.old ~
408+ ?@ tid.new
409+ %- zing
410+ %+ turn ~(tap by mem.tid.u.old)
411+ |= [=ship =(set dude)]
412+ (turn ~(tap in set) |=(=dude (send tomb/~ ship dude path)))
413+ ?~ alo.new ~
414+ =/ new-alo=(jug ship dude)
415+ (malt (turn ~(tap in u.alo.new) (late *(set @))))
416+ %- zing
417+ %+ turn ~(tap by (~(dif by mem.tid.u.old) new-alo))
418+ |= [=ship =(set dude)]
419+ (turn ~(tap in set) |=(=dude (send tomb/~ ship dude path)))
420+ ::
421+ ++ form
422+ |= =tide
423+ ^+ tide
424+ =/ max-rock=[=aeon =rock:lake] (fall (pry:rok rok.tide) *[key val]:rok)
425+ =/ max-wave (fall (bind (ram:wav wav.tide) head) 0)
426+ =? rok.tide :: Create new rock.
427+ ?& !=(frequency.rul.tide 0)
428+ (gte max-wave (add aeon.max-rock frequency.rul.tide))
429+ ==
430+ %+ put:rok rok.tide
431+ %+ roll (tab:wav wav.tide `aeon.max-rock max-wave)
432+ |: [*[now=aeon =wave:lake] `[prev=aeon =rock:lake]`max-rock]
433+ ~| %aeon-awry
434+ ?> =(now +(prev))
435+ [now (wash:lake rock wave)]
436+ =. rok.tide
437+ ?~ horizon.rul.tide :: Only keep genesis and latest.
438+ (gas:rok ~ (murn ~[(ram:rok rok.tide) (pry:rok rok.tide)] same))
439+ %^ lot:rok rok.tide :: Delete beyond horizon.
440+ ~
441+ (mole |.((sub max-wave (max [u.horizon frequency]:rul.tide))))
442+ ~| %rock-zero
443+ tide(wav (lot:wav wav.tide (bind (ram:rok rok.tide) |=([r=@ *] (dec r))) ~))
444+ --
445+--
base-desk/lib/story.hoonnew file+88
@@ -0,0 +1,88 @@
1+/- *story
2+!:
3+^?
4+|%
5+:: XX generalize move to hoon.hoon
6+++ dif-ju
7+ |= [a=story b=story]
8+ ^- story
9+ :: if 0 is the empty set,
10+ :: a \ 0 = a
11+ :: 0 \ b = 0 :: anything in 0 but not in b is by definition 0
12+ ::
13+ ?: =(~ a) ~
14+ :: uno := (a-b) + (merged items in both a and b) + (b-a)
15+ :: ret := (a-b) + (merged items in both a and b)
16+ :: ret = (~(int by a) uno) :: preserve only the entries whose keys are in a
17+ =/ uno=story
18+ %- (~(uno by a) b)
19+ |= [k=tako:clay proses-a=proses proses-b=proses]
20+ ^- proses
21+ (~(dif in proses-a) proses-b)
22+ ::
23+ =/ ret=story (~(int by a) uno)
24+ :: normalizing step, remove any keys with null sets,
25+ :: which can occur if proses-a == proses-b above
26+ %- ~(gas by *story)
27+ (skip ~(tap by ret) |=([k=* v=proses] ?=(~ v)))
28+::
29+++ uni-ju
30+ |= [a=story b=story]
31+ ^- story
32+ :: 0 + b = b
33+ ?: =(~ a) b
34+ %- (~(uno by a) b)
35+ |= [k=tako:clay proses-a=proses proses-b=proses]
36+ ^- proses
37+ (~(uni in proses-a) proses-b)
38+::
39+:: Canonical textual representation
40+::
41+++ chapter-to-text
42+ |= [=tako:clay =proses]
43+ ^- wain
44+ :- (crip "commit: {<`@uv`tako>}")
45+ %- zing
46+ %+ join `wain`~['|||']
47+ %+ turn ~(tap in proses)
48+ |= prose
49+ ^- wain
50+ %- to-wain:format
51+ (rap 3 title '\0a\0a' body ~)
52+::
53+:: Parsers
54+::
55+++ parse-commit-hash
56+ ;~ sfix
57+ ;~ pfix (jest 'commit: ')
58+ (cook @uv ;~(pfix (jest '0v') viz:ag))
59+ ==
60+ ::
61+ (just '\0a')
62+ ==
63+::
64+++ parse-title
65+ ;~ sfix
66+ (cook crip (star prn))
67+ (jest '\0a\0a')
68+ ==
69+::
70+++ parse-body
71+ %+ cook of-wain:format
72+ %- star
73+ ;~ less
74+ ;~(pose (jest '|||\0a') (jest '---\0a'))
75+ (cook crip ;~(sfix (star prn) (just '\0a')))
76+ ==
77+::
78+++ parse-prose ;~(plug parse-title parse-body)
79+++ parse-rest-proses (star ;~(pfix (jest '|||\0a') parse-prose))
80+++ parse-proses (cook silt ;~(plug parse-prose parse-rest-proses))
81+++ parse-chapter ;~(plug parse-commit-hash parse-proses)
82+++ parse-story
83+ (cook ~(gas by *story) (star ;~(sfix parse-chapter (jest '---\0a'))))
84+::
85+:: N.B: If conflicting messages are written individually,
86+:: instead of under the same commit, we will overwrite previous entries
87+:: with later ones due to the nature of gas:by.
88+--
base-desk/lib/strand.hoon+1−188
@@ -1,188 +1 @@
1−|%
2−+$ card card:agent:gall
3−+$ input
4− $% [%poke =cage]
5− [%sign =wire =sign-arvo]
6− [%agent =wire =sign:agent:gall]
7− [%watch =path]
8− ==
9−+$ strand-input [=bowl in=(unit input)]
10−+$ tid @tatid
11−+$ bowl
12− $: our=ship
13− src=ship
14− tid=tid
15− mom=(unit tid)
16− wex=boat:gall
17− sup=bitt:gall
18− eny=@uvJ
19− now=@da
20− byk=beak
21− ==
22−::
23−:: cards: cards to send immediately. These will go out even if a
24−:: later stage of the computation fails, so they shouldn't have
25−:: any semantic effect on the rest of the system.
26−:: Alternately, they may record an entry in contracts with
27−:: enough information to undo the effect if the computation
28−:: fails.
29−:: wait: don't move on, stay here. The next sign should come back
30−:: to this same callback.
31−:: skip: didn't expect this input; drop it down to be handled
32−:: elsewhere
33−:: cont: continue computation with new callback.
34−:: fail: abort computation; don't send effects
35−:: done: finish computation; send effects
36−::
37−++ strand-output-raw
38− |* a=mold
39− $~ [~ %done *a]
40− $: cards=(list card)
41− $= next
42− $% [%wait ~]
43− [%skip ~]
44− [%cont self=(strand-form-raw a)]
45− [%fail err=(pair term tang)]
46− [%done value=a]
47− ==
48− ==
49−::
50−++ strand-form-raw
51− |* a=mold
52− $-(strand-input (strand-output-raw a))
53−::
54−:: Abort strand computation with error message
55−::
56−++ strand-fail
57− |= err=(pair term tang)
58− |= strand-input
59− [~ %fail err]
60−::
61−:: Asynchronous transcaction monad.
62−::
63−:: Combo of four monads:
64−:: - Reader on input
65−:: - Writer on card
66−:: - Continuation
67−:: - Exception
68−::
69−++ strand
70− |* a=mold
71− |%
72− ++ output (strand-output-raw a)
73− ::
74− :: Type of an strand computation.
75− ::
76− ++ form (strand-form-raw a)
77− ::
78− :: Monadic pure. Identity computation for bind.
79− ::
80− ++ pure
81− |= arg=a
82− ^- form
83− |= strand-input
84− [~ %done arg]
85− ::
86− :: Monadic bind. Combines two computations, associatively.
87− ::
88− ++ bind
89− |* b=mold
90− |= [m-b=(strand-form-raw b) fun=$-(b form)]
91− ^- form
92− |= input=strand-input
93− =/ b-res=(strand-output-raw b)
94− (m-b input)
95− ^- output
96− :- cards.b-res
97− ?- -.next.b-res
98− %wait [%wait ~]
99− %skip [%skip ~]
100− %cont [%cont ..$(m-b self.next.b-res)]
101− %fail [%fail err.next.b-res]
102− %done [%cont (fun value.next.b-res)]
103− ==
104− ::
105− :: The strand monad must be evaluted in a particular way to maintain
106− :: its monadic character. +take:eval implements this.
107− ::
108− ++ eval
109− |%
110− :: Indelible state of a strand
111− ::
112− +$ eval-form
113− $: =form
114− ==
115− ::
116− :: Convert initial form to eval-form
117− ::
118− ++ from-form
119− |= =form
120− ^- eval-form
121− form
122− ::
123− :: The cases of results of +take
124− ::
125− +$ eval-result
126− $% [%next ~]
127− [%fail err=(pair term tang)]
128− [%done value=a]
129− ==
130− ::
131− ++ validate-mark
132− |= [in=* =mark =bowl]
133− ^- cage
134− =+ .^ =dais:clay %cb
135− /(scot %p our.bowl)/[q.byk.bowl]/(scot %da now.bowl)/[mark]
136− ==
137− =/ res (mule |.((vale.dais in)))
138− ?: ?=(%| -.res)
139− ~|(%spider-mark-fail (mean leaf+"spider: ames vale fail {<mark>}" p.res))
140− [mark p.res]
141− ::
142− :: Take a new sign and run the strand against it
143− ::
144− ++ take
145− :: cards: accumulate throughout recursion the cards to be
146− :: produced now
147− =| cards=(list card)
148− |= [=eval-form =strand-input]
149− ^- [[(list card) =eval-result] _eval-form]
150− =* take-loop $
151− =. in.strand-input
152− ?~ in.strand-input ~
153− =/ in u.in.strand-input
154− ?. ?=(%agent -.in) `in
155− ?. ?=(%fact -.sign.in) `in
156− ::
157− :- ~
158− :+ %agent wire.in
159− [%fact (validate-mark q.q.cage.sign.in p.cage.sign.in bowl.strand-input)]
160− :: run the strand callback
161− ::
162− =/ =output (form.eval-form strand-input)
163− :: add cards to cards
164− ::
165− =. cards
166− %+ welp
167− cards
168− :: XX add tag to wires?
169− cards.output
170− :: case-wise handle next steps
171− ::
172− ?- -.next.output
173− %wait [[cards %next ~] eval-form]
174− %skip [[cards %next ~] eval-form]
175− %fail [[cards %fail err.next.output] eval-form]
176− %done [[cards %done value.next.output] eval-form]
177− %cont
178− :: recurse to run continuation with initialization input
179− ::
180− %_ take-loop
181− form.eval-form self.next.output
182− strand-input [bowl.strand-input ~]
183− ==
184− ==
185− --
186− --
187−--
188−::
1+rand
base-desk/lib/strandio.hoon+171−14
@@ -73,21 +73,23 @@
7373 ^- form:m
7474 |= tin=strand-input:strand
7575 ?+ in.tin `[%skip ~]
76− ~ `[%wait ~]
76+ ~
77+ `[%wait ~]
78+ ::
7779 [~ %poke @ *]
7880 ?. =(mark p.cage.u.in.tin)
7981 `[%skip ~]
8082 `[%done q.cage.u.in.tin]
8183 ==
8284::
83−::
84−::
8585++ take-sign-arvo
8686 =/ m (strand ,[wire sign-arvo])
8787 ^- form:m
8888 |= tin=strand-input:strand
8989 ?+ in.tin `[%skip ~]
90− ~ `[%wait ~]
90+ ~
91+ `[%wait ~]
92+ ::
9193 [~ %sign *]
9294 `[%done [wire sign-arvo]:u.in.tin]
9395 ==
@@ -180,6 +182,53 @@
180182 `[%done ~]
181183 `[%fail %timer-error u.error.sign-arvo.u.in.tin]
182184 ==
185+++ take-whey
186+ |= =wire
187+ =/ m (strand ,[spar:ames fragment-size=@ud num-fragments=@ud])
188+ ^- form:m
189+ |= tin=strand-input:strand
190+ ?+ in.tin `[%skip ~]
191+ ~ `[%wait ~]
192+ ::
193+ [~ %sign * %ames %sage sage=*]
194+ ?. =(wire wire.u.in.tin)
195+ `[%skip ~]
196+ =/ =sage:mess:ames sage.sign-arvo.u.in.tin
197+ :^ ~ %done spar=p.sage
198+ ?~ q.sage
199+ [boq=13 tot=0]
200+ =< [boq tot]
201+ ;;([%whey boq=@ud tot=@ud] q.sage)
202+ ==
203+::
204+++ take-sage
205+ |= =wire
206+ =/ m (strand ,sage:mess:ames)
207+ ^- form:m
208+ |= tin=strand-input:strand
209+ ?+ in.tin `[%skip ~]
210+ ~ `[%wait ~]
211+ ::
212+ [~ %sign * %ames %sage sage=*]
213+ ?. =(wire wire.u.in.tin)
214+ `[%skip ~]
215+ `[%done sage.sign-arvo.u.in.tin]
216+ ==
217+:: XX deprecate in 409k
218+::
219+++ take-near
220+ |= =wire
221+ =/ m (strand ,[spar:ames (unit (unit page))])
222+ ^- form:m
223+ |= tin=strand-input:strand
224+ ?+ in.tin `[%skip ~]
225+ ~ `[%wait ~]
226+ ::
227+ [~ %sign * %ames %near ^ *]
228+ ?. =(wire wire.u.in.tin)
229+ `[%skip ~]
230+ `[%done +>.sign-arvo.u.in.tin]
231+ ==
183232::
184233++ take-poke-ack
185234 |= =wire
@@ -315,6 +364,78 @@
315364 ;< ~ bind:m (send-wait until)
316365 (take-wake `until)
317366::
367+++ mass
368+ =/ m (strand ,(list quac:dill))
369+ ^- form:m
370+ =/ =card:agent:gall [%pass /mass %arvo %d %mass ~]
371+ ;< ~ bind:m (send-raw-card card)
372+ ;< quz=(list quac:dill) bind:m take-meme
373+ (pure:m quz)
374+::
375+++ take-meme
376+ =/ m (strand ,(list quac:dill))
377+ ^- form:m
378+ |= tin=strand-input:strand
379+ ?+ in.tin `[%skip ~]
380+ ~ `[%wait ~]
381+ [~ %sign [%mass ~] %dill %meme *]
382+ `[%done p.sign-arvo.u.in.tin]
383+ ==
384+::
385+++ keen
386+ |= [=wire =spar:ames sec=(unit [@ @])]
387+ =/ m (strand ,~)
388+ ^- form:m
389+ (send-raw-card %pass wire %arvo %a %keen sec spar)
390+::
391+++ chum
392+ |= [=wire =spar:ames]
393+ =/ m (strand ,~)
394+ ^- form:m
395+ (send-raw-card %pass wire %arvo %a %chum spar)
396+::
397+++ keen-shut
398+ |= [=wire =spar:ames]
399+ =/ m (strand ,~)
400+ ^- form:m
401+ (send-raw-card %pass wire %keen & spar)
402+::
403+++ yawn
404+ |= [=wire =spar:ames]
405+ =/ m (strand ,~)
406+ ^- form:m
407+ (send-raw-card %pass wire %arvo %a %yawn spar)
408+::
409+++ whey
410+ |= [=wire boq=@ud =spar:ames]
411+ =/ m (strand ,~)
412+ ^- form:m
413+ :: encrypted using %chum namespace
414+ ::
415+ ;< our=@p bind:m get-our
416+ =. path.spar
417+ [%a %x '1' %$ %whey (scot %ud boq) (scot %p our) path.spar]
418+ (chum wire spar)
419+::
420+++ meta
421+ |= [=wire =spar:ames]
422+ =/ m (strand ,~)
423+ ^- form:m
424+ ?+ path.spar !!
425+ $% [%ames bone=@ %cork ~]
426+ [%ames bone=@ %sent seq=@ ~]
427+ [%flow bone=@ =dire:ames %cork ~]
428+ [?(%ames %flow) bone=@ =dire:ames ?(%sent %loads %naxp %acked) seq=@ ~]
429+ ==
430+ :: encrypted using %chum namespace
431+ ::
432+ ;< our=@p bind:m get-our
433+ %- send-raw-card
434+ :* %pass wire %arvo %a %chum
435+ spar(path [%a %x '1' %$ %meta (scot %p our) path.spar])
436+ ==
437+ ==
438+::
318439++ sleep
319440 |= for=@dr
320441 =/ m (strand ,~)
@@ -433,7 +554,6 @@
433554 %- pure:m
434555 ?~ full-file.client-response ''
435556 q.data.u.full-file.client-response
436−
437557::
438558++ fetch-cord
439559 |= url=tape
@@ -449,7 +569,7 @@
449569 =/ m (strand ,json)
450570 ^- form:m
451571 ;< =cord bind:m (fetch-cord url)
452− =/ json=(unit json) (de-json:html cord)
572+ =/ json=(unit json) (de:json:html cord)
453573 ?~ json
454574 (strand-fail %json-parse-error ~)
455575 (pure:m u.json)
@@ -474,6 +594,18 @@
474594 (pure:m ~)
475595 ?> =(%vase p.r.u.riot)
476596 (pure:m (some !<(vase q.r.u.riot)))
597+::
598+++ build-file-hard
599+ |= [[=ship =desk =case] =spur]
600+ =* arg +<
601+ =/ m (strand ,vase)
602+ ^- form:m
603+ ;< =riot:clay
604+ bind:m
605+ (warp ship desk ~ %sing %a case spur)
606+ ?> ?=(^ riot)
607+ ?> ?=(%vase p.r.u.riot)
608+ (pure:m !<(vase q.r.u.riot))
477609:: +build-mark: build a mark definition to a $dais
478610::
479611++ build-mark
@@ -537,7 +669,7 @@
537669 (take-writ /warp)
538670::
539671++ read-file
540− |= [[=ship =desk =case:clay] =spur]
672+ |= [[=ship =desk =case] =spur]
541673 =* arg +<
542674 =/ m (strand ,cage)
543675 ;< =riot:clay bind:m (warp ship desk ~ %sing %x case spur)
@@ -546,13 +678,14 @@
546678 (pure:m r.u.riot)
547679::
548680++ check-for-file
549− |= [[=ship =desk =case:clay] =spur]
681+ |= [[=ship =desk =case] =spur]
550682 =/ m (strand ,?)
551− ;< =riot:clay bind:m (warp ship desk ~ %sing %x case spur)
552− (pure:m ?=(^ riot))
683+ ;< =riot:clay bind:m (warp ship desk ~ %sing %u case spur)
684+ ?> ?=(^ riot)
685+ (pure:m !<(? q.r.u.riot))
553686::
554687++ list-tree
555− |= [[=ship =desk =case:clay] =spur]
688+ |= [[=ship =desk =case] =spur]
556689 =* arg +<
557690 =/ m (strand ,(list path))
558691 ;< =riot:clay bind:m (warp ship desk ~ %sing %t case spur)
@@ -560,6 +693,14 @@
560693 (strand-fail %list-tree >arg< ~)
561694 (pure:m !<((list path) q.r.u.riot))
562695::
696+++ list-desk
697+ |= [[=ship =desk =case] =spur]
698+ =* arg +<
699+ =/ m (strand arch)
700+ ;< =riot:clay bind:m (warp ship desk ~ %sing %y case spur)
701+ ?~ riot
702+ (strand-fail %list-desk >arg< ~)
703+ (pure:m !<(arch q.r.u.riot))
563704:: Take Clay read result
564705::
565706++ take-writ
@@ -586,6 +727,23 @@
586727 (poke [who %hood] %helm-hi !>(~))
587728 (pure:m ~)
588729::
730+++ eval-hoon
731+ |= [gen=hoon bez=(list beam)]
732+ =/ m (strand ,vase)
733+ ^- form:m
734+ =/ sut=vase !>(..zuse)
735+ |-
736+ ?~ bez
737+ (pure:m (slap sut gen))
738+ ;< vax=vase bind:m (build-file-hard i.bez)
739+ $(bez t.bez, sut (slop vax sut))
740+::
741+++ send-thread
742+ |= [=bear:khan =shed:khan =wire]
743+ =/ m (strand ,~)
744+ ^- form:m
745+ (send-raw-card %pass wire %arvo %k %lard bear shed)
746+::
589747:: Queue on skip, try next on fail %ignore
590748::
591749++ main-loop
@@ -734,7 +892,7 @@
734892 ;< =bowl:spider bind:m get-bowl
735893 =/ tid
736894 (scot %ta (cat 3 (cat 3 'strand_' file) (scot %uv (sham file eny.bowl))))
737− =/ poke-vase !>([`tid.bowl `tid beak file args])
895+ =/ poke-vase !>(`start-args:spider`[`tid.bowl `tid beak file args])
738896 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
739897 ;< ~ bind:m (sleep ~s0) :: wait for thread to start
740898 (pure:m tid)
@@ -748,10 +906,9 @@
748906 ^- form:m
749907 ;< =bowl:spider bind:m get-bowl
750908 =/ tid (scot %ta (cat 3 'strand_' (scot %uv (sham file eny.bowl))))
751− =/ poke-vase !>([`tid.bowl `tid file args])
909+ =/ poke-vase !>(`start-args:spider`[`tid.bowl `tid byk.bowl file args])
752910 ;< ~ bind:m (watch-our /awaiting/[tid] %spider /thread-result/[tid])
753911 ;< ~ bind:m (poke-our %spider %spider-start poke-vase)
754− ;< ~ bind:m (sleep ~s0) :: wait for thread to start
755912 ;< =cage bind:m (take-fact /awaiting/[tid])
756913 ;< ~ bind:m (take-kick /awaiting/[tid])
757914 ?+ p.cage ~|([%strange-thread-result p.cage file tid] !!)
base-desk/lib/test.hoon+55−2
@@ -13,7 +13,7 @@
1313 %+ weld result
1414 ^- tang
1515 :~ [%palm [": " ~ ~ ~] [leaf+"expected" (sell expected) ~]]
16− [%palm [": " ~ ~ ~] [leaf+"actual" (sell actual) ~]]
16+ [%palm [": " ~ ~ ~] [leaf+"actual " (sell actual) ~]]
1717 ==
1818 ::
1919 =? result !(~(nest ut p.actual) | p.expected)
@@ -38,7 +38,60 @@
3838 =/ b (mule a)
3939 ?- -.b
4040 %| ~
41− %& [leaf+"expected failure - succeeded" ~]
41+ %& ['expected failure - succeeded' ~]
42+ ==
43+:: +expect-success: kicks a trap, expecting success; returns trace on failure
44+::
45+++ expect-success
46+ |= a=(trap)
47+ ^- tang
48+ =/ b (mule a)
49+ ?- -.b
50+ %& ~
51+ %| ['expected success - failed' p.b]
52+ ==
53+::
54+:: +expect-fail-message: kicks a trap, expecting crash, compares the resulting error message
55+++ expect-fail-message
56+ |= [msg=@t a=(trap)]
57+ ^- tang
58+ =/ b (mule a)
59+ ?- -.b
60+ %| |^
61+ =/ =tang (flatten +.b)
62+ ?: ?=(^ (find (trip msg) tang))
63+ ~
64+ ['expected error message - not found' ~]
65+ ++ flatten
66+ |= tang=(list tank)
67+ =| res=tape
68+ |- ^- tape
69+ ?~ tang res
70+ $(tang t.tang, res (weld ~(ram re i.tang) res))
71+ --
72+ %& ['expected failure - succeeded' ~]
73+ ==
74+:: $a-test-chain: a sequence of tests to be run
75+::
76+:: NB: arms shouldn't start with `test-` so that `-test % ~` runs
77+::
78++$ a-test-chain
79+ $_
80+ |?
81+ ?: =(0 0)
82+ [%& p=*tang]
83+ [%| p=[tang=*tang next=^?(..$)]]
84+:: +run-chain: run a sequence of tests, stopping at first failure
85+::
86+++ run-chain
87+ |= seq=a-test-chain
88+ ^- tang
89+ =/ res $:seq
90+ ?- -.res
91+ %& p.res
92+ %| ?. =(~ tang.p.res)
93+ tang.p.res
94+ $(seq next.p.res)
4295 ==
4396:: +category: prepends a name to an error result; passes successes unchanged
4497::
base-desk/lib/tiny.hoonnew file+670
@@ -0,0 +1,670 @@
1+!.
2+=> %a50
3+~% %a.50 ~ ~
4+|%
5+:: Types
6+::
7++$ ship @p
8++$ life @ud
9++$ rift @ud
10++$ pass @
11++$ bloq @
12++$ step _`@u`1
13++$ bite $@(bloq [=bloq =step])
14++$ octs [p=@ud q=@]
15++$ mold $~(* $-(* *))
16+++ unit |$ [item] $@(~ [~ u=item])
17+++ list |$ [item] $@(~ [i=item t=(list item)])
18+++ lest |$ [item] [i=item t=(list item)]
19+++ tree |$ [node] $@(~ [n=node l=(tree node) r=(tree node)])
20+++ pair |$ [head tail] [p=head q=tail]
21+++ map
22+ |$ [key value]
23+ $| (tree (pair key value))
24+ |=(a=(tree (pair)) ?:(=(~ a) & ~(apt by a)))
25+::
26+++ set
27+ |$ [item]
28+ $| (tree item)
29+ |=(a=(tree) ?:(=(~ a) & ~(apt in a)))
30+::
31+++ jug |$ [key value] (map key (set value))
32+::
33+:: Bits
34+::
35+++ dec :: decrement
36+ ~/ %dec
37+ |= a=@
38+ ~_ leaf+"decrement-underflow"
39+ ?< =(0 a)
40+ =+ b=0
41+ |- ^- @
42+ ?: =(a +(b)) b
43+ $(b +(b))
44+::
45+++ add :: plus
46+ ~/ %add
47+ |= [a=@ b=@]
48+ ^- @
49+ ?: =(0 a) b
50+ $(a (dec a), b +(b))
51+::
52+++ sub :: subtract
53+ ~/ %sub
54+ |= [a=@ b=@]
55+ ~_ leaf+"subtract-underflow"
56+ :: difference
57+ ^- @
58+ ?: =(0 b) a
59+ $(a (dec a), b (dec b))
60+::
61+++ mul :: multiply
62+ ~/ %mul
63+ |: [a=`@`1 b=`@`1]
64+ ^- @
65+ =+ c=0
66+ |-
67+ ?: =(0 a) c
68+ $(a (dec a), c (add b c))
69+::
70+++ div :: divide
71+ ~/ %div
72+ |: [a=`@`1 b=`@`1]
73+ ^- @
74+ ~_ leaf+"divide-by-zero"
75+ ?< =(0 b)
76+ =+ c=0
77+ |-
78+ ?: (lth a b) c
79+ $(a (sub a b), c +(c))
80+::
81+++ dvr :: divide w/remainder
82+ ~/ %dvr
83+ |: [a=`@`1 b=`@`1]
84+ ^- [p=@ q=@]
85+ [(div a b) (mod a b)]
86+::
87+++ mod :: modulus
88+ ~/ %mod
89+ |: [a=`@`1 b=`@`1]
90+ ^- @
91+ ?< =(0 b)
92+ (sub a (mul b (div a b)))
93+::
94+++ bex :: binary exponent
95+ ~/ %bex
96+ |= a=bloq
97+ ^- @
98+ ?: =(0 a) 1
99+ (mul 2 $(a (dec a)))
100+::
101+++ lsh :: left-shift
102+ ~/ %lsh
103+ |= [a=bite b=@]
104+ =/ [=bloq =step] ?^(a a [a *step])
105+ (mul b (bex (mul (bex bloq) step)))
106+::
107+++ rsh :: right-shift
108+ ~/ %rsh
109+ |= [a=bite b=@]
110+ =/ [=bloq =step] ?^(a a [a *step])
111+ (div b (bex (mul (bex bloq) step)))
112+::
113+++ con :: binary or
114+ ~/ %con
115+ |= [a=@ b=@]
116+ =+ [c=0 d=0]
117+ |- ^- @
118+ ?: ?&(=(0 a) =(0 b)) d
119+ %= $
120+ a (rsh 0 a)
121+ b (rsh 0 b)
122+ c +(c)
123+ d %+ add d
124+ %+ lsh [0 c]
125+ ?& =(0 (end 0 a))
126+ =(0 (end 0 b))
127+ ==
128+ ==
129+::
130+++ dis :: binary and
131+ ~/ %dis
132+ |= [a=@ b=@]
133+ =| [c=@ d=@]
134+ |- ^- @
135+ ?: ?|(=(0 a) =(0 b)) d
136+ %= $
137+ a (rsh 0 a)
138+ b (rsh 0 b)
139+ c +(c)
140+ d %+ add d
141+ %+ lsh [0 c]
142+ ?| =(0 (end 0 a))
143+ =(0 (end 0 b))
144+ ==
145+ ==
146+::
147+++ mix :: binary xor
148+ ~/ %mix
149+ |= [a=@ b=@]
150+ ^- @
151+ =+ [c=0 d=0]
152+ |-
153+ ?: ?&(=(0 a) =(0 b)) d
154+ %= $
155+ a (rsh 0 a)
156+ b (rsh 0 b)
157+ c +(c)
158+ d (add d (lsh [0 c] =((end 0 a) (end 0 b))))
159+ ==
160+::
161+++ lth :: less
162+ ~/ %lth
163+ |= [a=@ b=@]
164+ ^- ?
165+ ?& !=(a b)
166+ |-
167+ ?| =(0 a)
168+ ?& !=(0 b)
169+ $(a (dec a), b (dec b))
170+ == == ==
171+::
172+++ lte :: less or equal
173+ ~/ %lte
174+ |= [a=@ b=@]
175+ |(=(a b) (lth a b))
176+::
177+++ gte :: greater or equal
178+ ~/ %gte
179+ |= [a=@ b=@]
180+ ^- ?
181+ !(lth a b)
182+::
183+++ gth :: greater
184+ ~/ %gth
185+ |= [a=@ b=@]
186+ ^- ?
187+ !(lte a b)
188+::
189+++ swp :: naive rev bloq order
190+ ~/ %swp
191+ |= [a=bloq b=@]
192+ (rep a (flop (rip a b)))
193+::
194+++ met :: measure
195+ ~/ %met
196+ |= [a=bloq b=@]
197+ ^- @
198+ =+ c=0
199+ |-
200+ ?: =(0 b) c
201+ $(b (rsh a b), c +(c))
202+::
203+++ end :: tail
204+ ~/ %end
205+ |= [a=bite b=@]
206+ =/ [=bloq =step] ?^(a a [a *step])
207+ (mod b (bex (mul (bex bloq) step)))
208+::
209+++ cat :: concatenate
210+ ~/ %cat
211+ |= [a=bloq b=@ c=@]
212+ (add (lsh [a (met a b)] c) b)
213+::
214+++ cut :: slice
215+ ~/ %cut
216+ |= [a=bloq [b=step c=step] d=@]
217+ (end [a c] (rsh [a b] d))
218+::
219+++ can :: assemble
220+ ~/ %can
221+ |= [a=bloq b=(list [p=step q=@])]
222+ ^- @
223+ ?~ b 0
224+ (add (end [a p.i.b] q.i.b) (lsh [a p.i.b] $(b t.b)))
225+::
226+++ cad :: assemble specific
227+ ~/ %cad
228+ |= [a=bloq b=(list [p=step q=@])]
229+ ^- [=step @]
230+ :_ (can a b)
231+ |-
232+ ?~ b
233+ 0
234+ (add p.i.b $(b t.b))
235+::
236+++ rep :: assemble fixed
237+ ~/ %rep
238+ |= [a=bite b=(list @)]
239+ =/ [=bloq =step] ?^(a a [a *step])
240+ =| i=@ud
241+ |- ^- @
242+ ?~ b 0
243+ %+ add $(i +(i), b t.b)
244+ (lsh [bloq (mul step i)] (end [bloq step] i.b))
245+::
246+++ rip :: disassemble
247+ ~/ %rip
248+ |= [a=bite b=@]
249+ ^- (list @)
250+ ?: =(0 b) ~
251+ [(end a b) $(b (rsh a b))]
252+::
253+::
254+:: Lists
255+::
256+++ lent :: length
257+ ~/ %lent
258+ |= a=(list)
259+ ^- @
260+ =+ b=0
261+ |-
262+ ?~ a b
263+ $(a t.a, b +(b))
264+::
265+++ slag :: suffix
266+ ~/ %slag
267+ |* [a=@ b=(list)]
268+ |- ^+ b
269+ ?: =(0 a) b
270+ ?~ b ~
271+ $(b t.b, a (dec a))
272+::
273+++ snag :: index
274+ ~/ %snag
275+ |* [a=@ b=(list)]
276+ |- ^+ ?>(?=(^ b) i.b)
277+ ?~ b
278+ ~_ leaf+"snag-fail"
279+ !!
280+ ?: =(0 a) i.b
281+ $(b t.b, a (dec a))
282+::
283+++ homo :: homogenize
284+ |* a=(list)
285+ ^+ =< $
286+ |@ ++ $ ?:(*? ~ [i=(snag 0 a) t=$])
287+ --
288+ a
289+::
290+++ flop :: reverse
291+ ~/ %flop
292+ |* a=(list)
293+ => .(a (homo a))
294+ ^+ a
295+ =+ b=`_a`~
296+ |-
297+ ?~ a b
298+ $(a t.a, b [i.a b])
299+::
300+++ welp :: concatenate
301+ ~/ %welp
302+ =| [* *]
303+ |@
304+ ++ $
305+ ?~ +<-
306+ +<-(. +<+)
307+ +<-(+ $(+<- +<->))
308+ --
309+::
310+++ reap :: replicate
311+ ~/ %reap
312+ |* [a=@ b=*]
313+ |- ^- (list _b)
314+ ?~ a ~
315+ [b $(a (dec a))]
316+::
317+:: Modular arithmetic
318+::
319+++ fe :: modulo bloq
320+ |_ a=bloq
321+ ++ rol |= [b=bloq c=@ d=@] ^- @ :: roll left
322+ =+ e=(sit d)
323+ =+ f=(bex (sub a b))
324+ =+ g=(mod c f)
325+ (sit (con (lsh [b g] e) (rsh [b (sub f g)] e)))
326+ ++ sum |=([b=@ c=@] (sit (add b c))) :: wrapping add
327+ ++ sit |=(b=@ (end a b)) :: enforce modulo
328+ --
329+::
330+:: Hashes
331+::
332+++ muk :: standard murmur3
333+ ~% %muk ..muk ~
334+ =+ ~(. fe 5)
335+ |= [syd=@ len=@ key=@]
336+ =. syd (end 5 syd)
337+ =/ pad (sub len (met 3 key))
338+ =/ data (welp (rip 3 key) (reap pad 0))
339+ =/ nblocks (div len 4) :: intentionally off-by-one
340+ =/ h1 syd
341+ =+ [c1=0xcc9e.2d51 c2=0x1b87.3593]
342+ =/ blocks (rip 5 key)
343+ =/ i nblocks
344+ =. h1 =/ hi h1 |-
345+ ?: =(0 i) hi
346+ =/ k1 (snag (sub nblocks i) blocks) :: negative array index
347+ =. k1 (sit (mul k1 c1))
348+ =. k1 (rol 0 15 k1)
349+ =. k1 (sit (mul k1 c2))
350+ =. hi (mix hi k1)
351+ =. hi (rol 0 13 hi)
352+ =. hi (sum (sit (mul hi 5)) 0xe654.6b64)
353+ $(i (dec i))
354+ =/ tail (slag (mul 4 nblocks) data)
355+ =/ k1 0
356+ =/ tlen (dis len 3)
357+ =. h1
358+ ?+ tlen h1 :: fallthrough switch
359+ %3 =. k1 (mix k1 (lsh [0 16] (snag 2 tail)))
360+ =. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
361+ =. k1 (mix k1 (snag 0 tail))
362+ =. k1 (sit (mul k1 c1))
363+ =. k1 (rol 0 15 k1)
364+ =. k1 (sit (mul k1 c2))
365+ (mix h1 k1)
366+ %2 =. k1 (mix k1 (lsh [0 8] (snag 1 tail)))
367+ =. k1 (mix k1 (snag 0 tail))
368+ =. k1 (sit (mul k1 c1))
369+ =. k1 (rol 0 15 k1)
370+ =. k1 (sit (mul k1 c2))
371+ (mix h1 k1)
372+ %1 =. k1 (mix k1 (snag 0 tail))
373+ =. k1 (sit (mul k1 c1))
374+ =. k1 (rol 0 15 k1)
375+ =. k1 (sit (mul k1 c2))
376+ (mix h1 k1)
377+ ==
378+ =. h1 (mix h1 len)
379+ |^ (fmix32 h1)
380+ ++ fmix32
381+ |= h=@
382+ =. h (mix h (rsh [0 16] h))
383+ =. h (sit (mul h 0x85eb.ca6b))
384+ =. h (mix h (rsh [0 13] h))
385+ =. h (sit (mul h 0xc2b2.ae35))
386+ =. h (mix h (rsh [0 16] h))
387+ h
388+ --
389+::
390+++ mug :: mug with murmur3
391+ ~/ %mug
392+ |= a=*
393+ |^ ?@ a (mum 0xcafe.babe 0x7fff a)
394+ =/ b (cat 5 $(a -.a) $(a +.a))
395+ (mum 0xdead.beef 0xfffe b)
396+ ::
397+ ++ mum
398+ |= [syd=@uxF fal=@F key=@]
399+ =/ wyd (met 3 key)
400+ =| i=@ud
401+ |- ^- @F
402+ ?: =(8 i) fal
403+ =/ haz=@F (muk syd wyd key)
404+ =/ ham=@F (mix (rsh [0 31] haz) (end [0 31] haz))
405+ ?.(=(0 ham) ham $(i +(i), syd +(syd)))
406+ --
407+::
408+++ gor :: mug order
409+ ~/ %gor
410+ |= [a=* b=*]
411+ ^- ?
412+ =+ [c=(mug a) d=(mug b)]
413+ ?: =(c d)
414+ (dor a b)
415+ (lth c d)
416+::
417+++ mor :: more mug order
418+ ~/ %mor
419+ |= [a=* b=*]
420+ ^- ?
421+ =+ [c=(mug (mug a)) d=(mug (mug b))]
422+ ?: =(c d)
423+ (dor a b)
424+ (lth c d)
425+::
426+++ dor :: tree order
427+ ~/ %dor
428+ |= [a=* b=*]
429+ ^- ?
430+ ?: =(a b) &
431+ ?. ?=(@ a)
432+ ?: ?=(@ b) |
433+ ?: =(-.a -.b)
434+ $(a +.a, b +.b)
435+ $(a -.a, b -.b)
436+ ?. ?=(@ b) &
437+ (lth a b)
438+::
439+++ por :: parent order
440+ ~/ %por
441+ |= [a=@p b=@p]
442+ ^- ?
443+ ?: =(a b) &
444+ =| i=@
445+ |-
446+ ?: =(i 2)
447+ :: second two bytes
448+ (lte a b)
449+ :: first two bytes
450+ =+ [c=(end 3 a) d=(end 3 b)]
451+ ?: =(c d)
452+ $(a (rsh 3 a), b (rsh 3 b), i +(i))
453+ (lth c d)
454+::
455+:: Maps
456+::
457+++ by
458+ ~/ %by
459+ =| a=(tree (pair)) :: (map)
460+ =* node ?>(?=(^ a) n.a)
461+ |@
462+ ++ get
463+ ~/ %get
464+ |* b=*
465+ => .(b `_?>(?=(^ a) p.n.a)`b)
466+ |- ^- (unit _?>(?=(^ a) q.n.a))
467+ ?~ a
468+ ~
469+ ?: =(b p.n.a)
470+ `q.n.a
471+ ?: (gor b p.n.a)
472+ $(a l.a)
473+ $(a r.a)
474+ ::
475+ ++ put
476+ ~/ %put
477+ |* [b=* c=*]
478+ |- ^+ a
479+ ?~ a
480+ [[b c] ~ ~]
481+ ?: =(b p.n.a)
482+ ?: =(c q.n.a)
483+ a
484+ a(n [b c])
485+ ?: (gor b p.n.a)
486+ =+ d=$(a l.a)
487+ ?> ?=(^ d)
488+ ?: (mor p.n.a p.n.d)
489+ a(l d)
490+ d(r a(l r.d))
491+ =+ d=$(a r.a)
492+ ?> ?=(^ d)
493+ ?: (mor p.n.a p.n.d)
494+ a(r d)
495+ d(l a(r l.d))
496+ ::
497+ ++ del
498+ ~/ %del
499+ |* b=*
500+ |- ^+ a
501+ ?~ a
502+ ~
503+ ?. =(b p.n.a)
504+ ?: (gor b p.n.a)
505+ a(l $(a l.a))
506+ a(r $(a r.a))
507+ |- ^- [$?(~ _a)]
508+ ?~ l.a r.a
509+ ?~ r.a l.a
510+ ?: (mor p.n.l.a p.n.r.a)
511+ l.a(r $(l.a r.l.a))
512+ r.a(l $(r.a l.r.a))
513+ ::
514+ ++ apt
515+ =< $
516+ ~/ %apt
517+ =| [l=(unit) r=(unit)]
518+ |. ^- ?
519+ ?~ a &
520+ ?& ?~(l & &((gor p.n.a u.l) !=(p.n.a u.l)))
521+ ?~(r & &((gor u.r p.n.a) !=(u.r p.n.a)))
522+ ?~ l.a &
523+ &((mor p.n.a p.n.l.a) !=(p.n.a p.n.l.a) $(a l.a, l `p.n.a))
524+ ?~ r.a &
525+ &((mor p.n.a p.n.r.a) !=(p.n.a p.n.r.a) $(a r.a, r `p.n.a))
526+ ==
527+ --
528+::
529+++ on :: ordered map
530+ ~/ %on
531+ |* [key=mold val=mold]
532+ => |%
533+ +$ item [key=key val=val]
534+ --
535+ ::
536+ ~% %comp +>+ ~
537+ |= compare=$-([key key] ?)
538+ ~% %core + ~
539+ |%
540+ ::
541+ ++ apt
542+ ~/ %apt
543+ |= a=(tree item)
544+ =| [l=(unit key) r=(unit key)]
545+ |- ^- ?
546+ ?~ a %.y
547+ ?& ?~(l %.y (compare key.n.a u.l))
548+ ?~(r %.y (compare u.r key.n.a))
549+ ?~(l.a %.y &((mor key.n.a key.n.l.a) $(a l.a, l `key.n.a)))
550+ ?~(r.a %.y &((mor key.n.a key.n.r.a) $(a r.a, r `key.n.a)))
551+ ==
552+ ::
553+ ++ get
554+ ~/ %get
555+ |= [a=(tree item) b=key]
556+ ^- (unit val)
557+ ?~ a ~
558+ ?: =(b key.n.a)
559+ `val.n.a
560+ ?: (compare b key.n.a)
561+ $(a l.a)
562+ $(a r.a)
563+ ::
564+ ++ has
565+ ~/ %has
566+ |= [a=(tree item) b=key]
567+ ^- ?
568+ !=(~ (get a b))
569+ ::
570+ ++ put
571+ ~/ %put
572+ |= [a=(tree item) =key =val]
573+ ^- (tree item)
574+ ?~ a [n=[key val] l=~ r=~]
575+ ?: =(key.n.a key) a(val.n val)
576+ ?: (compare key key.n.a)
577+ =/ l $(a l.a)
578+ ?> ?=(^ l)
579+ ?: (mor key.n.a key.n.l)
580+ a(l l)
581+ l(r a(l r.l))
582+ =/ r $(a r.a)
583+ ?> ?=(^ r)
584+ ?: (mor key.n.a key.n.r)
585+ a(r r)
586+ r(l a(r l.r))
587+ --
588+::
589+:: Sets
590+::
591+++ in
592+ ~/ %in
593+ =| a=(tree) :: (set)
594+ |@
595+ ++ put
596+ ~/ %put
597+ |* b=*
598+ |- ^+ a
599+ ?~ a
600+ [b ~ ~]
601+ ?: =(b n.a)
602+ a
603+ ?: (gor b n.a)
604+ =+ c=$(a l.a)
605+ ?> ?=(^ c)
606+ ?: (mor n.a n.c)
607+ a(l c)
608+ c(r a(l r.c))
609+ =+ c=$(a r.a)
610+ ?> ?=(^ c)
611+ ?: (mor n.a n.c)
612+ a(r c)
613+ c(l a(r l.c))
614+ ::
615+ ++ del
616+ ~/ %del
617+ |* b=*
618+ |- ^+ a
619+ ?~ a
620+ ~
621+ ?. =(b n.a)
622+ ?: (gor b n.a)
623+ a(l $(a l.a))
624+ a(r $(a r.a))
625+ |- ^- [$?(~ _a)]
626+ ?~ l.a r.a
627+ ?~ r.a l.a
628+ ?: (mor n.l.a n.r.a)
629+ l.a(r $(l.a r.l.a))
630+ r.a(l $(r.a l.r.a))
631+ ::
632+ ++ apt
633+ =< $
634+ ~/ %apt
635+ =| [l=(unit) r=(unit)]
636+ |. ^- ?
637+ ?~ a &
638+ ?& ?~(l & (gor n.a u.l))
639+ ?~(r & (gor u.r n.a))
640+ ?~(l.a & ?&((mor n.a n.l.a) $(a l.a, l `n.a)))
641+ ?~(r.a & ?&((mor n.a n.r.a) $(a r.a, r `n.a)))
642+ ==
643+ --
644+::
645+:: Jugs
646+::
647+++ ju
648+ =| a=(tree (pair * (tree))) :: (jug)
649+ |@
650+ ++ get
651+ |* b=*
652+ =+ c=(~(get by a) b)
653+ ?~(c ~ u.c)
654+ ::
655+ ++ del
656+ |* [b=* c=*]
657+ ^+ a
658+ =+ d=(get b)
659+ =+ e=(~(del in d) c)
660+ ?~ e
661+ (~(del by a) b)
662+ (~(put by a) b e)
663+ ::
664+ ++ put
665+ |* [b=* c=*]
666+ ^+ a
667+ =+ d=(get b)
668+ (~(put by a) b (~(put in d) c))
669+ --
670+--
base-desk/lib/verb.hoon+88−11
@@ -1,6 +1,6 @@
11:: Print what your agent is doing.
22::
3−/- verb
3+/- *verb
44::
55|= [loud=? =agent:gall]
66=| bowl-print=_|
@@ -14,7 +14,10 @@
1414 ^- (quip card:agent:gall agent:gall)
1515 %- (print bowl |.("{<dap.bowl>}: on-init"))
1616 =^ cards agent on-init:ag
17− [[(emit-event %on-init ~) cards] this]
17+ :_ this
18+ :_ :_ cards
19+ (emit-event %on-init ~)
20+ (emit-event-plus bowl [%on-init ~] cards)
1821::
1922++ on-save
2023 ^- vase
@@ -26,7 +29,10 @@
2629 ^- (quip card:agent:gall agent:gall)
2730 %- (print bowl |.("{<dap.bowl>}: on-load"))
2831 =^ cards agent (on-load:ag old-state)
29− [[(emit-event %on-load ~) cards] this]
32+ :_ this
33+ :_ :_ cards
34+ (emit-event %on-load ~)
35+ (emit-event-plus bowl [%on-load ~] cards)
3036::
3137++ on-poke
3238 |= [=mark =vase]
@@ -38,17 +44,23 @@
3844 %bowl `this(bowl-print !bowl-print)
3945 ==
4046 =^ cards agent (on-poke:ag mark vase)
41− [[(emit-event %on-poke mark) cards] this]
47+ :_ this
48+ :_ :_ cards
49+ (emit-event %on-poke mark)
50+ (emit-event-plus bowl [%on-poke mark (mug q.vase)] cards)
4251::
4352++ on-watch
4453 |= =path
4554 ^- (quip card:agent:gall agent:gall)
4655 %- (print bowl |.("{<dap.bowl>}: on-watch on path {<path>}"))
4756 =^ cards agent
48− ?: ?=([%verb %events ~] path)
57+ ?: ?=([%verb ?(%events %events-plus) ~] path)
4958 [~ agent]
5059 (on-watch:ag path)
51− [[(emit-event %on-watch path) cards] this]
60+ :_ this
61+ :_ :_ cards
62+ (emit-event %on-watch path)
63+ (emit-event-plus bowl [%on-watch path] cards)
5264::
5365++ on-leave
5466 |= =path
@@ -57,7 +69,10 @@
5769 ?: ?=([%verb %event ~] path)
5870 [~ this]
5971 =^ cards agent (on-leave:ag path)
60− [[(emit-event %on-leave path) cards] this]
72+ :_ this
73+ :_ :_ cards
74+ (emit-event %on-leave path)
75+ (emit-event-plus bowl [%on-leave path] cards)
6176::
6277++ on-peek
6378 |= =path
@@ -70,7 +85,17 @@
7085 ^- (quip card:agent:gall agent:gall)
7186 %- (print bowl |.("{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}"))
7287 =^ cards agent (on-agent:ag wire sign)
73− [[(emit-event %on-agent wire -.sign) cards] this]
88+ :_ this
89+ :_ :_ cards
90+ (emit-event %on-agent wire -.sign)
91+ =; =^sign
92+ (emit-event-plus bowl [%on-agent wire sign] cards)
93+ ?- -.sign
94+ %poke-ack [%poke-ack ?=(~ p.sign)]
95+ %watch-ack [%watch-ack ?=(~ p.sign)]
96+ %kick [%kick ~]
97+ %fact [%fact p.cage.sign (mug q.q.cage.sign)]
98+ ==
7499::
75100++ on-arvo
76101 |= [=wire =sign-arvo]
@@ -78,14 +103,20 @@
78103 %- %+ print bowl |.
79104 "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}"
80105 =^ cards agent (on-arvo:ag wire sign-arvo)
81− [[(emit-event %on-arvo wire [- +<]:sign-arvo) cards] this]
106+ :_ this
107+ :_ :_ cards
108+ (emit-event %on-arvo wire [- +<]:sign-arvo)
109+ (emit-event-plus bowl [%on-arvo wire [- +<]:sign-arvo] cards)
82110::
83111++ on-fail
84112 |= [=term =tang]
85113 ^- (quip card:agent:gall agent:gall)
86114 %- (print bowl |.("{<dap.bowl>}: on-fail with term {<term>}"))
87115 =^ cards agent (on-fail:ag term tang)
88− [[(emit-event %on-fail term) cards] this]
116+ :_ this
117+ :_ :_ cards
118+ (emit-event %on-fail term)
119+ (emit-event-plus bowl [%on-fail term] cards)
89120--
90121::
91122++ print
@@ -99,7 +130,53 @@
99130 same
100131::
101132++ emit-event
102− |= =event:verb
133+ |= =event
103134 ^- card:agent:gall
104135 [%give %fact ~[/verb/events] %verb-event !>(event)]
136+::
137+++ emit-event-plus
138+ |= [=bowl:gall =cause cards=(list card:agent:gall)]
139+ ^- card:agent:gall
140+ =; event=event-plus
141+ [%give %fact ~[/verb/events-plus] %verb-event-plus !>(event)]
142+ =- [act.bowl now.bowl src.bowl sap.bowl cause -]
143+ %+ turn cards
144+ |= =card:agent:gall
145+ ^- effect
146+ ::TODO for %fact, %kick, could calculate how many ships affected
147+ ?- card
148+ [%pass * %agent * ?(%poke %poke-as) *]
149+ =, q.card
150+ =/ =cage ?-(-.task.q.card %poke cage.task, %poke-as [mark.task q.cage.task])
151+ [%poke p.card [ship name] p.cage `@`(mug q.q.cage)]
152+ ::
153+ [%pass * %agent * ?(%watch %watch-as) *]
154+ =, q.card
155+ =/ =path ?-(-.task.q.card %watch path.task, %watch-as path.task)
156+ [%watch p.card [ship name] path]
157+ ::
158+ [%pass * %agent * %leave *]
159+ =, q.card
160+ [%leave p.card [ship name]]
161+ ::
162+ [%give %fact *]
163+ =, p.card
164+ [%fact paths p.cage (mug q.q.cage)]
165+ ::
166+ [%give %kick *]
167+ [%kick paths.p.card]
168+ ::
169+ [%give ?(%poke-ack %watch-ack) *]
170+ ~| %explicit-ack
171+ !! :: shouldn't be given explicitly
172+ ::
173+ [%pass * %arvo *]
174+ [%arvo p.card -.q.card +<.q.card]
175+ ::
176+ [%pass *]
177+ [%arvo p.card %$ -.q.card]
178+ ::
179+ [%slip *]
180+ $(card [%pass //slip p.card])
181+ ==
105182--
base-desk/mar/belt.hoon+3−12
@@ -1,5 +1,7 @@
11:: belt: runtime belt structure
22::
3+/+ dill
4+::
35|_ =belt:dill
46++ grad %noun
57:: +grab: convert from
@@ -7,18 +9,7 @@
79++ grab
810 |%
911 ++ noun belt:dill
10− ++ json
11− ^- $-(^json belt:dill)
12− =, dejs:format
13− %- of
14− :~ aro+(su (perk %d %l %r %u ~))
15− bac+ul
16− ctl+(cu taft so)
17− del+ul
18− met+(cu taft so)
19− ret+ul
20− txt+(ar (cu taft so))
21− ==
12+ ++ json belt:dejs:dill
2213 --
2314:: +grow: convert to
2415::
base-desk/mar/blit.hoon+3−44
@@ -1,5 +1,7 @@
11:: blit: runtime blit structure
22::
3+/+ dill
4+::
35|_ =blit:dill
46++ grad %noun
57:: +grab: convert from
@@ -13,49 +15,6 @@
1315++ grow
1416 |%
1517 ++ noun blit
16− ++ json
17− ^- ^json
18− =, enjs:format
19− %+ frond -.blit
20− ?- -.blit
21− %bel b+&
22− %clr b+&
23− %hop (numb p.blit)
24− %lin a+(turn p.blit |=(c=@c s+(tuft c)))
25− %mor b+&
26− %url s+p.blit
27− ::
28− %sag
29− %- pairs
30− :~ 'path'^(path p.blit)
31− 'file'^s+(en:base64:mimes:html (as-octs:mimes:html (jam q.blit)))
32− ==
33− ::
34− %sav
35− %- pairs
36− :~ 'path'^(path p.blit)
37− 'file'^s+(en:base64:mimes:html (as-octs:mimes:html q.blit))
38− ==
39− ::
40− %klr
41− :- %a
42− %+ turn p.blit
43− |= [=stye text=(list @c)]
44− %- pairs
45− :~ 'text'^a+(turn text |=(c=@c s+(tuft c)))
46− ::
47− :- 'stye'
48− %- pairs
49− |^ :~ 'back'^(color p.q.stye)
50− 'fore'^(color q.q.stye)
51− 'deco'^a+(turn ~(tap in p.stye) |=(d=deco ?~(d ~ s+d)))
52− ==
53− ++ color
54− |= =tint
55− ?@ tint ?~(tint ~ s+tint)
56− s+(crip ((x-co:co 6) (rep 3 ~[b g r]:tint)))
57− --
58− ==
59− ==
18+ ++ json (blit:enjs:dill blit)
6019 --
6120--
base-desk/mar/css.hoonnew file+25
@@ -0,0 +1,25 @@
1+::
2+:::: /hoon/css/mar
3+ ::
4+/? 310
5+=, eyre
6+=, mimes:html
7+|_ mud=@t
8+++ grow :: convert to
9+ |%
10+ ++ mime [/text/css (as-octs mud)] :: convert to %mime
11+ ++ hymn :: convert to %hymn
12+ |^ html
13+ ++ style ;style
14+ ;- (trip mud)
15+ ==
16+ ++ html ;html:(head:"{style}" body)
17+ --
18+ --
19+++ grab
20+ |% :: convert from
21+ ++ mime |=([p=mite q=octs] (@t q.q))
22+ ++ noun @t :: clam from %noun
23+ --
24+++ grad %mime
25+--
base-desk/mar/hoon.hoon+285−31
@@ -1,41 +1,295 @@
1−:::: /hoon/hoon/mar
2− ::
3−/? 310
4−::
5−=, eyre
61|_ own=@t
72::
83++ grow :: convert to
94 |%
105 ++ mime `^mime`[/text/x-hoon (as-octs:mimes:html own)] :: convert to %mime
11− ++ elem :: convert to %html
12− ;div:pre(urb_codemirror "", mode "hoon"):"{(trip own)}"
13− :: =+ gen-id="src-{<`@ui`(mug own)>}"
14− :: ;div
15− :: ;textarea(id "{gen-id}"):"{(trip own)}"
16− :: ;script:"""
17− :: CodeMirror.fromTextArea(
18− :: window[{<gen-id>}],
19− :: \{lineNumbers:true, readOnly:true}
20− :: )
21− :: """
22− :: ==
236 ++ hymn
24− :: ;html:(head:title:"Source" "+{elem}")
25− ;html
26− ;head
27− ;title:"Source"
28− ;script@"//cdnjs.cloudflare.com/ajax/libs/codemirror/4.3.0/codemirror.js";
29− ;script@"/lib/syntax/hoon.js";
30− ;link(rel "stylesheet", href "//cdnjs.cloudflare.com/ajax/libs/".
31− "codemirror/4.3.0/codemirror.min.css");
32− ;link/"/lib/syntax/codemirror.css"(rel "stylesheet");
33− ==
34− ;body
35− ;textarea#src:"{(trip own)}"
36− ;script:'CodeMirror.fromTextArea(src, {lineNumbers:true, readOnly:true})'
7+ ^- manx
8+ =/ to-wall
9+ |= =tape
10+ ^- wall
11+ %+ roll (flop tape)
12+ |= [char=@tD =wall]
13+ ?~ wall
14+ [[char ~] ~]
15+ ?: =('\0a' char)
16+ [~ wall]
17+ [[char i.wall] t.wall]
18+ ::
19+ =/ src=tape (trip own)
20+ =/ count=@ud (lent (to-wall src))
21+ =; [style=tape script=tape]
22+ ;html
23+ ;head
24+ ;title: source
25+ ;meta(charset "utf-8");
26+ ;meta(name "viewport", content "width=device-width, initial-scale=1");
27+ ;style: {style}
28+ ==
29+ ;body
30+ ;div.viewer
31+ :: ;div.viewer-bar
32+ :: ;span.viewer-name: {(scow %ud count)} lines
33+ :: ;button.copy-link(id "copy-link", hidden ""): copy link
34+ :: ==
35+ ;div.code
36+ ;pre.gutter(id "gutter");
37+ ;div.code-body
38+ ;div(id "hl");
39+ ;pre.code-pre(id "code"): {src}
40+ ==
41+ ==
42+ ==
43+ ;script: {script}
44+ ==
3745 ==
38− ==
46+ ::
47+ :-
48+ ^~ ^= style
49+ ^- tape
50+ %- trip
51+ '''
52+ :root {
53+ --bg: #fbfbf9;
54+ --panel: #ffffff;
55+ --gutter-bg: #f3f3ef;
56+ --line: #e4e4dd;
57+ --fg: #2b2b2b;
58+ --fg-dim: #9a9a90;
59+ --sel: #fff7d6;
60+ --sel-edge: #f1e4a8;
61+ --accent: #4a6da7;
62+ --lh: 20px;
63+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
64+ "Liberation Mono", "Courier New", monospace;
65+ }
66+ * { box-sizing: border-box; }
67+ html, body {
68+ margin: 0;
69+ height: 100%;
70+ background: var(--bg);
71+ color: var(--fg);
72+ font-family: var(--mono);
73+ -webkit-font-smoothing: antialiased;
74+ }
75+ .viewer {
76+ height: 100vh;
77+ display: flex;
78+ flex-direction: column;
79+ padding: 16px;
80+ gap: 12px;
81+ }
82+ .viewer-bar {
83+ flex: 0 0 auto;
84+ display: flex;
85+ align-items: center;
86+ gap: 12px;
87+ font-size: 13px;
88+ color: var(--fg-dim);
89+ }
90+ .viewer-name {
91+ flex: 1 1 auto;
92+ min-width: 0;
93+ overflow: hidden;
94+ text-overflow: ellipsis;
95+ white-space: nowrap;
96+ color: var(--fg);
97+ }
98+ .copy-link {
99+ flex: 0 0 auto;
100+ padding: 4px 12px;
101+ border: 1px solid var(--line);
102+ border-radius: 6px;
103+ background: var(--panel);
104+ color: var(--fg-dim);
105+ font: inherit;
106+ font-size: 12px;
107+ cursor: pointer;
108+ transition: color .12s, border-color .12s;
109+ }
110+ .copy-link:hover { color: var(--accent); border-color: var(--accent); }
111+ .copy-link[hidden] { display: none; }
112+ .code {
113+ flex: 1 1 auto;
114+ min-height: 0;
115+ display: flex;
116+ align-items: flex-start;
117+ overflow: auto;
118+ background: var(--panel);
119+ border: 1px solid var(--line);
120+ border-radius: 8px;
121+ }
122+ .code-body {
123+ position: relative;
124+ flex: 1 1 auto;
125+ min-width: 0;
126+ width: max-content;
127+ }
128+ #gutter, #code {
129+ margin: 0;
130+ padding: 14px 0;
131+ font-family: var(--mono);
132+ font-size: 13px;
133+ line-height: var(--lh);
134+ white-space: pre;
135+ tab-size: 2;
136+ }
137+ #gutter {
138+ position: sticky;
139+ left: 0;
140+ z-index: 2;
141+ flex: 0 0 auto;
142+ padding-left: 16px;
143+ padding-right: 12px;
144+ text-align: right;
145+ color: var(--fg-dim);
146+ background: var(--gutter-bg);
147+ border-right: 1px solid var(--line);
148+ user-select: none;
149+ -webkit-user-select: none;
150+ cursor: pointer;
151+ }
152+ #code {
153+ display: block;
154+ padding-left: 14px;
155+ padding-right: 18px;
156+ color: var(--fg);
157+ }
158+ #hl {
159+ position: absolute;
160+ left: 0;
161+ right: 0;
162+ z-index: 0; /* under the text */
163+ display: none;
164+ background: var(--sel);
165+ box-shadow: inset 2px 0 0 var(--sel-edge);
166+ pointer-events: none;
167+ }
168+ #code { position: relative; z-index: 1; } /* text above the bar */
169+ :focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
170+ '''
171+ ^~ ^= code
172+ ^- tape
173+ %- trip
174+ '''
175+ (function () {
176+ "use strict";
177+ const panel = document.querySelector(".code"); // the scroll container
178+ const code = document.getElementById("code");
179+ const gutter = document.getElementById("gutter");
180+ const hl = document.getElementById("hl");
181+ if (!panel || !code || !gutter || !hl) return;
182+ const text = code.textContent;
183+ let lineCount = text.length ? text.split("\n").length : 1;
184+ if (text.endsWith("\n")) lineCount -= 1;
185+ if (lineCount < 1) lineCount = 1;
186+ (function fillGutter() {
187+ let s = "";
188+ for (let i = 1; i <= lineCount; i++) s += i + "\n";
189+ gutter.textContent = s;
190+ })();
191+ let lineH = 0;
192+ let codeTop = 0; // code's content-box top padding, the y-origin of line 1
193+ function measure() {
194+ const cs = getComputedStyle(code);
195+ const lh = parseFloat(cs.lineHeight);
196+ lineH = isNaN(lh) ? parseFloat(cs.fontSize) * 1.55 : lh;
197+ codeTop = parseFloat(cs.paddingTop) || 0;
198+ positionHighlight(); // keep the bar correct after a re-measure
199+ }
200+ let anchor = null;
201+ let selLo = 0;
202+ let selHi = 0;
203+ function positionHighlight() {
204+ if (!selLo) { hl.style.display = "none"; return; }
205+ hl.style.display = "block";
206+ hl.style.top = (codeTop + (selLo - 1) * lineH) + "px";
207+ hl.style.height = ((selHi - selLo + 1) * lineH) + "px";
208+ }
209+ function applySel(lo, hi) {
210+ selLo = lo;
211+ selHi = hi;
212+ positionHighlight();
213+ }
214+ function deselect() {
215+ selLo = selHi = 0;
216+ anchor = null;
217+ positionHighlight();
218+ history.replaceState(null, "", location.pathname + location.search);
219+ }
220+ function parseHash(hash) {
221+ const m = /^#L(\d+)(?:-L(\d+))?$/.exec(hash || "");
222+ if (!m) return null;
223+ let lo = parseInt(m[1], 10);
224+ let hi = m[2] ? parseInt(m[2], 10) : lo;
225+ if (hi < lo) { const t = lo; lo = hi; hi = t; }
226+ lo = Math.max(1, Math.min(lo, lineCount));
227+ hi = Math.max(1, Math.min(hi, lineCount));
228+ return [lo, hi];
229+ }
230+ function setHash(lo, hi) {
231+ const frag = lo === hi ? "L" + lo : "L" + lo + "-L" + hi;
232+ history.replaceState(null, "", "#" + frag);
233+ }
234+ function scrollToLine(n) {
235+ const y = codeTop + (n - 1) * lineH;
236+ if (y < panel.scrollTop || y > panel.scrollTop + panel.clientHeight) {
237+ panel.scrollTop = Math.max(0, y - panel.clientHeight / 2);
238+ }
239+ }
240+ function selectFromHash(scroll) {
241+ const r = parseHash(location.hash);
242+ if (!r) return false;
243+ anchor = r[0];
244+ applySel(r[0], r[1]);
245+ if (scroll) scrollToLine(r[0]);
246+ return true;
247+ }
248+ function lineFromEvent(e) {
249+ // y within the code content: pointer relative to the code pre's rendered
250+ // top, which already accounts for scroll since getBoundingClientRect is
251+ // viewport-relative and the pre moves as the panel scrolls.
252+ const rect = code.getBoundingClientRect();
253+ const y = e.clientY - rect.top - codeTop;
254+ let n = Math.floor(y / lineH) + 1;
255+ if (n < 1) n = 1;
256+ if (n > lineCount) n = lineCount;
257+ return n;
258+ }
259+ function onClick(e) {
260+ const inGutter = e.target === gutter;
261+ if (!inGutter && !e.shiftKey) return; // normal text interaction in code
262+ const n = lineFromEvent(e);
263+ if (!e.shiftKey && selLo === n && selHi === n) {
264+ deselect();
265+ return;
266+ }
267+ if (e.shiftKey && anchor !== null) {
268+ applySel(Math.min(anchor, n), Math.max(anchor, n));
269+ setHash(selLo, selHi);
270+ } else {
271+ anchor = n;
272+ applySel(n, n);
273+ setHash(n, n);
274+ }
275+ }
276+ gutter.addEventListener("click", onClick);
277+ code.addEventListener("click", onClick);
278+ document.addEventListener("keydown", function (e) {
279+ if (e.key === "Escape") deselect();
280+ });
281+ window.addEventListener("hashchange", function () {
282+ if (!selectFromHash(true)) deselect();
283+ });
284+ let rz = null;
285+ window.addEventListener("resize", function () {
286+ clearTimeout(rz);
287+ rz = setTimeout(measure, 100);
288+ });
289+ measure();
290+ selectFromHash(true);
291+ })();
292+ '''
39293 ++ txt
40294 (to-wain:format own)
41295 --
base-desk/mar/htm.hoon+2−11
@@ -2,14 +2,5 @@
22:::: /hoon/htm/mar
33 ::
44/? 310
5−|_ own=manx
6−::
7−++ grad %noun
8−++ grow :: convert to
9− |%
10− ++ noun own
11− ++ hymn own
12− --
13−++ grab |% :: convert from
14− ++ noun manx :: clam from %noun
15−-- --
5+/= htm /mar/html
6+htm
base-desk/mar/httr.hoon+1−2
@@ -5,12 +5,11 @@
55::
66=, eyre
77=, format
8−=, html
98|_ hit=httr
109++ grad %noun
1110++ grow |% ++ wall (turn wain trip)
1211 ++ wain (to-wain cord)
13− ++ json (need (de-json cord))
12+ ++ json (need (de:json:html cord))
1413 ++ cord q:octs
1514 ++ noun hit
1615 ++ octs
base-desk/mar/js.hoon+7−4
@@ -8,10 +8,13 @@
88++ grow
99 |%
1010 ++ mime [/application/javascript (as-octs:mimes:html (@t mud))]
11− ++ elem ;script
12− ;- (trip (@t mud))
13− ==
14− ++ hymn ;html:(head:"+{elem}" body)
11+ ++ hymn :: convert to %hymn
12+ |^ html
13+ ++ script ;script
14+ ;- (trip (@t mud))
15+ ==
16+ ++ html ;html:(head:"{script}" body)
17+ --
1518 --
1619++ grab
1720 |% :: convert from
base-desk/mar/json.hoon+4−4
@@ -8,17 +8,17 @@
88=, eyre
99=, format
1010=, html
11−|_ jon=json
11+|_ jon=^json
1212::
1313++ grow :: convert to
1414 |%
1515 ++ mime [/application/json (as-octs:mimes -:txt)] :: convert to %mime
16− ++ txt [(crip (en-json jon))]~
16+ ++ txt [(en:json jon)]~
1717 --
1818++ grab
1919 |% :: convert from
20− ++ mime |=([p=mite q=octs] (fall (rush (@t q.q) apex:de-json) *json))
21− ++ noun json :: clam from %noun
20+ ++ mime |=([p=mite q=octs] (fall (de:json (@t q.q)) *^json))
21+ ++ noun ^json :: clam from %noun
2222 ++ numb numb:enjs
2323 ++ time time:enjs
2424 --
base-desk/mar/json/rpc/response.hoon+1−2
@@ -16,11 +16,10 @@
1616 ^- response
1717 ~| hit
1818 ?: ?=(%2 (div p.hit 100))
19− =, html
2019 %- json
2120 ?~ r.hit
2221 a+~
23− (need (de-json q:u.r.hit))
22+ (need (de:json:html q:u.r.hit))
2423 fail+hit
2524 ++ json :: from json
2625 =, dejs-soft:format
base-desk/mar/kelvin.hoon+16−6
@@ -1,18 +1,28 @@
1−=/ weft ,[lal=@tas num=@ud] :: TODO remove after merge
2−|_ kel=weft
1+|_ kal=waft:clay
32++ grow
43 |%
54 ++ mime `^mime`[/text/x-kelvin (as-octs:mimes:html hoon)]
6− ++ noun kel
7− ++ hoon (crip "{<[lal num]:kel>}\0a")
5+ ++ noun kal
6+ ++ hoon
7+ %+ rap 3
8+ %+ turn
9+ %+ sort
10+ ~(tap in (waft-to-wefts:clay kal))
11+ |= [a=weft b=weft]
12+ ?: =(lal.a lal.b)
13+ (gte num.a num.b)
14+ (gte lal.a lal.b)
15+ |= =weft
16+ (rap 3 '[%' (scot %tas lal.weft) ' ' (scot %ud num.weft) ']\0a' ~)
17+ ::
818 ++ txt (to-wain:format hoon)
919 --
1020++ grab
1121 |%
12− ++ noun weft
22+ ++ noun waft:clay
1323 ++ mime
1424 |= [=mite len=@ud tex=@]
15− !<(weft (slap !>(~) (ream tex)))
25+ (cord-to-waft:clay tex)
1626 --
1727++ grad %noun
1828--
base-desk/mar/noun.hoon+3
@@ -8,6 +8,9 @@
88++ grab |%
99 ++ noun *
1010 --
11+++ grow |%
12+ ++ mime [/application/x-urb-jam (as-octs:mimes:html (jam non))]
13+ --
1114++ grad
1215 |%
1316 ++ form %noun
base-desk/mar/sole/action.hoon+3−1
@@ -20,7 +20,9 @@
2020 |= jon=^json ^- sole-action
2121 %- need %. jon
2222 => [dejs-soft:format ..sole-action]
23− |^ (ot id+so dat+(fo %ret (of det+change tab+ni ~)) ~)
23+ |^ (ot id+id dat+(fo %ret (of det+change tab+ni ~)) ~)
24+ ++ id
25+ (ot who+(su ;~(pfix sig fed:ag)) ses+so ~)
2426 ++ fo
2527 |* [a=term b=fist]
2628 |=(c=json ?.(=([%s a] c) (b c) (some [a ~])))
base-desk/mar/story-diff.hoonnew file+16
@@ -0,0 +1,16 @@
1+::
2+::::
3+ ::
4+/- *story
5+|_ =story-diff
6+::
7+++ grad %noun
8+++ grow
9+ |%
10+ ++ noun story-diff
11+ --
12+++ grab :: convert from
13+ |%
14+ ++ noun ^story-diff :: make from %noun
15+ --
16+--
base-desk/mar/story.hoonnew file+65
@@ -0,0 +1,65 @@
1+/- *story
2+/+ *story
3+|_ tale=story
4+++ grad
5+ |%
6+ ++ form %story-diff
7+ ++ diff
8+ |= tory=story
9+ ^- story-diff
10+ :: Given new story (tory), old story (tale), compute the diff
11+ :: additions = new - old
12+ :: deletions = old - new
13+ [(dif-ju tory tale) (dif-ju tale tory)]
14+ ++ pact
15+ |= dif=story-diff
16+ :: Compute the new story after applying dif to tale.
17+ ::
18+ ^- story
19+ =. tale (uni-ju tale additions.dif)
20+ =. tale (dif-ju tale deletions.dif)
21+ tale
22+ ++ join
23+ |= [ali=story-diff bob=story-diff]
24+ ^- (unit story-diff)
25+ =/ joined-additions (uni-ju additions.ali additions.bob)
26+ =/ joined-deletions (uni-ju deletions.ali deletions.bob)
27+ ::
28+ :: In a true join, we'd do a set intersection on the keys.
29+ :: If they're not equal, we have a conflict.
30+ :: In this case, we'd produce null and kick the flow to +mash
31+ ::
32+ %- some
33+ [joined-additions joined-deletions]
34+ ++ mash
35+ :: called by meld, force merge, annotating conflicts
36+ |= $: [als=ship ald=desk ali=story-diff]
37+ [bos=ship bod=desk bob=story-diff]
38+ ==
39+ ^- story-diff
40+ (need (join ali bob)) :: XX temporary, only because join doesn't fail
41+ --
42+::
43+++ grow :: convert to
44+ |% ::
45+ ++ mime :: to %mime
46+ [/text/x-urb-story (as-octs:mimes:html (of-wain:format txt))]
47+ ++ txt
48+ ^- wain
49+ %- zing
50+ %+ join `wain`~['---']
51+ %+ murn ~(tap by tale)
52+ |= [=tako:clay =proses]
53+ ^- (unit wain)
54+ ?~ proses ~
55+ (some (chapter-to-text tako proses))
56+ --
57+++ grab
58+ |% :: convert from
59+ ++ noun story :: clam from %noun
60+ ++ mime :: retrieve from %mime
61+ |= [p=mite q=octs]
62+ =/ story-text `@t`q.q
63+ `story`(rash story-text parse-story)
64+ --
65+--
base-desk/mar/tang.hoon+5−5
@@ -16,15 +16,15 @@
1616 %& a+p.result
1717 %| a+[a+[%s '[[output rendering error]]']~]~
1818 ==
19− ::
20− ++ elem
21− =- ;pre:code:"{(of-wall -)}"
22− ^- wall %- zing ^- (list wall)
23− (turn (flop tan) |=(a=tank (wash 0^160 a)))
19+ ++ txt
20+ ^- wain
21+ %+ turn (flop tan)
22+ |=(=tank (crip ~(ram re tank)))
2423 --
2524++ grab :: convert from
2625 |%
2726 ++ noun (list ^tank) :: clam from %noun
2827 ++ tank |=(a=^tank [a]~)
28+ ++ txt |=(=wain (flop (turn wain tank)))
2929 --
3030--
base-desk/mar/thread-done.hoonnew file+11
@@ -0,0 +1,11 @@
1+|_ res=*
2+++ grab
3+ |%
4+ ++ noun *
5+ --
6+++ grow
7+ |%
8+ ++ noun res
9+ --
10+++ grad %noun
11+--
base-desk/mar/thread-fail.hoonnew file+11
@@ -0,0 +1,11 @@
1+|_ err=[term tang]
2+++ grab
3+ |%
4+ ++ noun (pair term tang)
5+ --
6+++ grow
7+ |%
8+ ++ noun err
9+ --
10+++ grad %noun
11+--
base-desk/mar/txt.hoon−1
@@ -18,7 +18,6 @@
1818 => v=.
1919 |%
2020 ++ mime => v [/text/plain (as-octs (of-wain txt))]
21− ++ elem => v ;pre: {(trip (of-wain txt))}
2221 --
2322++ grad
2423 |%
base-desk/mar/udon.hoon−5
@@ -9,10 +9,6 @@
99 ++ mime [/text/x-unmark (as-octs:mimes:html mud)]
1010 ++ txt
1111 (to-wain:format mud)
12− ++ elem
13− ^- manx
14− =, cram
15− elm:(static (ream mud))
1612 ++ front :: XX performance, types
1713 ^- (map term knot)
1814 %- ~(run by inf:(static:cram (ream mud)))
@@ -28,5 +24,4 @@
2824 ++ txt of-wain:format
2925 --
3026++ grad %txt
31−++ garb /down
3227--
base-desk/mar/umd.hoon−4
@@ -9,10 +9,6 @@
99 ++ mime [/text/x-unmark (as-octs:mimes:html mud)]
1010 ++ txt
1111 (to-wain:format mud)
12− ++ elem
13− ^- manx
14− =, cram
15− elm:(static (ream mud))
1612 ++ front :: XX performance, types
1713 ^- (map term knot)
1814 %- ~(run by inf:(static:cram (ream mud)))
base-desk/mar/urb.hoon+1−1
@@ -1,5 +1,5 @@
11::
2−:::: /hoon/elem/urb/mar
2+:::: /hoon/urb/mar
33 ::
44/? 310
55=, mimes:html
base-desk/sources.txtnew file+3
@@ -0,0 +1,3 @@
1+urbit-src/pkg/base-dev
2+urbit-src/pkg/landscape
3+urbit-src/pkg/arvo
base-desk/sur/aquarium.hoon+18−5
@@ -10,8 +10,6 @@
1010:: Short names are simply the first letter of each word plus `s` if
1111:: it's a list.
1212::
13−/+ pill
14−=, pill-lib=pill
1513|%
1614+$ az-log [topics=(lest @) data=@t]
1715+$ az-state
@@ -24,14 +22,23 @@
2422 aqua-event
2523 ==
2624::
27−+$ unix-event unix-event:pill-lib
28−+$ pill pill:pill-lib
25++$ unix-event ::NOTE like unix-event:pill-lib but for all tasks
26+ %+ pair wire
27+ $% [%wack p=@]
28+ [%what p=(list (pair path (cask)))]
29+ [%whom p=ship]
30+ [%boot ? $%($>(%fake task:jael) $>(%dawn task:jael))]
31+ [%wyrd p=vere]
32+ [%verb p=(unit ?)]
33+ task-arvo
34+ ==
2935::
3036+$ aqua-event
31− $% [%init-ship who=ship fake=?]
37+ $% [%init-ship who=ship fake=? feed=(unit feed:jael)]
3238 [%pause-events who=ship]
3339 [%snap-ships lab=term hers=(list ship)]
3440 [%restore-snap lab=term]
41+ [%read [from=[=ship life=@ubC] =path] for=[=lane:ames life=@ubC] num=@ud]
3542 [%event who=ship ue=unix-event]
3643 ==
3744::
@@ -64,6 +71,9 @@
6471 %+ pair wire
6572 $% [%blit p=(list blit:dill)]
6673 [%send p=lane:ames q=@]
74+ [%push p=(list lane:pact:ames) q=@]
75+ [%saxo sponsors=(list ship)]
76+ [%nail =ship lanes=(list lane:ames)]
6777 [%doze p=(unit @da)]
6878 [%thus p=@ud q=(unit hiss:eyre)]
6979 [%ergo p=@tas q=mode:clay]
@@ -72,5 +82,8 @@
7282 [%kill ~]
7383 [%init ~]
7484 [%request id=@ud request=request:http]
85+ [%turf p=(list turf)]
86+ [%unto =unto:gall] :: internal gifts to host ships
87+ :: (%facts are transformed to %raw-facts)
7588 ==
7689--
base-desk/sur/bitcoin.hoon+1−1
@@ -4,7 +4,7 @@
44:: chyg: whether account is (non-)change. 0 or 1
55:: bytc: "btc-byts" with dat cast to @ux
66|%
7−+$ network ?(%main %testnet)
7++$ network ?(%main %testnet %regtest)
88+$ hexb [wid=@ dat=@ux] :: hex byts
99+$ bits [wid=@ dat=@ub]
1010+$ xpub @ta
base-desk/sur/hood.hoon+283−197
@@ -1,152 +1,312 @@
1+/% kelvin %kelvin
12=, clay
23=* dude dude:gall
34|%
4−:: $snap: kiln snapshot
5++$ pike
6+ $: sync=(unit [=ship =desk])
7+ hash=@uv
8+ =zest
9+ wic=(set weft)
10+ ==
511::
6−+$ snap (map desk arak)
7−:: $diff: subscription update
12++$ pikes (map desk pike)
13+:: $jump: changes to update source change requests
814::
9−+$ diff
10− $% [%block =desk =arak =weft blockers=(set desk)]
11− [%reset =desk =arak]
12− [%commit =desk =arak]
13− [%merge-sunk =desk =arak =tang]
14− [%merge-fail =desk =arak =tang]
15− [%suspend =desk =arak]
16− [%revive =desk =arak]
15++$ jump
16+ $% [%all all=(map dock dock)] :: pending requests
17+ [%add old=dock new=dock] :: new request
18+ [%yea old=dock new=dock] :: approved
19+ [%nay old=dock new=dock] :: denied
1720 ==
18−:: $arak: foreign vat tracker
21+:: $rung: reference to upstream commit
1922::
20−:: .rail: upstream tracking state, if any
21−:: .rein: configuration for agents
23++$ rung [=aeon =weft]
24+:: #sync-record: source and destination of a kiln sync
2225::
23−+$ arak
24− $: rail=(unit rail)
25− =rein
26++$ sync-record ::
27+ $: syd=desk :: local desk
28+ her=ship :: foreign ship
29+ sud=desk :: foreign desk
2630 ==
27−:: $rail: upstream tracking state
2831::
29−:: .publisher: Ship that originally published desk, if available
30−:: .paused: is tracking paused? or live
31−:: .ship: upstream ship (could be .our)
32−:: .desk: name of upstream desk
33−:: .aeon: next aeon to pull from upstream
34−:: .next: list of pending commits with future kelvins
32++$ sync-state ::
33+ $: nun=@ta :: nonce
34+ kid=(unit desk) :: has kids desk too?
35+ let=@ud :: next revision
36+ nit=(unit ?) :: automerge or default
37+ hav=(unit @ud) :: update available
38+ yea=? :: update approved
39+ ==
3540::
36−+$ rail
37− $: publisher=(unit ship)
38− paused=?
39− =ship
40− =desk
41− =aeon
42− next=(list rung)
41++$ sync-update
42+ $% [%new for=sync-record rev=@ud]
43+ [%done for=sync-record rev=@ud]
44+ [%drop for=sync-record rev=@ud]
45+ [%pending pending=(set [for=sync-record rev=@ud])]
4346 ==
44−:: $rung: reference to upstream commit
4547::
46−+$ rung [=aeon =weft]
47−:: $rein: diff from desk manifest
48++$ sink (unit [her=@p sud=desk kid=(unit desk) let=@ud])
49+:: +truncate-hash: get last 5 digits of hash and convert to tape
4850::
49−:: .liv: suspended? if suspended, no agents should run
50−:: .add: agents not in manifest that should be running
51−:: .sub: agents in manifest that should not be running
51+++ truncate-hash
52+ |= hash=@uv
53+ ^- tape
54+ (slag 2 <`@uv`(mod hash 0v1.00000)>)
55+:: +report-prep: get data required for reports
5256::
53−+$ rein
54− $: liv=_&
55− add=(set dude)
56− sub=(set dude)
57+++ report-prep
58+ |= [our=@p now=@da]
59+ =/ ego (scot %p our)
60+ =/ wen (scot %da now)
61+ :* .^(rock:tire %cx /[ego]//[wen]/tire)
62+ .^(cone %cx /[ego]//[wen]/domes)
63+ .^((map desk [ship desk]) %gx /[ego]/hood/[wen]/kiln/sources/noun)
64+ .^ (map [desk ship desk] sync-state) %gx
65+ /[ego]/hood/[wen]/kiln/syncs/noun
66+ ==
5767 ==
58−::
59−+$ vat [=desk hash=@uv =cass =arak]
6068:: +report-vats: report on all desk installations
6169::
6270++ report-vats
63− |= [our=@p now=@da]
71+ |= [our=@p now=@da desks=(list desk) filt=@tas verb=?]
6472 ^- tang
65− =+ .^ raz=(list vat)
66− %gx /(scot %p our)/hood/(scot %da now)/kiln/vats/noun
73+ =/ ego (scot %p our)
74+ =/ wen (scot %da now)
75+ =+ prep=[tyr cone sor zyn]=(report-prep our now)
76+ ?: =(%$ filt)
77+ %- zing
78+ %+ turn
79+ ?^ desks
80+ (flop desks)
81+ %+ sort ~(tap in ~(key by tyr.prep))
82+ |= [a=desk b=desk]
83+ ?: |(=(a %kids) =(b %base)) &
84+ ?: |(=(a %base) =(b %kids)) |
85+ (aor b a)
86+ |=(syd=@tas (report-vat prep our now syd verb))
87+ =/ deks=(list [=desk =zest wic=(set weft)])
88+ ?~ desks
89+ %+ sort ~(tap by tyr.prep)
90+ |= [[a=desk *] [b=desk *]]
91+ ?: |(=(a %kids) =(b %base)) &
92+ ?: |(=(a %base) =(b %kids)) |
93+ (aor b a)
94+ %+ murn (flop desks)
95+ |= des=desk
96+ ^- (unit [=desk =zest wic=(set weft)])
97+ ?~ got=(~(get by tyr.prep) des)
98+ ~
99+ `[des u.got]
100+ ?: =(filt %blocking)
101+ =/ base-weft=(unit weft)
102+ %- ~(rep in wic:(~(got by tyr.prep) %base))
103+ |= [=weft out=(unit weft)]
104+ ?~ out
105+ `weft
106+ ?: (lth num.weft num.u.out)
107+ out
108+ `weft
109+ ?~ base-weft ~['%base already up-to-date']
110+ =/ blockers=(list desk)
111+ %+ sort
112+ ^- (list desk)
113+ %+ murn deks
114+ |= [=desk =zest wic=(set weft)]
115+ ^- (unit ^desk)
116+ ?. =(%live zest)
117+ ~
118+ ?: (~(has in wic) u.base-weft)
119+ ~
120+ `desk
121+ aor
122+ ?~ blockers ~['No desks blocking upgrade']
123+ %- zing
124+ ^- (list tang)
125+ :- :~ %+ rap 3
126+ :~ 'These desks are blocking upgrade to [%zuse '
127+ (scot %ud num.u.base-weft)
128+ ']:'
129+ == ==
130+ %+ turn blockers
131+ |=(syd=desk (report-vat prep our now syd verb))
132+ ::
133+ %- zing
134+ %+ turn
135+ ?+ filt !!
136+ %exists
137+ %+ skip deks
138+ |= [syd=desk *]
139+ ?~ got=(~(get by cone.prep) our syd)
140+ &
141+ =(0 let.u.got)
142+ ::
143+ %running
144+ %+ skim deks
145+ |=([* [zest=@tas *]] =(zest %live))
146+ ::
147+ %suspended
148+ %+ skip deks
149+ |= [syd=@tas [zest=@tas *]]
150+ ?| =(syd %kids)
151+ =(zest %live)
152+ ?~ got=(~(get by cone.prep) our syd)
153+ &
154+ =(0 let.u.got)
67155 ==
68− :- (report-kids our now)
69− (turn raz |=(v=vat (report-vat our now v)))
156+ ::
157+ %exists-not
158+ %+ skim deks
159+ |= [syd=desk *]
160+ ?~ got=(~(get by cone.prep) our syd)
161+ |
162+ =(0 let.u.got)
163+ ==
164+ |=([syd=desk *] (report-vat prep our now syd verb))
70165:: +report-vat: report on a single desk installation
71166::
72167++ report-vat
73− |= [our=ship now=@da vat]
74− ^- tank
75− ?: =(ud.cass 0)
76− leaf+"desk does not yet exist: {<desk>}"
77− =/ kel-path
78− /(scot %p our)/[desk]/(scot %da now)/sys/kelvin
168+ |= $: $: tyr=rock:tire =cone sor=(map desk (pair ship desk))
169+ zyn=(map [desk ship desk] sync-state)
170+ ==
171+ our=ship now=@da syd=desk verb=?
172+ ==
173+ |^ ^- tang
174+ =/ ego (scot %p our)
175+ =/ wen (scot %da now)
176+ ?. ((sane %tas) syd)
177+ ~[(cat 3 'insane desk: %' syd)]
178+ ?. (~(has by cone) our syd)
179+ ~[(cat 3 'desk does not yet exist: %' syd)]
180+ =/ hash .^(@uv %cz /[ego]/[syd]/[wen])
181+ ?: =(%kids syd)
182+ ~[(cat 3 '%kids %cz hash: ' (scot %uv hash))]
183+ =/ kel-path /[ego]/[syd]/[wen]/sys/kelvin
79184 ?. .^(? %cu kel-path)
80− leaf+"bad desk: {<desk>}"
81− =+ .^(=weft %cx kel-path)
82− :+ %rose ["" "{<desk>}" "::"]
185+ ~[(cat 3 'bad desk: %' syd)]
186+ =+ .^(=waft %cx kel-path)
83187 ^- tang
84− =/ meb (mergebase-hashes our desk now arak)
85− =/ poz
86− ?~ rail.arak "local"
87− ?:(paused.u.rail.arak "paused" "tracking")
88− =/ sat ?:(liv.rein.arak "running" "suspended")
89− =/ pen
90− ?~ rail.arak "~"
91− <(turn next.u.rail.arak |=([@ lal=@tas num=@] [lal num]))>
92− :~ leaf/"/sys/kelvin: {<[lal num]:weft>}"
93− leaf/"base hash: {?.(=(1 (lent meb)) <meb> <(head meb)>)}"
94− leaf/"%cz hash: {<hash>}"
95− ::
96− leaf/"app status: {sat}"
97− leaf/"force on: {?:(=(~ add.rein.arak) "~" <add.rein.arak>)}"
98− leaf/"force off: {?:(=(~ sub.rein.arak) "~" <sub.rein.arak>)}"
99− ::
100− leaf/"publishing ship: {?~(rail.arak <~> <publisher.u.rail.arak>)}"
101− leaf/"updates: {poz}"
102− leaf/"source ship: {?~(rail.arak <~> <ship.u.rail.arak>)}"
103− leaf/"source desk: {?~(rail.arak <~> <desk.u.rail.arak>)}"
104− leaf/"source aeon: {?~(rail.arak <~> <aeon.u.rail.arak>)}"
105− leaf/"pending updates: {pen}"
188+ =/ =sink
189+ ?~ s=(~(get by sor) syd)
190+ ~
191+ ?~ z=(~(get by zyn) syd u.s)
192+ ~
193+ `[p.u.s q.u.s [kid let]:u.z]
194+ =/ meb=(list @uv)
195+ ?~ sink ~[hash]
196+ %+ turn
197+ .^ (list tako) %cs
198+ /[ego]/[syd]/[wen]/base/(scot %p her.u.sink)/[sud.u.sink]
199+ ==
200+ |=(=tako .^(@uv %cs /[ego]/[syd]/[wen]/hash/(scot %uv tako)))
201+ =/ dek (~(got by tyr) syd)
202+ =/ sat
203+ ?- zest.dek
204+ %live 'running'
205+ %dead 'suspended'
206+ %held 'suspended until next update'
207+ ==
208+ =/ kul=cord (print-wefts (waft-to-wefts waft))
209+ =/ ese=cord
210+ ?:(.^(? %cx /[ego]//[wen]/esse/[syd]) 'yes' 'no')
211+ =/ pax=path /(scot %p our)/[syd]/(scot %da now)
212+ ?. verb
213+ :~ '::'
214+ =+ .^(exist=? cu+(weld pax /desk/bill))
215+ ?. exist ' /desk/bill: missing'
216+ =+ .^(dudes=(list dude:gall) cx+(weld pax /desk/bill))
217+ (cat 3 ' /desk/bill: ' (crip "{<dudes>}"))
218+ (cat 3 ' pending updates: ' (print-wefts wic.dek))
219+ (cat 3 ' source ship: ' ?~(sink '~' (scot %p her.u.sink)))
220+ (cat 3 ' app status: ' sat)
221+ (cat 3 ' essential desk: ' ese)
222+ (cat 3 ' %cz hash ends in: ' (print-shorthash hash))
223+ (cat 3 ' /sys/kelvin: ' (print-wefts (waft-to-wefts waft)))
224+ (cat 3 '%' syd)
225+ ==
226+ ::
227+ =/ [on=(list @tas) of=(list @tas)]
228+ =/ [on=(list @tas) of=(list @tas)]
229+ %- ~(rep by ren:(~(got by cone) our syd))
230+ |= [[=dude:gall is-on=?] on=(list @tas) of=(list @tas)]
231+ ?: is-on
232+ [[dude on] of]
233+ [on [dude of]]
234+ [(sort on aor) (sort of aor)]
235+ :~ '::'
236+ (cat 3 ' pending updates: ' (print-wefts wic.dek))
237+ %^ cat 3 ' kids desk: ' ?~ sink '~'
238+ ?~ kid.u.sink '~'
239+ (cat 3 '%' u.kid.u.sink)
240+ (cat 3 ' source aeon: ' ?~(sink '~' (scot %ud let.u.sink)))
241+ (cat 3 ' source desk: ' ?~(sink '~' (cat 3 '%' sud.u.sink)))
242+ (cat 3 ' source ship: ' ?~(sink '~' (scot %p her.u.sink)))
243+ (cat 3 ' updates: ' ?~(sink 'local' 'remote'))
244+ %^ cat 3 ' publishing ship: ' ?~ got=(get-publisher our syd now)
245+ '~'
246+ (scot %p u.got)
247+ ::
248+ (cat 3 ' force off: ' (print-agents of))
249+ (cat 3 ' force on: ' (print-agents on))
250+ (cat 3 ' app status: ' sat)
251+ (cat 3 ' essential desk: ' ese)
252+ ::
253+ (cat 3 ' %cz hash: ' (scot %uv hash))
254+ (cat 3 ' base hash: ' (print-mergebases meb))
255+ (cat 3 ' /sys/kelvin: ' (print-wefts (waft-to-wefts waft)))
256+ (cat 3 '%' syd)
106257 ==
258+ ++ print-wefts
259+ |= wefts=(set weft)
260+ ^- @t
261+ ?: =(~ wefts)
262+ '~'
263+ %+ roll (sort ~(tap in wefts) aor)
264+ |= [=weft out=@t]
265+ ?: =('' out)
266+ (rap 3 '[%' lal.weft ' ' (scot %ud num.weft) ']' ~)
267+ (rap 3 out ' [%' lal.weft ' ' (scot %ud num.weft) ']' ~)
268+ ::
269+ ++ print-shorthash
270+ |= hash=@uv
271+ ^- @t
272+ (crip ((v-co:co 5) (end [0 25] hash)))
273+ ::
274+ ++ print-mergebases
275+ |= hashes=(list @uv)
276+ ^- @t
277+ ?~ hashes
278+ '~'
279+ ?~ t.hashes
280+ (scot %uv i.hashes)
281+ %+ roll `(list @uv)`hashes
282+ |= [hash=@uv out=@t]
283+ ?: =('' out)
284+ (print-shorthash hash)
285+ (rap 3 out ' ' (print-shorthash hash) ~)
286+ ::
287+ ++ print-agents
288+ |= agents=(list @tas)
289+ ^- @t
290+ ?~ agents
291+ '~'
292+ %+ roll `(list @tas)`agents
293+ |= [agent=@tas out=@tas]
294+ ?: =('' out)
295+ (cat 3 '%' agent)
296+ (rap 3 out ' %' agent ~)
297+ --
107298:: +report-kids: non-vat cz hash report for kids desk
108299::
109300++ report-kids
110301 |= [our=ship now=@da]
111302 ^- tank
112− =/ dek %kids
303+ =/ syd %kids
113304 =/ ego (scot %p our)
114305 =/ wen (scot %da now)
115− ?. (~(has in .^((set desk) %cd /[ego]//[wen])) dek)
116− leaf/"no %kids desk"
117− =+ .^(hash=@uv %cz /[ego]/[dek]/[wen])
118− leaf/"%kids %cz hash: {<hash>}"
119−:: +read-kelvin-foreign: read /sys/kelvin from a foreign desk
120−::
121−++ read-kelvin-foreign
122− |= [=ship =desk =aeon]
123− ^- weft
124− ~| read-foreign-kelvin/+<
125− =/ her (scot %p ship)
126− =/ syd (scot %tas desk)
127− =/ yon (scot %ud aeon)
128− ::
129− =/ dom .^(dome cv/~[her syd yon])
130− =/ tak (scot %uv (~(got by hit.dom) let.dom))
131− =/ yak .^(yaki cs/~[her syd yon %yaki tak])
132− =/ lob (scot %uv (~(got by q.yak) /sys/kelvin))
133− =/ bob .^(blob cs/~[her syd yon %blob lob])
134− ::
135− ;; weft
136− ?- -.bob
137− %direct q.q.bob
138− %delta q.r.bob
139− ==
140−:: +read-kelvin-local: read /sys/kelvin from a local desk
141−::
142−++ read-kelvin-local
143− |= [our=ship =desk now=@da]
144− ^- (unit weft)
145− ~| read-kelvin-local+desk
146− =/ pax (en-beam [our desk da+now] /sys/kelvin)
147− ?. .^(? cu/pax)
148− ~
149− [~ .^(weft cx/pax)]
306+ ?. (~(has in .^((set desk) %cd /[ego]//[wen])) syd)
307+ 'no %kids desk'
308+ =+ .^(hash=@uv %cz /[ego]/[syd]/[wen])
309+ (cat 3 '%kids %cz hash: ' (scot %uv hash))
150310:: +read-bill-foreign: read /desk/bill from a foreign desk
151311::
152312++ read-bill-foreign
@@ -157,20 +317,15 @@
157317 =/ syd (scot %tas desk)
158318 =/ yon (scot %ud aeon)
159319 ::
160− =/ dom .^(dome cv/~[her syd yon])
320+ =/ dom .^(domo cv/~[her syd yon])
161321 =/ tak ~| aeons=~(key by hit.dom)
162322 (scot %uv (~(got by hit.dom) aeon))
163323 =/ yak .^(yaki cs/~[her syd yon %yaki tak])
164324 =/ fil (~(get by q.yak) /desk/bill)
165325 ?~ fil ~
166326 =/ lob (scot %uv u.fil)
167− =/ bob .^(blob cs/~[her syd yon %blob lob])
168− ::
169− ;; (list dude)
170− ?- -.bob
171− %direct q.q.bob
172− %delta q.r.bob
173− ==
327+ =/ peg .^(page cs/~[her syd yon %blob lob])
328+ ;;((list dude) q.peg)
174329:: +read-bill: read contents of /desk/bill manifest
175330::
176331++ read-bill
@@ -179,21 +334,6 @@
179334 ?. .^(? cu/pax)
180335 *(list dude)
181336 .^((list dude) cx/pax)
182−:: +adjust-dudes: which agents should be started and stopped
183−::
184−:: Will ask Gall to start agents that it's already running
185−:: but that should be ok, and might be safer in case other
186−:: unprocessed moves would have turned them off.
187−::
188−++ adjust-dudes
189− |= $: local=[our=ship =desk now=@da]
190− =rein
191− ==
192− ^- [jolt=(list dude) idle=(list dude)]
193− =/ all=(list dude) (read-bill local)
194− =/ want (get-apps-want local all rein)
195− =/ have (get-apps-live local)
196− [want (skip have ~(has in (sy want)))]
197337::
198338++ get-remote-diff
199339 |= [our=ship here=desk now=@da her=ship there=desk when=aeon]
@@ -219,39 +359,19 @@
219359 |= [our=ship =desk now=@da]
220360 ^- (list [=dude live=?])
221361 %~ tap in
222− .^((set [=dude live=?]) ge+/(scot %p our)/[desk]/(scot %da now))
223−:: +get-apps-want: find which apps should be running on a desk
224−::
225−++ get-apps-want
226− |= [local=[our=ship =desk now=@da] duz=(list dude) =rein]
227− ^- (list dude)
228− ?. liv.rein ~
229− ?. |(=(`zuse+zuse (read-kelvin-local local)) =(%base desk.local)) ~
230− =. duz (skip duz ~(has in sub.rein))
231− =. duz (weld duz (skip ~(tap in add.rein) ~(has in (sy duz))))
232− duz
362+ .^((set [=dude live=?]) ge+/(scot %p our)/[desk]/(scot %da now)/$)
233363::
234364++ mergebase-hashes
235− |= [our=@p =desk now=@da =arak]
236− ?~ rail.arak
237− ~
238− =/ her (scot %p ship.u.rail.arak)
365+ |= [our=@p syd=desk now=@da her=ship sud=desk]
366+ =/ her (scot %p her)
239367 =/ ego (scot %p our)
240368 =/ wen (scot %da now)
241− %+ turn .^((list tako) %cs ~[ego desk wen %base her desk.u.rail.arak])
242− |=(=tako .^(@uv %cs ~[ego desk wen %hash (scot %uv tako)]))
369+ %+ turn .^((list tako) %cs /[ego]/[syd]/[wen]/base/[her]/[sud])
370+ |=(=tako .^(@uv %cs /[ego]/[syd]/[wen]/hash/(scot %uv tako)))
243371::
244372++ enjs
245373 =, enjs:format
246374 |%
247− ++ vats
248− |= v=(list ^vat)
249− ^- json
250− %- pairs
251− %+ turn v
252− |= va=^vat
253− [desk.va (vat va)]
254− ::
255375 ++ tim
256376 |= t=@
257377 ^- json
@@ -264,15 +384,6 @@
264384 da+(tim da.c)
265385 ==
266386 ::
267− ++ vat
268− |= v=^vat
269− %- pairs
270− :~ desk+s+desk.v
271− hash+s+(scot %uv hash.v)
272− cass+(cass cass.v)
273− arak+(arak arak.v)
274− ==
275− ::
276387 ++ weft
277388 |= w=^weft
278389 %- pairs
@@ -286,30 +397,5 @@
286397 :~ aeon+(numb aeon.r)
287398 weft+(weft weft.r)
288399 ==
289− ::
290− ++ rein
291− |= r=^rein
292− %- pairs
293− :~ add+a+(turn ~(tap in add.r) (lead %s))
294− sub+a+(turn ~(tap in sub.r) (lead %s))
295− ==
296− ::
297− ++ arak
298− |= a=^arak
299− %- pairs
300− :~ rail+?~(rail.a ~ (rail u.rail.a))
301− rein+(rein rein.a)
302− ==
303− ::
304− ++ rail
305− |= r=^rail
306− %- pairs
307− :~ ship+s+(scot %p ship.r)
308− publisher+?~(publisher.r ~ s+(scot %p u.publisher.r))
309− desk+s+desk.r
310− paused+b+paused.r
311− aeon+(numb aeon.r)
312− next+a+(turn next.r rung)
313− ==
314400 --
315401--
base-desk/sur/ring.hoon+1−1
@@ -15,7 +15,7 @@
1515 :: resistance...but if your private keys are compromised, your
1616 :: adversary can determine which signatures you made.
1717 ::
18− y=(unit @udpoint)
18+ y=(unit @uxpoint)
1919 ==
2020:: +ring-signature: higher level ring signature type
2121::
base-desk/sur/sole.hoon+2−1
@@ -3,8 +3,9 @@
33 ::
44^?
55|%
6++$ sole-id [who=@p ses=@ta]
67+$ sole-action :: sole to app
7− $: id=@ta :: duct id
8+ $: id=sole-id :: session id
89 $= dat
910 $% :: [%abo ~] :: reset interaction
1011 [%det sole-change] :: command line edit
base-desk/sur/spider.hoon+14−1
@@ -1,7 +1,7 @@
11/+ libstrand=strand
22=, strand=strand:libstrand
33|%
4−+$ thread $-(vase _*form:(strand ,vase))
4++$ thread $-(vase shed:khan)
55+$ input [=tid =cage]
66+$ tid tid:strand
77+$ bowl bowl:strand
@@ -11,4 +11,17 @@
1111 %nonexistent :: 404
1212 %offline :: 504
1313 ==
14++$ start-args
15+ $: parent=(unit tid)
16+ use=(unit tid)
17+ =beak
18+ file=term
19+ =vase
20+ ==
21++$ inline-args
22+ $: parent=(unit tid)
23+ use=(unit tid)
24+ =beak
25+ =shed:khan
26+ ==
1427--
base-desk/sur/sss.hoonnew file+39
@@ -0,0 +1,39 @@
1+|%
2+++ lake
3+ |$ [rock wave]
4+ $_ ^?
5+ |%
6+ ++ name *term
7+ +$ rock ^rock
8+ +$ wave ^wave
9+ ++ wash |~ [rock wave] *rock
10+ --
11++$ aeon @ud
12++$ dude dude:agent:gall
13+++ poke
14+ |%
15+ ++ request
16+ |* paths=mold
17+ $: path=paths
18+ =dude
19+ ==
20+ ++ response
21+ |* [=(lake) paths=mold]
22+ $: path=paths
23+ =dude
24+ $% [what=%tomb ~]
25+ [what=%rock =aeon =rock:lake]
26+ [what=%wave =aeon =wave:lake]
27+ == ==
28+ ++ on-rock
29+ |* [=(lake) paths=mold]
30+ $: path=paths
31+ src=ship
32+ from=dude
33+ stale=?
34+ fail=?
35+ =rock:lake
36+ wave=(unit wave:lake)
37+ ==
38+ --
39+--
base-desk/sur/story.hoonnew file+9
@@ -0,0 +1,9 @@
1+^?
2+|%
3++$ prose [title=@t body=@t]
4++$ proses (set prose)
5++$ story (jug tako:clay prose) :: set len > 1 means conflicting messages have been assigned
6++$ chapter [tako:clay proses] :: prose entry type
7++$ cash $%([%tako p=tako:clay] case) :: used in generators to accept a tako directly as well
8++$ story-diff [additions=story deletions=story]
9+--
base-desk/sur/verb.hoon+37−1
@@ -9,4 +9,40 @@
99 [%on-arvo =wire vane=term sign=term]
1010 [%on-fail =term]
1111 ==
12−--
12+::
13++$ event-plus
14+ $: act=@ud
15+ now=@da
16+ src=@p
17+ sap=path
18+ =cause
19+ effects=(list effect)
20+ ==
21+::
22++$ cause
23+ $% [%on-init ~]
24+ [%on-load ~]
25+ [%on-poke =mark mug=@ux]
26+ [%on-watch =path]
27+ [%on-leave =path]
28+ [%on-agent =wire =sign]
29+ [%on-arvo =wire vane=term sign=term]
30+ [%on-fail =term]
31+ ==
32+::
33++$ sign
34+ $% [%poke-ack ack=?]
35+ [%watch-ack ack=?]
36+ [%kick ~]
37+ [%fact =mark mug=@ux]
38+ ==
39+::
40++$ effect
41+ $% [%poke =wire =gill:gall =mark mug=@ux]
42+ [%watch =wire =gill:gall =path]
43+ [%leave =wire =gill:gall]
44+ [%fact paths=(list path) =mark mug=@ux]
45+ [%kick paths=(list path)]
46+ [%arvo =wire vane=term task=term]
47+ ==
48+--
bin/commit.shnew file+24
@@ -0,0 +1,24 @@
1+#!/bin/bash
2+
3+usage() { printf "Usage: $0 URBIT_DESK_DIRECTORY\n" 1>&2; exit 1; }
4+
5+cdir=$(dirname $0)
6+
7+if [ $# -eq 0 ]; then
8+ usage
9+ exit 2
10+fi
11+DESK_DIR=${@: -1}
12+DESK_DIR="${DESK_DIR/#\~/$HOME}"
13+PIER=$(dirname $DESK_DIR)
14+DESK=$(basename $DESK_DIR)
15+
16+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
17+
18+lensa() {
19+ curl -s \
20+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
21+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
22+}
23+
24+lensa 'hood' "+hood/commit %$DESK"
bin/get_glob.shnew file+19
@@ -0,0 +1,19 @@
1+#!/bin/bash
2+
3+usage() { printf "Usage: $0 URBIT_GLOBBER_DESK_DIRECTORY" 1>&2; exit 1; }
4+
5+cdir=$(dirname $0)
6+
7+if [ $# -eq 0 ]; then
8+ usage
9+ exit 2
10+fi
11+DESK="${1//\~/$HOME}"
12+GLOBS="$DESK/../.urb/put"
13+GLOB=$(ls -t $GLOBS | head -1)
14+TARGET_GLOBS="$cdir"/../globs/
15+mkdir -p $TARGET_GLOBS
16+cp "$GLOBS/$GLOB" $TARGET_GLOBS/
17+HASH="${GLOB//glob-/}"
18+HASH="${HASH//.glob/}"
19+echo $HASH
bin/install.shnew file+82
@@ -0,0 +1,82 @@
1+#!/bin/bash
2+#
3+# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
4+# by ~timluc-miptev
5+#
6+# Modified HEAVILY
7+#
8+usage() { printf "Usage: $0 [-w] [-g] URBIT_DESK_DIRECTORY\n(-w: flag to watch and live copy code)\n(-g: install built ui to globber desk)\n" 1>&2; exit 1; }
9+
10+cdir=$(dirname $0)
11+
12+if [ $# -eq 0 ]; then
13+ usage
14+ exit 2
15+fi
16+DESK_DIR=${@: -1}
17+DESK_DIR="${DESK_DIR/#\~/$HOME}"
18+PIER=$(dirname $DESK_DIR)
19+DESK=$(basename $DESK_DIR)
20+EXCLUDE_FILE="${cdir}"/../config/ignore_files_on_install.txt
21+
22+port=$(cat $PIER/.http.ports | grep loopback | tr -s ' ' '\n' | head -n 1)
23+
24+lensd() {
25+ curl -s \
26+ --data "{\"source\":{\"dojo\":\"$1\"},\"sink\":{\"stdout\":null}}" \
27+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
28+}
29+
30+lensa() {
31+ curl -s \
32+ --data "{\"source\":{\"dojo\":\"$2\"},\"sink\":{\"app\":\"$1\"}}" \
33+ "http://localhost:$port" | xargs printf %s | sed 's/\\n/\n/g'
34+}
35+
36+sync() {
37+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../base-desk/* $DESK_DIR/
38+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../docs-dev-desk/* $DESK_DIR/
39+ rsync -r --copy-links --exclude-from=$EXCLUDE_FILE "${cdir}"/../desk/* $DESK_DIR/
40+ if [ "$GLOBBER" ]; then
41+ rsync -r --copy-links "${cdir}"/../ui/dist/* $DESK_DIR/astrolabe
42+ fi
43+}
44+
45+
46+while getopts "wg" opt; do
47+ case ${opt} in
48+ w) WATCH_MODE="true"
49+ ;;
50+ g) GLOBBER="true"
51+ ;;
52+ *) usage
53+ ;;
54+ esac
55+done
56+
57+if [ -z "$WATCH_MODE" ]; then
58+ echo "Installed %astrolabe to ${DESK_DIR}"
59+ rm -r $DESK_DIR/*
60+ sync
61+ lensa 'hood' "+hood/commit %$DESK"
62+ if [ "$GLOBBER" ]; then
63+ lensd '-garden!make-glob %globber /astrolabe'
64+ GLOBS="$PIER/.urb/put"
65+ GLOB=$(ls -t $GLOBS | head -1)
66+ TARGET_GLOBS="$cdir"/../globs/
67+ mkdir -p $TARGET_GLOBS
68+ cp "$GLOBS/$GLOB" $TARGET_GLOBS/
69+ echo "copied from $GLOBS to $TARGET_GLOBS"
70+ HASH="${GLOB//glob-/}"
71+ HASH="${HASH//.glob/}"
72+ echo $HASH
73+ fi
74+else
75+ echo "Watching for changes to copy to ${DESK_DIR}..."
76+ rm -r $DESK_DIR/*
77+ while [ 0 ]
78+ do
79+ sleep 0.8
80+ sync
81+ done
82+fi
bin/make_glob.pynew file+208
@@ -0,0 +1,208 @@
1+#!/usr/bin/env python3
2+
3+import hashlib
4+import sys
5+from pathlib import Path
6+from urllib.parse import quote
7+
8+from noun import Cell, deep, intbytes, mug
9+
10+MIME_TYPES = {
11+ "css": "text/css",
12+ "gif": "image/gif",
13+ "html": "text/html",
14+ "ico": "image/x-icon",
15+ "jpeg": "image/jpeg",
16+ "jpg": "image/jpeg",
17+ "js": "application/javascript",
18+ "json": "application/json",
19+ "png": "image/png",
20+ "svg": "image/svg+xml",
21+ "ttf": "font/ttf",
22+ "webmanifest": "application/manifest+json",
23+ "woff": "font/woff",
24+ "woff2": "font/woff2",
25+}
26+BASE32 = "0123456789abcdefghijklmnopqrstuv"
27+
28+
29+class BitWriter:
30+ def __init__(self):
31+ self.output = bytearray()
32+ self.buffer = 0
33+ self.used = 0
34+ self.cursor = 0
35+
36+ def write(self, value, count):
37+ if count == 0:
38+ return
39+ self.buffer |= value << self.used
40+ self.used += count
41+ self.cursor += count
42+ byte_count = self.used // 8
43+ if byte_count:
44+ self.output.extend(self.buffer.to_bytes((self.used + 7) // 8, "little")[:byte_count])
45+ self.buffer >>= byte_count * 8
46+ self.used -= byte_count * 8
47+
48+ def finish(self):
49+ if self.used:
50+ self.output.append(self.buffer)
51+ self.buffer = 0
52+ self.used = 0
53+ return bytes(self.output)
54+
55+
56+def jam_bytes(value):
57+ writer = BitWriter()
58+ references = {}
59+
60+ def save(noun):
61+ references[noun] = writer.cursor
62+
63+ def mat(atom):
64+ if atom == 0:
65+ writer.write(1, 1)
66+ return
67+ atom_length = atom.bit_length()
68+ length_size = atom_length.bit_length()
69+ writer.write(1 << length_size, length_size + 1)
70+ writer.write(atom_length & ((1 << (length_size - 1)) - 1), length_size - 1)
71+ writer.write(atom, atom_length)
72+
73+ def back(reference):
74+ writer.write(3, 2)
75+ mat(reference)
76+
77+ def serialize(noun):
78+ duplicate = references.get(noun)
79+ if deep(noun):
80+ if duplicate is not None:
81+ back(duplicate)
82+ return
83+ save(noun)
84+ writer.write(1, 1)
85+ writer.write(0, 1)
86+ serialize(noun.head)
87+ serialize(noun.tail)
88+ return
89+ if duplicate is not None:
90+ atom_size = noun.bit_length()
91+ reference_size = duplicate.bit_length()
92+ if atom_size >= reference_size:
93+ back(duplicate)
94+ return
95+ save(noun)
96+ writer.write(0, 1)
97+ mat(noun)
98+
99+ serialize(value)
100+ return writer.finish()
101+
102+
103+def cord(value):
104+ return int.from_bytes(value.encode(), "little")
105+
106+
107+def path_noun(parts):
108+ result = 0
109+ for part in reversed(parts):
110+ result = Cell(cord(part), result)
111+ return result
112+
113+
114+def dor(a, b):
115+ if a == b:
116+ return True
117+ if deep(a):
118+ if not deep(b):
119+ return False
120+ if a.head == b.head:
121+ return dor(a.tail, b.tail)
122+ return dor(a.head, b.head)
123+ if deep(b):
124+ return True
125+ return a < b
126+
127+
128+def gor(a, b):
129+ a_mug = mug(a)
130+ b_mug = mug(b)
131+ return dor(a, b) if a_mug == b_mug else a_mug < b_mug
132+
133+
134+def map_put(tree, entry):
135+ if tree == 0:
136+ return Cell(entry, Cell(0, 0))
137+ node = tree.head
138+ if entry.head == node.head:
139+ return Cell(entry, tree.tail)
140+ left = tree.tail.head
141+ right = tree.tail.tail
142+ if gor(entry.head, node.head):
143+ return Cell(node, Cell(map_put(left, entry), right))
144+ return Cell(node, Cell(left, map_put(right, entry)))
145+
146+
147+def shas(salt, serialized):
148+ value_hash = int.from_bytes(hashlib.sha256(serialized).digest(), "little")
149+ length = max(32, len(intbytes(salt)))
150+ digest = hashlib.sha256((salt ^ value_hash).to_bytes(length, "little")).digest()
151+ return int.from_bytes(digest, "little")
152+
153+
154+def sham(serialized):
155+ hashed = shas(cord("sham"), serialized)
156+ mask = (1 << 128) - 1
157+ return (hashed & mask) ^ (hashed >> 128)
158+
159+
160+def scow_uv(value):
161+ if value == 0:
162+ return "0v0"
163+ digits = []
164+ while value:
165+ value, digit = divmod(value, 32)
166+ digits.append(BASE32[digit])
167+ digits = "".join(reversed(digits))
168+ first_length = (len(digits) - 1) % 5 + 1
169+ groups = [digits[:first_length]]
170+ groups.extend(digits[i:i + 5] for i in range(first_length, len(digits), 5))
171+ return "0v" + ".".join(groups)
172+
173+
174+def file_entry(root, file_path):
175+ relative = file_path.relative_to(root)
176+ parts = []
177+ for part in relative.parts:
178+ parts.extend(quote(segment, safe="-_~") for segment in part.split("."))
179+ extension = relative.suffix.removeprefix(".").lower()
180+ if extension not in MIME_TYPES:
181+ raise ValueError(f"no MIME type configured for {relative}")
182+ data = file_path.read_bytes()
183+ mime = Cell(path_noun(MIME_TYPES[extension].split("/")), Cell(len(data), int.from_bytes(data, "little")))
184+ return Cell(path_noun(parts), mime)
185+
186+
187+def make_glob(source, destination):
188+ root = Path(source)
189+ files = sorted((path for path in root.rglob("*") if path.is_file()), key=lambda path: path.as_posix())
190+ if not (root / "index.html").is_file():
191+ raise ValueError("glob source must contain index.html")
192+ glob = 0
193+ for file_path in files:
194+ glob = map_put(glob, file_entry(root, file_path))
195+ serialized = jam_bytes(glob)
196+ glob_hash = scow_uv(sham(serialized))
197+ output = Path(destination)
198+ output.mkdir(parents=True, exist_ok=True)
199+ filename = f"glob-{glob_hash}.glob"
200+ (output / filename).write_bytes(serialized)
201+ (output / "hash").write_text(glob_hash + "\n")
202+ print(filename)
203+
204+
205+if __name__ == "__main__":
206+ if len(sys.argv) != 3:
207+ raise SystemExit(f"usage: {sys.argv[0]} SOURCE_DIRECTORY OUTPUT_DIRECTORY")
208+ make_glob(sys.argv[1], sys.argv[2])
config/ignore_files_on_install.txtnew file+4
@@ -0,0 +1,4 @@
1+.*
2+*.sh
3+*.md
4+*.txt
desk/app/astrolabe.hoonnew file+345
@@ -0,0 +1,345 @@
1+/- *astrolabe
2+/+ *astrolabe, *search, slib=state
3+/+ *mip, naive, default-agent, dbug, agentio
4+/$ udon-to-docu %udon %docu
5+|%
6++$ card card:agent:gall
7+--
8+%- agent:dbug
9+=| state-latest
10+=* state -
11+^- agent:gall
12+=<
13+|_ =bowl:gall
14++* this .
15+ def ~(. (default-agent this %.n) bowl)
16+ hc ~(. +> bowl)
17+::
18+++ on-init
19+ ^- (quip card _this)
20+ :_ this
21+ :~ ug-handler-card:hc
22+ groups-link-patch-card:hc
23+ talk-link-patch-card:hc
24+ az-events-card:hc
25+ ==
26+::
27+++ on-save !>(state)
28+++ on-load
29+ |= old-state=vase
30+ ^- (quip card _this)
31+ =/ old !<(versioned-state old-state)
32+ =/ [cards-01=(list card) new=state-latest]
33+ ?. |(?=(?(%0 %1 %2 %3) -.old) (lth bump.old bump)) `old
34+ (on-naive-state:hc get-nas:hc)
35+ ?> ?=(%4 -.new)
36+ =/ cards=(list card)
37+ :~ ug-handler-card:hc
38+ groups-link-patch-card:hc
39+ talk-link-patch-card:hc
40+ ==
41+ =. cards (weld cards-01 cards)
42+ [cards this(state new)]
43+++ on-poke on-poke:def
44+++ on-watch on-watch:def
45+++ on-leave on-leave:def
46+::
47+++ on-peek
48+ |= =path
49+ ^- (unit (unit cage))
50+ ?+ path (on-peek:def path)
51+ [%x %nop ~]
52+ ``noun+!>(~)
53+ [%x %point @ ~]
54+ ``astrolabe-point+!>((get-point:hc &3.path))
55+ [%x %point @ %spawned ?(~ [spawned-filter ~])]
56+ =/ =ship `@p`(slav %p &3.path)
57+ =/ filter t.t.t.t.path
58+ =/ arg ?~(filter %all -.filter)
59+ ``astrolabe-point-set+!>((get-spawned-points:hc ship arg))
60+ [%x %search %patp @ ?(~ [%narrow ~])]
61+ =/ =search-text (trip &4.path)
62+ ?~ search-text ~|(%search-predicted-too-broad !!)
63+ =/ expand-search =(t.t.t.path ~)
64+ ``astrolabe-point-set+!>((search-opoints-patp:hc search-text expand-search))
65+ [%x %search %sigil @ ~]
66+ =/ =search-text (trip &4.path)
67+ ?~ search-text ~|(%search-predicted-too-broad !!)
68+ ``astrolabe-point-set+!>((search-opoints-sigil:hc search-text))
69+ [%x %peers ~]
70+ =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
71+ ``astrolabe-point-set+!>((turn ~(tap in peers) head))
72+ [%x %chart-data ~]
73+ ``json+!>(get-chart-data:hc)
74+ [%x %doc ^]
75+ =/ doc-path=^path t.t.path
76+ =/ doc (read-doc:hc doc-path)
77+ ?~ doc
78+ `~
79+ ``json+!>(s+u.doc)
80+ ==
81+++ on-agent
82+ |= [=wire =sign:agent:gall]
83+ ^- (quip card _this)
84+ |^
85+ ?+ wire (on-agent:def wire sign)
86+ [%azimuth-events ~] (azimuth-event sign)
87+ ==
88+ ::
89+ ++ azimuth-event
90+ |= =sign:agent:gall
91+ ^- (quip card _this)
92+ ?+ -.sign [~ this]
93+ %watch-ack
94+ ?~ p.sign [~ this]
95+ =/ =tank leaf+"{(trip dap.bowl)} couldn't start listen to %azimuth"
96+ %- (slog tank u.p.sign)
97+ [~ this]
98+ ::
99+ %fact
100+ ?+ p.cage.sign (on-agent:def wire sign)
101+ %naive-diffs
102+ =+ !<(=diff:naive q.cage.sign)
103+ =^ cards state
104+ (on-naive-diff:hc diff)
105+ [cards this]
106+ ::
107+ %naive-state
108+ :: ~& > %received-azimuth-state
109+ :: =+ !<([nas=^state:naive =indices:dice] q.cage.sign)
110+ =+ !<([nas=^state:naive indices=*] q.cage.sign)
111+ =^ cards state
112+ (on-naive-state:hc nas)
113+ [cards this]
114+ ==
115+ ==
116+ --
117+::
118+++ on-arvo on-arvo:def
119+++ on-fail on-fail:def
120+--
121+::
122+:: Helper Core
123+|_ =bowl:gall
124+++ scrio ~(scry agentio bowl)
125+++ az-events-card [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event]
126+++ get-nas .^(^state:naive %gx (scrio %azimuth /nas/noun))
127+++ put-in-fropoints
128+ |= =ship
129+ ^- _state
130+ =. fopoints
131+ (put:ors fopoints (to-q ship) ~)
132+ =. ropoints
133+ (put:ors ropoints (switch-words (to-q ship)) ~)
134+ state
135+::
136+++ put-in-spawn-mip
137+ |= [child=ship nas=^state:naive]
138+ ^- _state
139+ =/ =npoint (get-npoint-from-nas child nas)
140+ (put-in-spawn-mip:slib child npoint state)
141+::
142+++ on-naive-state
143+ |= nas=^state:naive
144+ ^- (quip card _state)
145+ =+ points=points.nas :: =points, eh?
146+ :- ~
147+ |-
148+ ^- _state
149+ ?~ points state
150+ =/ =ship -.n.points
151+ =. state (put-in-fropoints ship)
152+ =. state (put-in-spawn-mip ship nas)
153+ $(points l.points, state $(points r.points))
154+::
155+++ on-naive-diff
156+ |= =diff:naive
157+ ^- (quip card _state)
158+ :: ~& %+ weld "received diff:naive: {<-.diff>}"
159+ :: ?+ -.diff ""
160+ :: %nonce " ship: {<ship.diff>}"
161+ :: %tx " from: {<ship.from.tx.raw-tx.diff>} - {<+<.tx.raw-tx.diff>}"
162+ :: %point " ship: {<ship.diff>} - {<+>-.diff>}"
163+ :: ==
164+ =? state ?=([%tx [* * * %spawn ship *] *] diff)
165+ %- put-in-fropoints
166+ ship.tx.raw-tx.diff
167+ =? state ?=([%point @ %owner *] diff)
168+ %- put-in-fropoints
169+ ship.diff
170+ =? state ?=([%point @ *] diff)
171+ %+ put-in-spawn-mip
172+ ship.diff
173+ get-nas
174+ `state
175+::
176+++ is-spawn-tx
177+ |= =diff:naive
178+ ^- ?
179+ ?= [%tx [* * * %spawn ship *] *] diff
180+::
181+++ get-unpoint-term
182+ |= ship-name=term
183+ .^(unpoint %gx (scrio %azimuth /point/[ship-name]/noun))
184+::
185+++ get-unpoint
186+ |= =ship
187+ (get-unpoint-term (scot %p ship))
188+::
189+++ get-point
190+ |= ship-name=term
191+ ^- point
192+ =/ =ship `@p`(slav %p ship-name)
193+ =/ unpoint (get-unpoint-term ship-name)
194+ :* ship
195+ unpoint
196+ (spa-count ship)
197+ (sponsor-chain ship unpoint)
198+ (probable-dominion ship unpoint)
199+ ==
200+++ get-npoint-from-nas
201+ |= [=ship nas=^state:naive]
202+ ^- npoint
203+ (need (get:orm:naive points.nas ship))
204+::
205+++ run-spawned-points
206+ |* [agg=mold targ=ship nas=^state:naive filter=spawned-filter]
207+ |= f=$-([agg [ship npoint]] agg)
208+ ^- agg
209+ =| =agg
210+ ?. ?=(?(%czar %king) (clan:title targ))
211+ agg
212+ =+ points=points.nas
213+ |-
214+ ^- _agg
215+ ?~ points
216+ agg
217+ =* n -.n.points
218+ =/ gt=? (por:naive targ n)
219+ ?: |(=(targ n) !gt)
220+ $(points r.points)
221+ =? agg =(targ (end (add 2 (xeb (met 3 targ))) n))
222+ $(points r.points)
223+ =/ should-agg
224+ ?& =(targ (^sein:title n))
225+ ?| =(filter %all)
226+ .= =(filter %locked) (is-npoint-locked +.n.points)
227+ ==
228+ ==
229+ =? agg should-agg
230+ (f agg n.points)
231+ $(points l.points)
232+++ get-spawned-points
233+ |= [targ=ship filter=spawned-filter]
234+ =/ nas get-nas
235+ %- (run-spawned-points (list ship) targ nas filter)
236+ |= [agg=(list ship) [spawn=ship *]]
237+ [spawn agg]
238+::
239+++ spa-count
240+ |= targ=ship
241+ %- (run-spawned-points @ud targ get-nas %all)
242+ |= [agg=@ud [spawn=ship *]]
243+ +(agg)
244+::
245+++ search-opoints-patp
246+ |= [=search-text expand-search=?]
247+ ^- (list ship)
248+ =/ search-fulls
249+ ?: expand-search
250+ (search-text-to-fulls search-text)
251+ ~[(search-text-to-full search-text)]
252+ (search-opoints search-fulls)
253+::
254+++ search-opoints-sigil
255+ |= [=search-text]
256+ ^- (list ship)
257+ =/ search-fulls
258+ ~[(sigil-search-text-to-full search-text)]
259+ (search-opoints search-fulls)
260+::
261+++ search-opoints
262+ |= [search-fulls=(list search-full)]
263+ ^- (list ship)
264+ =/ mr-searches (maybe-reverse-searches search-fulls 1.000)
265+ :: ~& (turn mr-searches |=(=mr-search pain.mr-search))
266+ :: ~& mr-searches
267+ ?~ mr-searches ~|(%search-predicted-too-broad !!)
268+ =/ res
269+ %+ roll `(list mr-search)`mr-searches
270+ (run-mr-search fopoints ropoints)
271+ :: ~& "search for {<search-text>}".
272+ :: " returning {<~(wyt by agg.res)>}".
273+ :: " of {<count.res>} results"
274+ (turn ~(tap in agg.res) head)
275+::
276+++ sponsor-chain
277+ |= [=ship =unpoint]
278+ ^- (list ^ship)
279+ ?~ unpoint ~
280+ =/ sponsor sponsor.net.u.unpoint
281+ ?. has.sponsor ~
282+ =/ spon who.sponsor
283+ ?: =(spon ship) ~
284+ :- spon
285+ =/ spon-point (get-unpoint spon)
286+ $(ship spon, unpoint spon-point)
287+::
288+++ probable-dominion
289+ |= [=ship =unpoint]
290+ ^- dominion:naive
291+ ?^ unpoint
292+ dominion.u.unpoint
293+ =/ parent (^sein:title ship)
294+ ?: =(parent ship) %l1
295+ =/ parent-point (get-unpoint parent)
296+ $(ship parent, unpoint parent-point)
297+::
298+++ get-chart-data
299+ ^- json
300+ =/ nas get-nas
301+ =/ main-data
302+ ^- tape
303+ %- ~(rep by spam)
304+ |= [[=ship kids=(map ship tape)] agg=tape]
305+ ^- tape
306+ %+ weld agg
307+ %+ pdata:smel (ship:smel ship)
308+ (flop ~(val by kids))
309+ =/ gal-data
310+ %+ pdata:smel "base"
311+ %+ turn galaxies
312+ |= gal=ship
313+ =/ npoint (get-npoint-from-nas gal nas)
314+ (pdata:smel (pmeta:smel gal npoint) ~)
315+ s+(crip :(weld "[" gal-data main-data "]"))
316+::
317+::
318+++ ug-handler-card
319+ =/ ug-vase !>([/astrolabe '/apps/astrolabe/#'])
320+ [%pass /monkey-ug %agent [our.bowl %monkey] %poke [%bind-ug ug-vase]]
321+++ groups-link-patch-card
322+ =/ catch [%cord '/apps/groups/desk.js']
323+ =/ hatch [%before "."]
324+ =/ patch [/apps/groups catch hatch groups-link-thatch]
325+ =/ patch-kit-vase !>([/groups/astrolabe/link patch])
326+ [%pass /monkey/patch/groups/link %agent [our.bowl %monkey] %poke [%patch patch-kit-vase]]
327+++ talk-link-patch-card
328+ =/ catch [%cord '/apps/talk/desk.js']
329+ =/ hatch [%before "."]
330+ =/ patch [/apps/talk catch hatch groups-link-thatch]
331+ =/ patch-kit-vase !>([/talk/astrolabe/link patch])
332+ [%pass /monkey/patch/talk/link %agent [our.bowl %monkey] %poke [%patch patch-kit-vase]]
333+::
334+++ read-doc
335+ |= =path
336+ =/ doc-path=^path :(weld /doc/usr path /udon)
337+ ^- (unit @t)
338+ ?. .^(? %cu (scrio q.byk.bowl doc-path))
339+ ~
340+ =/ mme=(each manx tang) (mule |.((udon-to-docu .^(@t %cx (scrio q.byk.bowl doc-path)))))
341+ ?: ?=(%.n -.mme)
342+ %- (slog p.mme)
343+ ~
344+ `(crip (en-xml:html p.mme))
345+--
desk/desk.bill+2−1
@@ -1 +1,2 @@
1−~
1+:~ %astrolabe
2+==
desk/desk.docket-0+6−5
@@ -1,9 +1,10 @@
11:~ title+'Astrolabe'
2− info+'An Urbit app that does great things'
3− color+0x2e.4347
4− glob-http+['https://bootstrap.urbit.org/glob-0v5.hurm4.ejod5.ngg9h.iub9i.n1j7o.glob' 0v5.hurm4.ejod5.ngg9h.iub9i.n1j7o]
2+ info+'Your guide to the Urbit constellations. Search for ships, view their info, and explore the Star Chart.'
3+ color+0x1f.243c
4+ image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
5+ glob-http+['https://s3.us-east-2.amazonaws.com/warkgnall.com/urbit/astrolabe/globs/glob-0v4.88rt1.guqn5.nnatp.lca68.ej9aj.glob' 0v4.88rt1.guqn5.nnatp.lca68.ej9aj]
56 base+'astrolabe'
6− version+[0 0 1]
7− website+'https://github.com'
7+ version+[1 0 7]
8+ website+'https://github.com/johnhyde/astrolabe'
89 license+'MIT'
910==
desk/desk.sealnew file+7
@@ -0,0 +1,7 @@
1+:- :~ [%watch & %azimuth /event]
2+ [%reads %gall `%x `%azimuth /]
3+ [%reads %clay `%u ~ /]
4+ [%reads %clay `%x ~ /]
5+ [%reads %ames `%x ~ /peers]
6+ ==
7+~
desk/doc.tocnew file+15
@@ -0,0 +1,15 @@
1+/overview/udon Overview
2+/usr User
3+ /urbit-id/udon What is an Urbit ID?
4+ /galaxy/udon Galaxies
5+ /star/udon Stars
6+ /planet/udon Planets
7+ /moon/udon Moons
8+ /comet/udon Comets
9+ /patpee/udon What is a @p?
10+ /phonemes/udon Phonemes - Prefixes & Suffixes
11+ /sigil/udon What is a Sigil?
12+ /azimuth/udon What is Azimuth?
13+ /l2/udon Layer 1 and Layer 2
14+ /keys/udon Networking Keys
15+ /astrolabe/udon About Astrolabe
desk/doc/overview.udonnew file+26
@@ -0,0 +1,26 @@
1+;>
2+# Astrolabe
3+
4+Currently Astrolabe lets you input `@p`s, Azimuth point numbers, or sigils
5+to look up ships on the network. Use the Star Chart to explore visually.
6+There's also Help Documentation if you want to learn more about how Urbit IDs work.
7+
8+[How to Use Astrolabe](/~/scry/docs/usr/astrolabe/astrolabe.html)
9+
10+## Info
11+
12+- Developer: ~midlev-mindyr
13+- License: MIT
14+- App link: [~dister-midlev-mindyr/astrolabe](web+urbitgraph://~dister-midlev-mindyr/astrolabe)
15+- Source repo: [https://github.com/johnhyde/astrolabe](https://github.com/johnhyde/astrolabe)
16+- Bug reports: [https://github.com/johnhyde/astrolabe/issues](https://github.com/johnhyde/astrolabe/issues)
17+
18+## Feedback
19+
20+If you run into issues with the app, or want to voice comments or concerns,
21+please post in the group `~poster-hoster-midlev-mindyr/astrolabe`, send a DM to `~midlev-mindyr`,
22+or [create an Issue on GitHub](https://github.com/johnhyde/astrolabe/issues).
23+
24+If you see something you want to improve, please fork the [GitHub repository](https://github.com/johnhyde/astrolabe) and make a pull request.
25+
26+Thanks for using Astrolabe. I hope it serves you well.
desk/doc/usr/astrolabe.udonnew file+107
@@ -0,0 +1,107 @@
1+;>
2+# Astrolabe
3+
4+Astrolabe is your one-stop shop for exploring Urbit address space.
5+Currently, Astrolabe lets you type in `@p`s or Azimuth point numbers
6+to look up ships on the network. You can search your contacts and
7+spawned ships with partial or wildcard searches, e.g. `sampel-***net`.
8+
9+There's also Help Documentation if
10+you want to learn more about how Urbit IDs work.
11+
12+## Keyboard Shortcuts
13+
14+- `/` - focus the search bar
15+- `escape` - leave text fields, close help pages, etc
16+- `?` - open the help docs
17+- `s` - switch to sigil search
18+- `p` - switch to patp search
19+- `backspace` or `x` - clear sigil input info
20+
21+
22+## How to Use Search
23+
24+When you type part of a `@p` into the search bar, all your contacts and peers will be
25+searched for IDs matching that string. You can use an asterisk (`*`) to
26+stand in for any character. If you wanted to search for both `~samnec` and `~sarbec`,
27+you could type `~sa**ec`.
28+Contacts are listed before other ships, with a gold border on the left.
29+
30+You can exclude moons or include comets you've met with the toggles in the gold
31+settings box under the search bar.
32+
33+The records of all spawned ships will also be searched, but if the search is
34+too general it will be narrowed in scope, or not run at all. For example,
35+if you search for `***hec`, it will search for all stars under `~hec`,
36+and all planets that match `~***hec-******`, but planets matching `~******-***hec`
37+will be ignored to prevent the search from taking too long.
38+
39+## Sigil Search
40+
41+Clicking the symbol at the right of the search bar opens the Sigil Input Tool.
42+Use the dropdown at the top to select Galaxy, Star, or Planet.
43+Click on a square to start selecting sigil components.
44+
45+Select component categories on the right, and then click the options on the sigil
46+to add them to your search.
47+
48+The ^ button goes up a level so you can select another category.
49+The X button will clear sigil components from the current symbol,
50+except the basic shape. If only the basic shape remains, the X button will clear that.
51+At the top level, the X button will clear all inputs.
52+
53+Your contacts will be searched for any input.
54+When the search has become specific enough,
55+all spawned ships with matching sigils will be shown.
56+If your input is unambiguous, the page for the matching ship will be shown,
57+whether the ship is spawned or not.
58+
59+NOTE: Some sigil inputs are considered ambiguous, even if they exactly match
60+a particular sigil, because further details could match a different sigil.
61+E.g. `~zod` is a circle, but a circle in the suffix position might be intended as a more general search.
62+A future version of Astrolabe will allow you to view the ship
63+whose sigil most closely matches your search, even if the search is ambiguous.
64+
65+Read more about Sigils [here](/~/scry/docs/usr/astrolabe/sigil.html).
66+
67+## The Star Chart
68+
69+Click and drag to move your view; use the mouse wheel to zoom in and out. Click on a dot
70+to see the full Ship View. Enter full `@p`s in the search bar to find a specific point.
71+
72+Please note that stars [locked in the Linear Release Contract](/~/scry/docs/usr/astrolabe/star.html)
73+are not shown in the Star Chart, as they are not yet a part of the network (though they've technically
74+been spawned).
75+
76+The Sponsors/Parents toggle lets you switch between two different views of the network.
77+The default Sponsors view shows each planet under its sponsoring star and each
78+star under its sponsoring galaxy. Planets and Stars without sponsors are not
79+present in this view.
80+The Parent view simply shows each point under the point that spawned it. All spawned points
81+are shown in this view, with the exception of locked stars.
82+In both views, all galaxies are shown for completeness.
83+
84+The All/Peers/Pals selector lets you filter which points are easily visible in the graph.
85+If you select Peers, only those ships that your ship has talked to will be full-colored.
86+If you select Pals, only ships you've added in `%pals` will be full-colored.
87+
88+## Info
89+
90+- Developer: ~midlev-mindyr
91+- License: MIT
92+- App link: [~dister-midlev-mindyr/astrolabe](web+urbitgraph://~dister-midlev-mindyr/astrolabe)
93+- Discussion Group: [~poster-hoster-midlev-mindyr/astrolabe](web+urbitgraph://group/~poster-hoster-midlev-mindyr/astrolabe)
94+- [%quorum](web+urbitgraph://~dister-dister-sidnym-ladrut/quorum) board: ~poster-hoster-midlev-mindyr/astrolabe
95+- Grant/Proposal: [https://urbit.org/grants/astrolabe](https://urbit.org/grants/astrolabe)
96+- Source repo: [https://github.com/johnhyde/astrolabe](https://github.com/johnhyde/astrolabe)
97+- Bug reports: [https://github.com/johnhyde/astrolabe/issues](https://github.com/johnhyde/astrolabe/issues)
98+
99+## Feedback
100+
101+If you run into issues with the app, or want to voice comments or concerns,
102+please post in the group `~poster-hoster-midlev-mindyr/astrolabe`, send a DM to `~midlev-mindyr`,
103+or [create an Issue on GitHub](https://github.com/johnhyde/astrolabe/issues).
104+
105+If you see something you want to improve, please fork the [GitHub repository](https://github.com/johnhyde/astrolabe) and make a pull request.
106+
107+Thanks for using Astrolabe. I hope it serves you well.
desk/doc/usr/azimuth.udonnew file+51
@@ -0,0 +1,51 @@
1+;>
2+# Azimuth
3+
4+_Adapted from [the Glossary](https://urbit.org/docs/glossary/azimuth)_
5+
6+Azimuth is Urbit's identity layer, built as a suite of smart contracts on
7+the Ethereum blockchain and several apps running locally on your
8+[ship](/~/scry/docs/usr/astrolabe/urbit-id.html).
9+It is Urbit's method of securing the digital identities that are required to use
10+the [Arvo](https://urbit.org/docs/glossary/arvo) peer-to-peer network,
11+without the need for a central authority.
12+Azimuth identities (also called points) exist as non-fungible tokens (NFTs),
13+which are owned by Ethereum addresses and can be transferred between such addresses.
14+Identities can use the [claims](https://urbit.org/docs/glossary/claims)
15+contract to make assertions – real-world or otherwise – about their owner.
16+
17+The primary way to interact with Azimuth and to manage your (also called points)
18+is through the [Bridge](https://bridge.urbit.org/) application.
19+Bridge is a front-end for [Ecliptic](https://urbit.org/docs/glossary/ecliptic),
20+the Azimuth contract that's used to perform actions on the blockchain.
21+
22+There are a limited number of Azimuth identities, which results in identities having value.
23+Users are therefore more careful to maintain a good reputation,
24+rather than using identities disposably.
25+Undesirable behavior—spamming, scamming, and malware-spreading—will generally result in
26+a [censure](https://urbit.org/docs/glossary/censure)
27+from [galaxies](/~/scry/docs/usr/astrolabe/galaxy.html)
28+and [stars](/~/scry/docs/usr/astrolabe/star.html), marking the bad actor.
29+
30+It should be noted that Azimuth isn't built just for Arvo.
31+It can be used as an identity system for other projects.
32+
33+## Layer 1 and Layer 2
34+
35+All Azimuth transactions that happen directly on Ethereum (the default) are considered to
36+be on "Layer 1", or L1 for short.
37+However, Ethereum gas prices can make simple L1 transactions like spawning a planet very
38+expensive.
39+In 2021, Tlon developed a "Layer 2" solution (a.k.a. L2) whereby many transactions can be
40+"rolled up" and posted to Ethereum at once, to be validated by each Urbit ship.
41+This dramatically reduces gas costs, and Tlon provides a roller which is free for public use.
42+
43+[Read More about L1 and L2](/~/scry/docs/usr/astrolabe/l2.html)
44+
45+## Further Reading
46+
47+- [The Azimuth concepts page](https://urbit.org/docs/azimuth/azimuth):
48+ A more in-depth explanation of Azimuth.
49+- [Install instructions](https://urbit.org/getting-started/):
50+ Guide to installing Urbit, which includes instructions on booting with your Urbit identity.
51+- [Understanding Urbit ID](https://urbit.org/understanding-urbit/urbit-id)
desk/doc/usr/comet.udonnew file+10
@@ -0,0 +1,10 @@
1+;>
2+[< What is an Urbit ID?](/~/scry/docs/usr/astrolabe/urbit-id.html)
3+
4+# Comets
5+
6+Comets are essentially unlimited, and can be created for free by anyone.
7+They're good for new people exploring the network, operating somewhat anonymously,
8+and testing things out. However, the keys for a comet can't be changed, so it's not
9+not a viable long-term ID. Also, since anyone can boot a comet, comets might be viewed
10+with distrust in some contexts.
desk/doc/usr/galaxy.udonnew file+36
@@ -0,0 +1,36 @@
1+;>
2+[< What is an Urbit ID?](/~/scry/docs/usr/astrolabe/urbit-id.html)
3+
4+# Galaxies
5+
6+Galaxies are the rarest and highest ranking class of Urbit ID, as there are only 256 of them.
7+Together, they form the [Galactic Senate](https://urbit.org/docs/glossary/senate)
8+which has the power make changes to the Urbit ID system.
9+Galaxies help route communication on the network, and each one can spawn 255 [stars](/~/scry/docs/usr/astrolabe/star.html).
10+
11+## @p
12+
13+A galaxy `@p` is just one phoneme, 3 letters. E.g. `~zod`, `~bus`, and `~fes` are galaxies.
14+
15+## Spawning
16+
17+Each galaxy can spawn 255 stars.
18+These stars will have 2-phoneme `@p`s starting with a prefix, and ending with the galaxy's `@p`.
19+E.g. `~zod` spawned `~marzod`, `~binzod`, and `~wanzod`, among others.
20+
21+## Sponsoring
22+
23+A galaxy can sponsor stars and planets, although usually planets are sponsored by stars.
24+By default, a galaxy will sponsor all its children, although it can sponsor others as well.
25+
26+Galaxies perform peer discovery for their sponsees, meaning that they tell other Urbit IDs
27+where they can find their sponsees so that they can communicate directly withthem—similar to
28+asking a DNS server to tell you where to look for google.com.
29+Thus, it is crucially important that galaxies be online and reachable.
30+
31+## The Galactic Senate
32+
33+All galaxy owners are members of the [Galactic Senate](https://urbit.org/docs/glossary/senate).
34+Galaxy owners can [vote](https://urbit.org/docs/glossary/voting) on proposals
35+to upgrade the logic of [Azimuth](/~/scry/docs/usr/astrolabe/azimuth.html)
36+or approve the content of a document.
desk/doc/usr/keys.udonnew file+63
@@ -0,0 +1,63 @@
1+;>
2+# Networking Keys
3+
4+Each Urbit ship proves its identity on the network by signing its messages
5+with a pair of networking keys. By signing a message with a private key,
6+you prove that you are the owner of the associated public key.
7+
8+Since public keys are stored in [Azimuth](/~/scry/docs/usr/astrolabe/azimuth.html),
9+other ships can verify your identity by checking your signatures.
10+
11+## Keyfile
12+
13+A keyfile is just a file containing your networking keys. It is used when
14+booting Arvo (Urbit's OS) for the first time, and the information within
15+is baked into the resulting ship. After this, the keyfile should be deleted
16+to prevent accidentally double-booting in the future.
17+
18+## Changing Keys
19+
20+If your private key is compromised, you can change your keys in Bridge and
21+update your ship by running `|rekey 'new-private-key-goes-here'` in Dojo (the terminal).
22+This will increase your `life` number in Azimuth, which is just a record of how many
23+times your keys have been revised. This number will also be incremented if you
24+set new keys in the course of performing a factory reset.
25+
26+Since keys must be set before a ship can be booted,
27+all active Urbit IDs will have a `life` of at least 1.
28+
29+# Performing a Factory Reset (a.k.a. Breaching)
30+
31+When using the network, ships keep track of the order of all the messages they've sent
32+and received, and use this to ensure that they process messages properly.
33+This is called continuity.
34+
35+Performing a factory reset means breaking continuity—asking the rest of the network
36+to forget your past messages and treat you like a new ship. It also involves clearing
37+your own event log, and consequently all apps and data on the ship.
38+Sometimes this is done to resolve networking errors, like those caused by double-booting.
39+This is also appropriate when transferring an ID to someone else, to give them a fresh start.
40+
41+Because factory resets involve booting a fresh ship, you'll need to rejoin groups,
42+reinstall apps, and generally start from scratch. Trying to continue using your old pier
43+(the folder containing your data) will just cause more networking issues.
44+There are, however, a few unofficial tools with which you can restore some data from
45+a previous pier, and more will likely be built over time.
46+
47+If you want to avoid breaching, avoid double-booting and don't delete your pier.
48+If you want to move your pier (e.g. to host your ship on a remote server), it is
49+crucial that you shut down your ship before you start it somewhere else.
50+If you have two different instances of the same ship trying to run at the same time,
51+other ships will get confused and you will need to breach to communicate with them properly.
52+Deleting your pier, or using an outdated archive of your pier will have the same effect.
53+
54+Breaching, or performing a factory reset, will increment your `rift` number in Azimuth,
55+so this number is an indicator of how many times a ship has breached.
56+Ships which have never breached have a `rift` of 0.
57+
58+# Further Reading
59+
60+- [Setting initial networking keys](https://urbit.org/using/id/using-bridge#set-your-networking-keys)
61+- [Guide to Factory Resets](https://urbit.org/using/id/guide-to-resets)
62+- [Life and Rift](https://urbit.org/docs/azimuth/life-and-rift)
63+- [Glossary: Keyfile](https://urbit.org/docs/glossary/keyfile)
desk/doc/usr/l2.udonnew file+60
@@ -0,0 +1,60 @@
1+;>
2+# What are Layer 1 and Layer 2?
3+
4+## Layer 1 (L1) - Azimuth smart contracts
5+
6+All Azimuth transactions that happen directly on Ethereum (the default) are considered to
7+be on "Layer 1", or L1 for short. The effects of these transactions are calculated and
8+stored on the Ethereum blockchain, so a nontrivial amount of "gas" must be expended for
9+each L1 transaction.
10+
11+## The need for a workaround
12+
13+In 2021, the Ethereum blockchain saw a large increase in traffic which caused
14+gas prices to skyrocket. It became prohibitively expensive for many to spawn or set keys
15+for new IDs, so Tlon started developing a workaround.
16+
17+## Layer 2 (L2) - Naive Rollups
18+
19+Tlon developed a "Layer 2" solution (a.k.a. L2) whereby many transactions can be
20+"rolled up" and posted to Ethereum at once, to be validated by each Urbit ship.
21+
22+The technical term for this is "naive rollups" because transactions are combined and
23+posted to Ethereum without being validated.
24+Each Urbit ship validates the transactions locally to determine who actually owns what.
25+
26+Combining transactions and offloading computation
27+from Ethereum to Urbit dramatically reduces gas costs.
28+Using L2 is so cheap that Tlon provides a free "roller" which is used when performing
29+L2 actions through Bridge.
30+
31+The introduction of L2 also makes it possible for new users to receive and start using
32+Urbit IDs without ever interacting with the blockchain themselves, making Urbit generally
33+more accessible.
34+
35+### Moving to Layer 2
36+
37+Most L2 actions require the ship to first "move to L2", which can be done through Bridge.
38+Technically, this consists of transferring the ID to `0x1111111111111111111111111111111111111111`.
39+This tells the Layer 2 code that the ship is on L2.
40+After that, all Azimuth transactions will be done on L2. It is not possible to move
41+an L2 ship back to L1.
42+
43+For galaxies and stars that want to spawn new IDs on L2, they can move only their spawn proxy
44+to L2. They will remain on L1, but any new children will be on L2. This change is also irreversible.
45+
46+Galaxies cannot move fully to L2, but they can move their spawn proxy to L2. Note, however, that
47+any stars locked in the Linear Release Contract are technically already spawned on L1,
48+and would need to be transferred to L2 separately.
49+
50+### Sponsorship between L1 and L2
51+
52+A sponsor and its sponsee can live on separate layers, but unless they are both on L1,
53+most sponsorship actions will need to take place on L2. This is the situation where an L1 ID
54+can take actions on L2 without moving to L2.
55+
56+# Further Reading
57+
58+- [Technical Overview of L2](https://urbit.org/docs/azimuth/l2/layer2)
59+- [Blog on Naive Rollups](https://urbit.org/blog/rollups)
60+- [Technical Details about which ships can perform which L2 actions](https://urbit.org/docs/azimuth/l2/l2-actions)
desk/doc/usr/moon.udonnew file+8
@@ -0,0 +1,8 @@
1+;>
2+[< What is an Urbit ID?](/~/scry/docs/usr/astrolabe/urbit-id.html)
3+
4+# Moons
5+
6+Moons are created, owned, and controlled by a parent ID, either a galaxy, star, or planet.
7+Moons are free and easy to create, and each Azimuth point can create about 4 billion.
8+Moons can use the Urbit network just like any other ID, but they are inextricably linked to their parents.
desk/doc/usr/patpee.udonnew file+104
@@ -0,0 +1,104 @@
1+;>
2+# What is a @p?
3+
4+First of all, it's pronounced "pat-pee", and we'll see why later.
5+
6+## It's a name
7+
8+Your `@p` is your name on Urbit, and it's designed to be pronounceable.
9+A `@p` starts with a tilde (`~`), and is made up of 3-letter phonemes/syllables, a combination of prefixes and suffixes.
10+There are 256 prefixes, and 256 suffixes.
11+`~zod` and `~bus` are galaxies, so their whole `@p`s are just one suffix.
12+
13+Most `@p`s are made up of 6-letter "words" ( a prefix + a suffix), joined by hyphens.
14+E.g. `~midlev-mindyr` has the prefixes `mid` and `min` and the suffixes `lev` and `dyr`.
15+You can see a list of prefixes and suffixes [here](/~/scry/docs/usr/astrolabe/phonemes.html).
16+
17+## It's a number
18+
19+A `@p` is actually just a way to write a non-negative integer with phonemes.
20+That means every `@p` can be written as a number, and any non-negative integer can be written as a `@p`.
21+- `~zod` is 0 (the first galaxy)
22+- `~marzod` is 256 (the first star)
23+- `~dapnep-ronmyl` is 65536 (256², the first planet)
24+- `~sampel-palnet` is 1624961343.
25+- `~doznec-dozzod-dozzod` is 4294967296 (256⁴, the first moon, moon of `~zod`)
26+
27+For [Azimuth points](/~/scry/docs/usr/astrolabe/azimuth.html), this number is also
28+called the Azimuth number, and it's stored on the blockchain instead of a `@p`.
29+The highest Azimuth number is 256⁴ - 1 (4294967295).
30+Everything above that is a moon, a comet, or not a valid ID number.
31+
32+## It's a username / email address / phone number
33+
34+A `@p` is the unique identifier for a ship, and it's used all throughout Urbit, in the code and in user-facing apps.
35+A `@p` can't be changed.
36+It will always refer to the same Urbit ID, and that Urbit ID will always have the same `@p`.
37+A given ID may change owners, however, and you can always buy a new ID with a new `@p`.
38+
39+## How to tell a ship's class
40+
41+It's easy to tell the class of a ship by looking at its `@p`.
42+- If it's just 1 phoneme, a suffix, like `~zod` or `~bus`, it's a [galaxy](/~/scry/docs/usr/astrolabe/galaxy.html).
43+
44+- If it has 2 phonemes, a prefix and a suffix, like `~marzod` or `~batbus`, it's a [star](/~/scry/docs/usr/astrolabe/star.html).
45+
46+ The suffix of a star `@p` is its parent galaxy, so the parent of `~marzod` is `~zod`, and the parent of `~batbus` is `~bus`.
47+
48+- If it has 4 phonemes separated by a hyphen, like `~sampel-palnet`, it's a [planet](/~/scry/docs/usr/astrolabe/planet.html).
49+
50+ Unlike stars, planet `@p`s don't contain the `@p` of their parent—the names are scrambled
51+ to give each star a good variety of planet `@p`s to spawn.
52+
53+- If it has 8 phonemes, like `~poster-hoster-midlev-mindyr`, it's a [moon](/~/scry/docs/usr/astrolabe/moon.html).
54+
55+ If it has 4 phonemes separated by a caret, like `~midlev^mindyr`, that's an abbreviated moon `@p`.
56+ The last 4 phonemes of a moon `@p` give its parent, and are used in the abbreviation.
57+
58+- If it has 16 phonemes, like `~dirbec-morfeb-ritlys-timsef--padnum-mapnep-nolfel-binzod`,
59+ or more likely 4 phonemes separated by an underscore, like `~dirbec_binzod`,
60+ it's a [comet](/~/scry/docs/usr/astrolabe/comet.html).
61+
62+ You may notice a double hyphen in the center of the full comet `@p`.
63+ This is part of the required syntax of a `@p`, and makes it easier to read.
64+ The abbreviation combines the first and last "words" of the `@p` with an underscore.
65+
66+
67+## The thing about `doz` and `zod`
68+
69+Remember that a `@p` is actually just a way of writing a number.
70+Technically, a `@p` is a base-256 number, as it's composed of prefixes and suffixes,
71+and there are 256 of each. Sure, you could write a number with just prefixes, or just suffixes,
72+but it wouldn't be as interesting to read, and it wouldn't be a valid `@p`.
73+
74+So each phoneme is equivalent to a number, 0-255.
75+`doz` is the prefix version of 0, and `zod` is the suffix version of 0.
76+Just as we usually don't write leading zeroes in our numbers, we omit the equivalent
77+of leading zeroes in `@p`s.
78+This is why you will never see `~dozler`, or `~dozzod-palnet`. It would be sort of like writing `000218` or `0000032591`. It's just `~ler` and `~palnet`. You might, however, run into `~dozler-palnet`, as planet `@p`s are never shortened to 3 phonemes.
79+
80+One place you're likely to see these show up is in moon `@p`s.
81+A moon `@p` is one or two "words" of the parent's choosing, plus the parent's `@p`.
82+E.g. `~sampel-palnet` could create `~dister-sampel-palnet` or `~mister-master-sampel-palnet`,
83+among others.
84+
85+If a galaxy or a star creates a moon,
86+its `@p` must be "padded out" to make up the last 4 phonemes of the moon `@p`.
87+`~ler` could thus create `~faster-dozzod-dozler` or `~poster-hoster-dozzod-dozler`.
88+See how `doz` and `zod` were used to pad out `ler` to 4 phonemes?
89+Similarly, `~palnet` could create `~walrus-dozzod-palnet` or `~doznec-tortul-dozzod-palnet`.
90+
91+## Why is it called a "patpee"?
92+
93+In Hoon, Urbit's typed programming language, numbers can have different "auras",
94+depending on what they represent. Some auras include `@u` for an unsigned integer,
95+`@t` for arbitrary text stored as a number, `@if` for an IPv4 address, and of course
96+`@p` for writing a number in phonemes.
97+
98+So why the "pat"?
99+Hoon involves a lot more symbols than most programming languages (e.g. `.!=?|+:%^$;~-` and so on).
100+To help programmers talk out loud about Hoon, each symbol is associated with a 3-letter phoneme.
101+In this system, `=/` is pronounced "tisfas", `%-` is "cenhep", and, you guessed it, `@` is "pat".
102+
103+In short, `@p` is originally a programming term, the programmers pronounce it "pat-pee",
104+so that's how it's pronounced.
desk/doc/usr/phonemes.udonnew file+45
@@ -0,0 +1,45 @@
1+;>
2+# Phonemes - Prefixes & Suffixes
3+
4+[< What is a @p?](/~/scry/docs/usr/astrolabe/patpee.html)
5+
6+`@p`s are made up of 3-letter phonemes, a mixture of prefixes and suffixes.
7+Here's an exhaustive list of each:
8+
9+## Prefixes
10+
11+`doz` `mar` `bin` `wan` `sam` `lit` `sig` `hid` `fid` `lis` `sog` `dir` `wac` `sab` `wis` `sib`
12+`rig` `sol` `dop` `mod` `fog` `lid` `hop` `dar` `dor` `lor` `hod` `fol` `rin` `tog` `sil` `mir`
13+`hol` `pas` `lac` `rov` `liv` `dal` `sat` `lib` `tab` `han` `tic` `pid` `tor` `bol` `fos` `dot`
14+`los` `dil` `for` `pil` `ram` `tir` `win` `tad` `bic` `dif` `roc` `wid` `bis` `das` `mid` `lop`
15+`ril` `nar` `dap` `mol` `san` `loc` `nov` `sit` `nid` `tip` `sic` `rop` `wit` `nat` `pan` `min`
16+`rit` `pod` `mot` `tam` `tol` `sav` `pos` `nap` `nop` `som` `fin` `fon` `ban` `mor` `wor` `sip`
17+`ron` `nor` `bot` `wic` `soc` `wat` `dol` `mag` `pic` `dav` `bid` `bal` `tim` `tas` `mal` `lig`
18+`siv` `tag` `pad` `sal` `div` `dac` `tan` `sid` `fab` `tar` `mon` `ran` `nis` `wol` `mis` `pal`
19+`las` `dis` `map` `rab` `tob` `rol` `lat` `lon` `nod` `nav` `fig` `nom` `nib` `pag` `sop` `ral`
20+`bil` `had` `doc` `rid` `moc` `pac` `rav` `rip` `fal` `tod` `til` `tin` `hap` `mic` `fan` `pat`
21+`tac` `lab` `mog` `sim` `son` `pin` `lom` `ric` `tap` `fir` `has` `bos` `bat` `poc` `hac` `tid`
22+`hav` `sap` `lin` `dib` `hos` `dab` `bit` `bar` `rac` `par` `lod` `dos` `bor` `toc` `hil` `mac`
23+`tom` `dig` `fil` `fas` `mit` `hob` `har` `mig` `hin` `rad` `mas` `hal` `rag` `lag` `fad` `top`
24+`mop` `hab` `nil` `nos` `mil` `fop` `fam` `dat` `nol` `din` `hat` `nac` `ris` `fot` `rib` `hoc`
25+`nim` `lar` `fit` `wal` `rap` `sar` `nal` `mos` `lan` `don` `dan` `lad` `dov` `riv` `bac` `pol`
26+`lap` `tal` `pit` `nam` `bon` `ros` `ton` `fod` `pon` `sov` `noc` `sor` `lav` `mat` `mip` `fip`
27+
28+## Suffixes
29+
30+`zod` `nec` `bud` `wes` `sev` `per` `sut` `let` `ful` `pen` `syt` `dur` `wep` `ser` `wyl` `sun`
31+`ryp` `syx` `dyr` `nup` `heb` `peg` `lup` `dep` `dys` `put` `lug` `hec` `ryt` `tyv` `syd` `nex`
32+`lun` `mep` `lut` `sep` `pes` `del` `sul` `ped` `tem` `led` `tul` `met` `wen` `byn` `hex` `feb`
33+`pyl` `dul` `het` `mev` `rut` `tyl` `wyd` `tep` `bes` `dex` `sef` `wyc` `bur` `der` `nep` `pur`
34+`rys` `reb` `den` `nut` `sub` `pet` `rul` `syn` `reg` `tyd` `sup` `sem` `wyn` `rec` `meg` `net`
35+`sec` `mul` `nym` `tev` `web` `sum` `mut` `nyx` `rex` `teb` `fus` `hep` `ben` `mus` `wyx` `sym`
36+`sel` `ruc` `dec` `wex` `syr` `wet` `dyl` `myn` `mes` `det` `bet` `bel` `tux` `tug` `myr` `pel`
37+`syp` `ter` `meb` `set` `dut` `deg` `tex` `sur` `fel` `tud` `nux` `rux` `ren` `wyt` `nub` `med`
38+`lyt` `dus` `neb` `rum` `tyn` `seg` `lyx` `pun` `res` `red` `fun` `rev` `ref` `mec` `ted` `rus`
39+`bex` `leb` `dux` `ryn` `num` `pyx` `ryg` `ryx` `fep` `tyr` `tus` `tyc` `leg` `nem` `fer` `mer`
40+`ten` `lus` `nus` `syl` `tec` `mex` `pub` `rym` `tuc` `fyl` `lep` `deb` `ber` `mug` `hut` `tun`
41+`byl` `sud` `pem` `dev` `lur` `def` `bus` `bep` `run` `mel` `pex` `dyt` `byt` `typ` `lev` `myl`
42+`wed` `duc` `fur` `fex` `nul` `luc` `len` `ner` `lex` `rup` `ned` `lec` `ryd` `lyd` `fen` `wel`
43+`nyd` `hus` `rel` `rud` `nes` `hes` `fet` `des` `ret` `dun` `ler` `nyr` `seb` `hul` `ryl` `lud`
44+`rem` `lys` `fyn` `wer` `ryc` `sug` `nys` `nyl` `lyn` `dyn` `dem` `lux` `fed` `sed` `bec` `mun`
45+`lyr` `tes` `mud` `nyt` `byr` `sen` `weg` `fyr` `mur` `tel` `rep` `teg` `pec` `nel` `nev` `fes`
desk/doc/usr/planet.udonnew file+9
@@ -0,0 +1,9 @@
1+;>
2+[< What is an Urbit ID?](/~/scry/docs/usr/astrolabe/urbit-id.html)
3+
4+# Planets
5+
6+There are about 4.3 billion potential planets, so while they are not unlimited, they are
7+widely accessible for the average user. Most people pilot a planet or a moon when using Urbit.
8+As planets are the lowest rank of Azimuth point, they cannot spawn other points. Like galaxies
9+and stars, however, they can create [moons](/~/scry/docs/usr/astrolabe/moon.html).
desk/doc/usr/sigil-input.udonnew file+27
@@ -0,0 +1,27 @@
1+;>
2+# How to Search by Sigil
3+
4+Use the dropdown at the top to select Galaxy, Star, or Planet.
5+Click on an empty square to start selecting sigil components.
6+
7+Select component categories on the right, and then click the options on the sigil
8+to add them to your search.
9+
10+The ^ button goes up a level so you can select another category.
11+The X button will clear sigil components from the current symbol,
12+except the basic shape. If only the basic shape remains, the X button will clear that.
13+At the top level, the X button will clear all inputs.
14+
15+Your contacts will be searched for any input.
16+When the search has become specific enough,
17+all spawned ships with matching sigils will be shown.
18+If your input is unambiguous, the page for the matching ship will be shown,
19+whether the ship is spawned or not.
20+
21+_NOTE:_ Some sigil inputs are considered ambiguous, even if they exactly match
22+a particular sigil, because further details could match a different sigil.
23+E.g. `~zod` is a circle, but a circle in the suffix position might be intended as a more general search.
24+A future version of Astrolabe will allow you to view the ship
25+whose sigil most closely matches your search, even if the search is ambiguous.
26+
27+_See Also:_ [*What is a Sigil?*](/~/scry/docs/usr/astrolabe/sigil.html)
desk/doc/usr/sigil.udonnew file+22
@@ -0,0 +1,22 @@
1+;>
2+# What is a Sigil?
3+
4+It's a default profile picture, based on your [`@p`](/~/scry/docs/usr/astrolabe/patpee.html).
5+Each 3-letter syllable corresponds to a sigil shape, or "symbol".
6+Since planet `@p`s have 4 syllables, a planet sigil is a 2x2 grid of 4 symbols.
7+Star sigils have 2 symbols, and galaxies have just 1.
8+
9+![planet sigils](/apps/astrolabe/sigil-example.png)
10+
11+## Is there a pattern to the sigil shapes?
12+
13+Some syllables which sound similar have similar symbols,
14+but there's no real system to it. The sigil shapes were manually selected and assigned,
15+with a focus on visual design rather than algorithmic purity.
16+
17+## Where did they come from?
18+
19+Tlon saw that Urbit IDs need a visual representation,
20+so they designed a system for generating default avatars.
21+You can read about it in the blog post [_Creating Sigils_](https://urbit.org/blog/creating-sigils).
22+
desk/doc/usr/star.udonnew file+21
@@ -0,0 +1,21 @@
1+;>
2+[< What is an Urbit ID?](/~/scry/docs/usr/astrolabe/urbit-id.html)
3+
4+# Stars
5+
6+Stars are scarce and valuable—a natural fit for businesses, communities, or other organizations.
7+The maximum supply of stars is 65,280.
8+
9+Each star can spawn 65,535 [planets](/~/scry/docs/usr/astrolabe/planet.html), and provides network routing, software updates,
10+and other services for the planets it sponsors.
11+Stars can also choose to de-sponsor planets which abuse the network.
12+
13+Stars are NFTs, but they can be exchanged 1:1 for WSTR at [star.market](https://star.market/),
14+and you can trade fractional amounts of WSTR for ETH [on Uniswap V3](https://info.uniswap.org/#/pools/0x7f4a0822a6e3c085df795a8d78acbcd36f210ebf).
15+
16+## Locked stars and the Linear Release Contract
17+
18+To prevent galaxies from spawning too many stars all at once, many galaxies' stars were locked
19+in an Ethereum contract, to be slowly released (at a linear rate) over time.
20+Stars which are locked in this way will show the contract address as their owner,
21+and any stars held by this contract are considered locked, even if they are ready to be released.
desk/doc/usr/test.udonnew file+24
@@ -0,0 +1,24 @@
1+;>
2+# Test Doc
3+
4+### served from /doc/usr/test/udon
5+
6+# H1
7+
8+## H2
9+
10+### H3
11+
12+#### H4
13+
14+*Urbit IDs* are _*cool*_:
15+- secure
16+- unique
17+- fun to say (_sometimes_)
18+
19+#### Todo
20+
21++ test out markdown
22++ write actual docs
23++ ???
24++ profit
desk/doc/usr/urbit-id.udonnew file+118
@@ -0,0 +1,118 @@
1+;>
2+# What is an Urbit ID?
3+
4+An Urbit ID is like an account on the Urbit network. You can’t use Urbit without an Urbit ID.
5+
6+Every active Urbit ID has a number of properties:
7+
8+- `@p` (pronounced pat-pee) - A permanent, unique identifier, e.g. `~zod` or `~midlev-mindyr`.
9+ This often functions like a username or email address.
10+ Every `@p` is equivalent to a number ≥ 0 and > 256¹⁶, with longer `@p`s giving larger numbers.
11+ The length of a `@p` indicates what class of ID it is.
12+ [Read More](/~/scry/docs/usr/astrolabe/patpee.html)
13+
14+- Public and Private Keys - These allow you to cryptographically prove your ownership of an ID.
15+ This is a bit like having a password for an account, or signing a document.
16+ All messages on Urbit are automatically signed and verified, so you know they’re authentic.
17+
18+- Ship - A ship is an Urbit computer, which is always tied to the Urbit ID it was booted with.
19+ When you boot Urbit for the first time, you provide your keys and we call this booting a “ship”.
20+ When you use this computer on the network, you are “piloting” your “ship”.
21+ Your ship stores all your data and apps.
22+
23+## Azimuth Points
24+
25+An [Azimuth point](/~/scry/docs/usr/astrolabe/azimuth.html) is an Urbit ID which is secured on the Ethereum blockchain.
26+Azimuth points are NFTs, governed by the Azimuth smart contracts.
27+Owning an Azimuth point lets you reset your [keys](/~/scry/docs/usr/astrolabe/keys.html),
28+spawn new IDs, and transfer ownership to someone else, among other things.
29+
30+When Ethereum got popular and transactions became very expensive a
31+[Layer 2 solution](/~/scry/docs/usr/astrolabe/l2.html)
32+was developed (commonly called "L2"), which allows Azimuth points to be spawned, transferred,
33+etc. basically for free.
34+
35+### Spawning and Sponsoring
36+
37+By thinking about possible combinations of syllables, we can predict every Urbit ID that might exist.
38+But galaxies, stars, and planets, being Azimuth points, don't come into existence until
39+a transaction is posted to Azimuth which explicitly "spawns" that point.
40+
41+All 256 galaxies have already been spawned. Those galaxies can spawn stars.
42+Those stars can spawn planets. Planets can't spawn anything, but all Azimuth points can
43+create moons, which is sometimes called "spawning" even though it's not done through Azimuth.
44+
45+By default, a galaxy will sponsor any stars it spawns, and a star will sponsor any planets it spawns.
46+This means helping route network traffic between the sponsee and other ships, providing OTA (over the air)
47+software updates, and potentially providing other services.
48+
49+A sponsee (star or planet) may choose to leave their sponsor and find another one. Conversely,
50+a sponsor (galaxy or star) may choose to de-sponsor their sponsee or sponsor additional points
51+which they didn't spawn. This lets the network hierarchy adjust itself in case someone misbehaves.
52+
53+## Classes of Urbit ID
54+
55+There are 5 classes of Urbit ID:
56+[Galaxy](/~/scry/docs/usr/astrolabe/galaxy.html),
57+[Star](/~/scry/docs/usr/astrolabe/star.html),
58+[Planet](/~/scry/docs/usr/astrolabe/planet.html),
59+[Moon](/~/scry/docs/usr/astrolabe/moon.html), and
60+[Comet](/~/scry/docs/usr/astrolabe/comet.html).
61+
62+Galaxies, stars, and planets are [Azimuth points](/~/scry/docs/usr/astrolabe/azimuth.html),
63+and are therefore very similar anatomically, although they have different roles.
64+Moons and comets are a bit different.
65+
66+### Galaxies (e.g. `~bus`)
67+
68+Galaxies are the rarest and highest ranking class of ID, as there are only 256 of them.
69+Together, they form the Galactic Senate.
70+Galaxies help route communication on the network, and each one can spawn 255 stars.
71+
72+[Read More](/~/scry/docs/usr/astrolabe/galaxy.html)
73+
74+### Stars (e.g. `~batbus`)
75+
76+Stars are fairly scarce and valuable—a natural fit for businesses, communities, or other organizations.
77+Each star can spawn 65,535 planets, and provides network routing, software updates,
78+and other services for the planets it sponsors.
79+Stars can also choose to de-sponsor planets which abuse the network.
80+Stars are NFTs, but they can be exchanged 1:1 for WSTR at [star.market](https://star.market/),
81+and you can trade fractional amounts of WSTR for ETH [on Uniswap V3](https://info.uniswap.org/#/pools/0x7f4a0822a6e3c085df795a8d78acbcd36f210ebf).
82+
83+[Read More](/~/scry/docs/usr/astrolabe/star.html)
84+
85+### Planets (e.g. `~midlev-mindyr`)
86+
87+There are about 4.3 billion potential planets, so while they are not unlimited, they are
88+widely accessible for the average user. Most people pilot a planet or a moon when using Urbit.
89+As planets are the lowest rank of Azimuth point, they cannot spawn other points. Like galaxies
90+and stars, however, they can create moons.
91+
92+[Read More](/~/scry/docs/usr/astrolabe/planet.html)
93+
94+### Moons (e.g. `~poster-hoster-midlev-mindyr` (`~midlev^mindyr` for short))
95+
96+Moons are created, owned, and controlled by a parent ID, either a galaxy, star, or planet.
97+Moons are free and easy to create, and each Azimuth point can create about 4 billion.
98+Moons can use the Urbit network just like any other ID, but they are inextricably linked to their parents.
99+
100+[Read More](/~/scry/docs/usr/astrolabe/moon.html)
101+
102+### Comets (e.g. `~dirbec-morfeb-ritlys-timsef--padnum-mapnep-nolfel-binzod` (`~dirbec_binzod` for short))
103+
104+Comets are essentially unlimited, and can be created for free by anyone.
105+They're good for new people exploring the network, operating somewhat anonymously,
106+and testing things out. However, the keys for a comet can't be changed, so it's not
107+not a viable long-term ID. Also, since anyone can boot a comet, comets might be viewed
108+with distrust in some contexts.
109+
110+[Read More](/~/scry/docs/usr/astrolabe/comet.html)
111+
112+# Further Reading
113+
114+- [A more technical overview](https://urbit.org/using/id/overview)
115+- From the Operator’s Guide:
116+ [Which ID should I buy?](https://operators.urbit.org/guides/which-id-should-i-buy)
117+- [Azimuth FAQ](https://azimuth.network/#faq)
118+- [Understanding Urbit ID](https://urbit.org/understanding-urbit/urbit-id)
desk/lib/astrolabe.hoonnew file+234
@@ -0,0 +1,234 @@
1+/- sur=astrolabe
2+/+ azimuth, naive
3+=< [sur .]
4+=, sur
5+|%
6+++ to-q fein:ob
7+++ to-p fynd:ob
8+++ rev-q
9+ |= a=@
10+ (rev 3 4 a)
11+++ switch-words
12+ |= a=@
13+ (rev 4 2 a)
14+++ add-d
15+ |= [a=@ b=@]
16+ (rev-q (add (rev-q a) b))
17+++ sub-d
18+ |= [a=@ b=@]
19+ (rev-q (sub (rev-q a) b))
20+++ rev-f
21+ |* [f=$-([@ @] *) a=@ b=@]
22+ (f (rev-q a) (rev-q b))
23+++ rpor :: reversed parent order
24+ |= [a=@ b=@]
25+ ^- ?
26+ (rev-f lte a b)
27+::
28+++ ors ((on @q @) rpor)
29+++ or-meta ((on @p tape) por:naive)
30+++ bi-meta
31+ =| a=(map * (map))
32+ |@
33+ ++ put
34+ |* [b=* c=* d=*]
35+ %+ ~(put by a) b
36+ %+ put:or-meta
37+ (~(gut by a) b ~)
38+ [c d]
39+ --
40+::
41++$ page-info [size=_100 page=_0]
42+++ fits-on-page
43+ |= [i=@ =page-info]
44+ ^- ?
45+ =, page-info
46+ ?: =(0 size) %.y
47+ =/ page-start (mul size page)
48+ =/ page-end (add page-start size)
49+ &((gte i page-start) (lth i page-end))
50+::
51+++ is-npoint-locked
52+ |= p=npoint
53+ .= linear-star-release:contracts:azimuth
54+ address.owner.own.p
55+::
56+++ split
57+ |* [nedl=(list) hstk=(list)]
58+ |- ^- (list _hstk)
59+ =/ brk (find nedl hstk)
60+ ?~ brk ~[hstk]
61+ =/ next (add u.brk (lent nedl))
62+ :- (scag u.brk hstk)
63+ $(hstk (slag next hstk))
64+::
65+++ smel
66+ |%
67+ ++ ship
68+ |= =^ship
69+ ^- tape
70+ (slag 1 (scow %p ship))
71+ ++ dom
72+ |= =dominion:naive
73+ ?- dominion
74+ %l1 ""
75+ %l2 "t"
76+ %spawn "s"
77+ ==
78+ ++ spo
79+ |= [self=^ship has=? spo=^ship]
80+ ?. has "~"
81+ =/ parent (^sein:title self)
82+ ?: =(spo parent)
83+ ""
84+ :(weld ":" (ship spo) "!" (ship parent))
85+ ++ pmeta
86+ |= [self=^ship point=npoint]
87+ ^- tape
88+ =/ self-str (ship self)
89+ =/ extras (weld (dom dominion.point) (spo self sponsor.net.point))
90+ ?~ extras self-str
91+ :(weld self-str ":" extras)
92+ ++ spa
93+ |= children=(list tape)
94+ =/ chil-str=tape (zing children)
95+ :: =/ chil-str=tape (reel children |=([a=tape b=tape] (weld a b)))
96+ ?~ chil-str ";"
97+ :(weld "[" chil-str "]")
98+ ++ pdata
99+ |= [pmeta=tape children=(list tape)]
100+ ^- tape
101+ (weld pmeta (spa children))
102+ --
103+::
104+++ nu :: parse number as hex
105+ |= jon=json
106+ ?> ?=([%s *] jon)
107+ (rash p.jon hex)
108+::
109+++ enjs
110+ =, enjs:format
111+ |%
112+ ++ point-set
113+ |= points=(list ^ship)
114+ ^- json
115+ %+ frond %points
116+ :- %a
117+ %+ turn
118+ points
119+ |= who=^ship
120+ (ship who)
121+ ++ point
122+ |= =^point
123+ ^- json
124+ =* unpoint unpoint.point
125+ =* npoint u.unpoint
126+ %+ frond %point
127+ %- pairs :*
128+ ship+(ship ship.point)
129+ spa-count+(numb spa-count.point)
130+ [%probable-dominion s+probable-dominion.point]
131+ [%sponsor-chain a+(turn sponsor-chain.point ship)]
132+ ?~ unpoint ~
133+ :_ ~
134+ :- %npoint
135+ %- pairs :~
136+ [%dominion s+dominion.npoint]
137+ :- %own
138+ =* own own.npoint
139+ %- pairs :~
140+ owner+(address owner.own)
141+ spawn-proxy+(address spawn-proxy.own)
142+ management-proxy+(address management-proxy.own)
143+ voting-proxy+(address voting-proxy.own)
144+ transfer-proxy+(address transfer-proxy.own)
145+ ==
146+ :- %net
147+ =* net net.npoint
148+ %- pairs
149+ :~ rift+(numb rift.net)
150+ :- %keys
151+ %- pairs :~
152+ life+(numb life.keys.net)
153+ suite+(numb suite.keys.net)
154+ auth+(numb auth.keys.net)
155+ crypt+(numb crypt.keys.net)
156+ ==
157+ :- %sponsor
158+ %- pairs :~
159+ has+b+has.sponsor.net
160+ who+(ship who.sponsor.net)
161+ ==
162+ :- %escape
163+ (biff escape.net ship)
164+ ==
165+ ==
166+ ==
167+ ++ address
168+ |= [address=@ux nonce=@ud]
169+ ^- json
170+ %- pairs
171+ :~ address+(numb-ux address)
172+ nonce+(numb nonce)
173+ ==
174+ ++ numb-ux
175+ |= a=@
176+ ^- json
177+ ?~ a ~
178+ s+(remove-dots (scot %ux a))
179+ ++ remove-dots
180+ |= a=@ta
181+ ^- @ta
182+ =+ b=(rip 3 a)
183+ %+ rap 3
184+ |- ^- ^tape
185+ ?~ b
186+ ~
187+ ?: =('.' i.b) $(b t.b)
188+ [i.b $(b t.b)]
189+ --
190+::
191+++ dejs
192+ =, dejs:format
193+ |%
194+ ++ point
195+ |= jon=json
196+ *^point
197+ --
198+::
199+++ share-dejs
200+ =, dejs:format
201+ |%
202+ ++ share
203+ ^- $-(json [%share ship])
204+ (of share+(su ;~(pfix sig fed:ag)) ~)
205+ --
206+::
207+::
208+++ groups-link-thatch
209+'''
210+const buttonText = 'View in Astrolabe';
211+setInterval(() => {
212+ const footer = document.querySelectorAll('section > div > footer')?.[0];
213+ if (!footer) return;
214+ if (footer.children[0].innerText === buttonText) return;
215+ const spans = document.querySelectorAll('section > div > div > div > span > span:first-child');
216+ spans.forEach((span) => {
217+ if (span.innerHTML !== '~') return;
218+ if (footer.children[0].innerText === buttonText) return;
219+ let cspan = span.parentElement;
220+ let patp = cspan.innerText;
221+ let b = document.createElement('a');
222+ b.innerHTML = buttonText;
223+ b.className = 'button';
224+ // b.href = 'web+urbitgraph://astrolabe/ship/' + patp;
225+ b.href = '/apps/astrolabe/#/ship/' + patp;
226+ b.target = '_blank';
227+ b.style.backgroundColor = "navy";
228+ b.style.marginLeft = "1rem";
229+ b.style.color = "white";
230+ footer.prepend(b);
231+ });
232+}, 100);
233+'''
234+--
desk/lib/search.hoonnew file+373
@@ -0,0 +1,373 @@
1+/- sur=astrolabe
2+/+ *astrolabe
3+=< [sur .]
4+=, sur
5+|%
6+++ search-text-chars "*abcdefghilmnoprstuvwxyz"
7+++ trip-syls
8+ |= syls=cord
9+ %+ turn
10+ (rip [0 24] syls)
11+ trip
12+++ prefixes ^~ (trip-syls sis:po)
13+++ suffixes ^~ (trip-syls dex:po)
14+++ full-opts ^~ (gulf .~zod .~fes)
15+++ galaxies ^~ `(list @p)`full-opts
16+++ sanitize-search-text
17+ |= =search-text
18+ ^- tape
19+ %+ skim search-text
20+ |= a=@t
21+ !=(~ (find [a]~ search-text-chars))
22++$ pfsf ?(%prefix %suffix)
23++$ syl [=tape =pfsf]
24++$ search-text tape
25++$ search-syls (list syl) :: in reverse order, e.g. ["net" "pal" "pel" "sam" ~]. length 1-4
26++$ search-syl-opts (list @q)
27++$ search-opts (list search-syl-opts) :: must have length 4
28++$ search-full
29+ $:
30+ =search-syls
31+ =search-opts
32+ ==
33++$ mr-search :: maybe reversed full search
34+ $:
35+ [reversed=? pain=@]
36+ =search-full
37+ ==
38+++ search-syl-to-search-syl-opts
39+ |= search=syl
40+ ^- search-syl-opts
41+ =/ syls
42+ ?: =(%prefix pfsf.search) prefixes suffixes
43+ ?: =(tape.search "***") full-opts
44+ %+ murn syls
45+ |= cand=tape
46+ ?. (tape-matches tape.search cand) ~
47+ %- ?: =(%prefix pfsf.search) ins:po ind:po
48+ (crip cand)
49+++ search-text-to-search-syls
50+ |= search-text=tape
51+ ^- search-syls
52+ =. search-text (sanitize-search-text search-text)
53+ =/ syl-tapes
54+ %+ turn
55+ %- flop
56+ (rip [0 24] (crip search-text)) :: jet doesn't support rip [3 3] which is equivalent
57+ trip
58+ =| count=@
59+ |-
60+ ^- search-syls
61+ =/ =pfsf
62+ ?: =(0 (mod count 2)) %suffix %prefix
63+ ?~ syl-tapes ~
64+ :- [i.syl-tapes pfsf]
65+ $(syl-tapes t.syl-tapes, count +(count))
66+::
67+++ search-syls-to-search-opts
68+ |= =search-syls
69+ ^- search-opts
70+ =/ opts
71+ %+ turn
72+ search-syls
73+ |= =syl
74+ (search-syl-to-search-syl-opts syl)
75+ ?: %+ lien opts
76+ |= syl-opts=search-syl-opts
77+ =(~ syl-opts)
78+ ~[~ ~ ~ ~]
79+ |-
80+ ?: (gte (lent opts) 4)
81+ opts
82+ $(opts (snoc (search-opts opts) (search-syl-opts ~[.~zod])))
83+::
84+++ search-text-to-full
85+ |= search-text=tape
86+ ^- search-full
87+ =/ =search-syls (search-text-to-search-syls search-text)
88+ :- search-syls
89+ (search-syls-to-search-opts search-syls)
90+::
91+++ expand-search-text-to-len
92+ |= [search=tape flen=@ud rotate=?]
93+ =. search (sanitize-search-text search)
94+ |-
95+ ^- (list tape)
96+ =+ slen=(lent search)
97+ ?: =(slen flen)
98+ ~[search]
99+ ?: (gth slen flen) ~
100+ :-
101+ (weld search (reap (sub flen slen) '*'))
102+ ?. rotate ~
103+ $(search (weld "*" search))
104+::
105+++ expand-search-text
106+ |= search=tape
107+ ^- (list tape)
108+ =. search (sanitize-search-text search)
109+ ?~ search ~
110+ =/ rotate
111+ ?& !=('~' i.search)
112+ !(levy (tape search) (cury test '*')) :: is all wild
113+ ==
114+ =/ expand (cury expand-search-text-to-len search)
115+ ;: weld
116+ (expand 3 rotate)
117+ (expand 6 rotate)
118+ (expand 12 rotate)
119+ ==
120+::
121+++ search-text-to-fulls
122+ |= search=tape
123+ ^- (list search-full)
124+ =/ texts (expand-search-text search)
125+ %+ murn
126+ texts
127+ |= text=tape
128+ =/ full (search-text-to-full text)
129+ ?: =(~[~ ~ ~ ~] search-opts.full)
130+ ~
131+ (some full)
132+::
133+++ sigil-search-text-to-full
134+ |= text=tape
135+ ^- search-full
136+ :- ~
137+ ^- search-opts
138+ %- flop
139+ %+ turn
140+ (split "_" text)
141+ |= ints-tape=tape
142+ ^- search-syl-opts
143+ ?: =(ints-tape "any")
144+ full-opts
145+ %+ turn
146+ (split "." ints-tape)
147+ |= =tape
148+ :: ~& tape
149+ `@q`(slav %ud (crip tape))
150+::
151+++ reverse-list :: that is, put first 2 in back "abcd" -> "cdab"
152+ |* lst=(list)
153+ ^- _lst
154+ %+ weld
155+ (slag 2 lst)
156+ (scag 2 lst)
157+++ pad-search-syls
158+ |= =search-syls
159+ ^- ^search-syls
160+ =/ padding (search-text-to-search-syls "dozzoddozzod")
161+ %+ weld
162+ search-syls
163+ (slag (lent search-syls) padding)
164+++ reverse-search
165+ |= search=search-full
166+ ^- search-full
167+ :- (reverse-list (pad-search-syls search-syls.search))
168+ (reverse-list search-opts.search)
169+::
170+++ estimate-search-pain
171+ |= [search=search-full reversed=?]
172+ ^- @
173+ =/ opts search-opts.search
174+ =/ layer-wyt=@rs .255
175+ =, rs
176+ =/ pain
177+ %- :(corl abs:si need toi)
178+ |-
179+ ^- @rs
180+ ?~ opts .0
181+ =/ layer-dropoff
182+ ?: &(reversed =(i.opts `search-syl-opts`~[.~zod]))
183+ .0.99
184+ .0.1694
185+ =/ num-opts (sun (lent i.opts))
186+ =/ cen-opts (div num-opts .255)
187+ =/ num-res (mul cen-opts layer-wyt)
188+ =/ ops (mul (sqt cen-opts) layer-wyt)
189+ %^ fma num-res
190+ %= $
191+ opts t.opts
192+ layer-wyt (mul layer-wyt layer-dropoff)
193+ ==
194+ ops
195+ :: ~& "pain for {<search-syls.search>}: {<pain>}"
196+ pain
197+++ estimate-search-text-pain
198+ |= =search-text
199+ (estimate-search-pain (search-text-to-full search-text) |)
200+++ maybe-reverse-search
201+ |= [search=search-full]
202+ ^- mr-search
203+ =/ search-pain (estimate-search-pain search |)
204+ =/ rsearch (reverse-search search)
205+ =/ rsearch-pain (estimate-search-pain rsearch &)
206+ ?: (gth search-pain rsearch-pain)
207+ [[%.y rsearch-pain] rsearch]
208+ [[%.n search-pain] search]
209+::
210+++ maybe-reverse-searches
211+ |= [searches=(list search-full) pain-thresh=@]
212+ =| pain=@
213+ |-
214+ ^- (list mr-search)
215+ ?~ searches ~
216+ =/ mr (maybe-reverse-search i.searches)
217+ =/ new-pain (add pain pain.mr)
218+ ?: (gth new-pain pain-thresh)
219+ $(searches t.searches)
220+ :- mr
221+ $(searches t.searches, pain new-pain)
222+::
223+++ tape-matches
224+ |= [search=tape cand=tape]
225+ ^- ?
226+ |-
227+ ?~ search %.y
228+ ?~ cand %.n
229+ ?: |(=(i.search i.cand) =(i.search '*'))
230+ $(search t.search, cand t.cand)
231+ %.n
232+++ tosd
233+ |= [a=@ =pfsf]
234+ %- ?:(=(%prefix pfsf) tos:po tod:po)
235+ a
236+++ syl-equals
237+ |= [=syl cand=@]
238+ =((crip tape.syl) (tosd cand pfsf.syl))
239+++ syl-matches
240+ |= [=syl cand=@]
241+ :: todo check cand < 256
242+ (tape-matches tape.syl (trip (tosd cand pfsf.syl)))
243+++ syl-opts-match
244+ |= [opts=search-syl-opts cand=@]
245+ !=(~ (find ~[cand] opts))
246+++ matches-search-syls
247+ |= [search=search-syls cand-atom=@]
248+ =/ cand (rip 3 cand-atom)
249+ |-
250+ ^- ?
251+ ?~ search =(cand ~)
252+ :: =? cand =(cand ~)
253+ :: `(lest @)`~[0]
254+ ?~ cand
255+ :: ?& (syl-equals i.search 0)
256+ ?& (syl-matches i.search 0)
257+ $(search t.search)
258+ ==
259+ :: =/ nncand ^- (lest @)
260+ :: ?~ cand ~[0] cand
261+ ?& (syl-matches i.search i.cand)
262+ $(search t.search, cand t.cand)
263+ ==
264+::
265+++ matches-search-opts
266+ |= [search=search-opts cand-atom=@]
267+ =/ cand (rip 3 cand-atom)
268+ |-
269+ ^- ?
270+ ?~ search =(cand ~)
271+ ?~ cand
272+ ?& (syl-opts-match i.search 0)
273+ $(search t.search)
274+ ==
275+ :: =/ nncand ^- (lest @)
276+ :: ?~ cand ~[0] cand
277+ ?& (syl-opts-match i.search i.cand)
278+ $(search t.search, cand t.cand)
279+ ==
280+::
281+++ matches-search
282+ |= [search=search-full cand-atom=@]
283+ ?~ search-syls.search
284+ (matches-search-opts search-opts.search cand-atom)
285+ (matches-search-syls search-syls.search cand-atom)
286+::
287+++ opts-in-range
288+ |= [lb=@ rb=@ opts=(list @)]
289+ ^- search-syl-opts
290+ %+ skim
291+ opts
292+ |= a=@
293+ &((lte lb a) (gte rb a))
294+::
295+++ range-contains-search
296+ |= [lb=@ rb=@ search=search-opts] :: includes lb and rb - [lb, rb]
297+ ^- [opts=search-opts contains=?]
298+ ?~ search `%.y
299+ =/ s-0 i.search
300+ ?~ s-0 `%.n
301+ =/ lb-0 (end 3 lb)
302+ =/ rb-0 (end 3 rb)
303+ =/ cand-0 (opts-in-range lb-0 rb-0 s-0)
304+ =/ cand-0-len (lent cand-0)
305+ :: ~& "lb: {<`@q`lb>} rb: {<`@q`rb>} cand-len: {<cand-0-len>}"
306+ ?: =(0 cand-0-len) `%.n
307+ =. i.search cand-0
308+ :_
309+ ?: (gte cand-0-len 3) %.y :: entire valid galaxy is bounded
310+ =/ lb-clean =(0 (rsh 3 lb)) :: lb is galaxy
311+ =/ rb-clean =(0 (rsh 3 (add-d rb 1))) :: rb is last planet in galaxy
312+ ?: &(=(2 cand-0-len) |(lb-clean rb-clean))
313+ %.y :: two galaxies, and one is right on the edge (fully bounded)
314+ |-
315+ ^- ?
316+ ?~ cand-0 %.n
317+ =/ crb (add .~fipfes-fipzod i.cand-0)
318+ =/ slb (rev-q (rev-f max lb i.cand-0))
319+ =/ srb (rev-q (rev-f min rb crb))
320+ =/ sub-contains +:^$(lb (rsh 3 slb), rb (rsh 3 srb), search t.search)
321+ ?: sub-contains %.y
322+ $(cand-0 t.cand-0)
323+ search
324+::
325+++ run-wildcard-search
326+ |* [agg=mold search=search-full points=opoints =page-info]
327+ |= [res=[count=@ =agg] f=$-([agg @q] agg)]
328+ ^- [count=@ =agg]
329+ =/ s-opts search-opts.search
330+ =+ [llb=.~zod rrb=`@q`(sub (bex 32) 1)]
331+ :: =< +
332+ |-
333+ ^- _res
334+ ?~ points
335+ :: ~& "nothing here, going up"
336+ res
337+ :: ?: (gth count.res 1.000) ~|(%search-too-broad !!)
338+ =/ n=@q `@`-.n.points
339+ =/ lrb=@q ?: (gth n 0) (sub-d n 1) .~zod
340+ =/ rlb=@q (add-d n 1)
341+ =/ left-view (range-contains-search llb lrb s-opts)
342+ :: ~& "arrived at {<n>}"
343+ :: ~& "left side: {<?:(contains.left-view 'Y' 'N')>} {<llb>} - {<lrb>}"
344+ =/ right-view (range-contains-search rlb rrb s-opts)
345+ =? res contains.left-view
346+ $(points l.points, rrb lrb, s-opts opts.left-view)
347+ =? res (matches-search search n)
348+ =/ fits (fits-on-page count.res page-info)
349+ =. count.res +(count.res)
350+ =? agg.res fits (f agg.res n)
351+ res
352+ :: ~& "right side: {<?:(contains.right-view 'Y' 'N')>} {<rlb>} - {<rrb>}"
353+ =? res contains.right-view
354+ $(points r.points, llb rlb, s-opts opts.right-view)
355+ res
356+::
357+++ run-mr-search
358+ |= [fopoints=opoints ropoints=opoints]
359+ |= [search=mr-search res=[count=@ agg=opoints]]
360+ ^- _res
361+ =/ points ?: reversed.search ropoints fopoints
362+ =. res
363+ %+ (run-wildcard-search _agg.res search-full.search points [0 0])
364+ res
365+ |= [agg=_agg.res result=@q]
366+ =? result reversed.search
367+ (switch-words result)
368+ =. result (to-p result)
369+ (put:((on @ @) gte) agg result ~)
370+ :: ~& "returning {<~(wyt by agg.res)>} of {<count.res>} results"
371+ res
372+::
373+--
desk/lib/state.hoonnew file+21
@@ -0,0 +1,21 @@
1+/- *astrolabe
2+/+ *astrolabe
3+|%
4+++ put-in-spawn-mip
5+ |= [child=ship =npoint s=state-latest]
6+ ^- _s
7+ =/ parent (^sein:title child)
8+ ?: =(parent child) s
9+ ?: (is-npoint-locked npoint) s
10+ =/ pdata (pdata:smel (pmeta:smel child npoint) ~)
11+ =. spam.s (~(put bi spam.s) parent child pdata)
12+ =* spo sponsor.net.npoint
13+ =/ prev (~(gut by prev-spo.s) child parent)
14+ =. prev-spo.s (~(del by prev-spo.s) child)
15+ =? spam.s !=(parent prev)
16+ (~(del bi spam.s) prev child)
17+ ?. has.spo s
18+ =. prev-spo.s (~(put by prev-spo.s) child who.spo)
19+ =. spam.s (~(put bi spam.s) who.spo child pdata)
20+ s
21+--
desk/mar/astrolabe/point-set.hoonnew file+13
@@ -0,0 +1,13 @@
1+/+ *astrolabe
2+|_ points=(list ship)
3+++ grab
4+ |%
5+ ++ noun (list ship)
6+ --
7+++ grow
8+ |%
9+ ++ noun points
10+ ++ json (point-set:enjs points)
11+ --
12+++ grad %noun
13+--
desk/mar/astrolabe/point.hoonnew file+14
@@ -0,0 +1,14 @@
1+/+ *astrolabe
2+|_ pnt=point
3+++ grab
4+ |%
5+ ++ noun point
6+ :: ++ json point:dejs
7+ --
8+++ grow
9+ |%
10+ ++ noun pnt
11+ ++ json (point:enjs pnt)
12+ --
13+++ grad %noun
14+--
desk/mar/ico.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ dat=@
2+++ grow
3+ |%
4+ ++ mime [/image/x-icon (as-octs:mimes:html dat)]
5+ --
6+++ grab
7+ |%
8+ ++ mime |=([p=mite q=octs] q.q)
9+ ++ noun @
10+ --
11+++ grad %mime
12+--
desk/mar/seal.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ dat=@
2+++ grow
3+ |%
4+ ++ mime [/text (as-octs:mimes:html dat)]
5+ --
6+++ grab
7+ |%
8+ ++ mime |=([p=mite q=octs] q.q)
9+ ++ noun @
10+ --
11+++ grad %mime
12+--
desk/mar/webmanifest.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ dat=@
2+++ grow
3+ |%
4+ ++ mime [/application/'manifest+json' (as-octs:mimes:html dat)]
5+ --
6+++ grab
7+ |%
8+ ++ mime |=([p=mite q=octs] q.q)
9+ ++ noun @
10+ --
11+++ grad %mime
12+--
desk/sur/astrolabe.hoonnew file+58
@@ -0,0 +1,58 @@
1+/+ naive, *mip
2+|%
3++$ versioned-state
4+ $% state-0
5+ state-1
6+ state-2
7+ state-3
8+ state-4
9+ ==
10++$ state-latest state-4
11++$ state-0
12+ $: %0
13+ spa=spawned
14+ ==
15++$ state-1
16+ $: %1
17+ fropoints-1
18+ ==
19++$ state-2
20+ $: %2
21+ spam=spawn-mip
22+ fropoints-1
23+ ==
24++$ state-3
25+ $: %3
26+ prev-spo=(map ship ship) :: so we know who the old sponsor was when sponsor changes
27+ spam=spawn-mip
28+ fropoints-1
29+ ==
30++$ state-4
31+ $: %4
32+ bump=_1 :: bump to update state without changing the type
33+ prev-spo=(map ship ship) :: so we know who the old sponsor was when sponsor changes
34+ spam=spawn-mip
35+ fropoints-1
36+ ==
37+::
38++$ fropoints-1
39+ $: fopoints=opoints :: forward ordered points
40+ ropoints=opoints :: reversed ordered points, e.g. .~palnet-sampel
41+ ==
42++$ spawned (jug ship ship)
43++$ spawn-mip (mip ship ship tape)
44++$ opoints (tree [@ @]) :: ordered points [ship ~]
45+::
46++$ npoint point:naive
47+++ unpoint (unit npoint)
48++$ point
49+ $: =ship
50+ =unpoint
51+ spa-count=@ud
52+ sponsor-chain=(list ship)
53+ probable-dominion=dominion:naive
54+ ==
55++$ spawned-filter ?(%all %locked %unlocked)
56+::
57+::
58+--
desk/sys.kelvin+1−1
@@ -1 +1 @@
1−[%zuse 419]
1+[%zuse 408]
desk/tests/lib/state.hoonnew file+59
@@ -0,0 +1,59 @@
1+/- *astrolabe
2+/+ *astrolabe, *state, *mip, *test
3+|%
4+++ test-put-in-spawn-mip
5+ =| state=state-latest
6+ =/ moved-to-bus=npoint
7+ =< .(who.sponsor.net ~bus)
8+ *npoint
9+ =/ moved-to-hec=npoint
10+ =< .(who.sponsor.net ~hec)
11+ *npoint
12+ ;: weld
13+ %+ expect-eq
14+ !> :-
15+ `~zod :: new prev-spo
16+ "marzod:s;" :: spawn-mip has something under ~zod
17+ !> =- :-
18+ (~(get by prev-spo) ~marzod)
19+ (~(gut bi spam) ~zod ~marzod "nothing!")
20+ (put-in-spawn-mip ~marzod *npoint state)
21+ ::
22+ %+ expect-eq
23+ !> :*
24+ `~hec :: new prev-spo
25+ "marzod:s:hec!zod;" :: under ~zod
26+ %.n :: nothing under ~bus
27+ "marzod:s:hec!zod;" :: under ~hec
28+ ==
29+ !>
30+ =. state (put-in-spawn-mip ~marzod *npoint state)
31+ =. state (put-in-spawn-mip ~marzod moved-to-bus state)
32+ =+ (put-in-spawn-mip ~marzod moved-to-hec state)
33+ :*
34+ (~(get by prev-spo) ~marzod)
35+ (~(gut bi spam) ~zod ~marzod "nothing!")
36+ (~(has by spam) ~bus)
37+ (~(gut bi spam) ~hec ~marzod "nothing!")
38+ ==
39+ ::
40+ %+ expect-eq
41+ !> :*
42+ `~zod :: new prev-spo
43+ "marzod:s;" :: under ~zod
44+ %.n :: nothing under ~bus
45+ %.n :: nothing under ~hec
46+ ==
47+ !>
48+ =. state (put-in-spawn-mip ~marzod *npoint state)
49+ =. state (put-in-spawn-mip ~marzod moved-to-bus state)
50+ =. state (put-in-spawn-mip ~marzod moved-to-hec state)
51+ =+ (put-in-spawn-mip ~marzod *npoint state)
52+ :*
53+ (~(get by prev-spo) ~marzod)
54+ (~(gut bi spam) ~zod ~marzod "nothing!")
55+ (~(has by spam) ~bus)
56+ (~(has by spam) ~hec)
57+ ==
58+ ==
59+--
docs-dev-desk/lib/docs.hoonnew file+205
@@ -0,0 +1,205 @@
1+/- *gemtext
2+|%
3+:: mark conversion functions
4+::
5+++ to-docu
6+ |%
7+ :: convert %gmi mark to %docu mark
8+ ::
9+ ++ gmi
10+ |= gem=(list gmni)
11+ ^- manx
12+ |^
13+ =/ res (reel gem process)
14+ ;div
15+ ;* ?~ acc.res out.res
16+ [(make-list acc.res) out.res]
17+ ==
18+ :: Convert each gmni type to manx
19+ :: and accumulate
20+ ::
21+ ++ process
22+ |= [g=gmni acc=(list @t) out=marl]
23+ ?- -.g
24+ %list [[text.g acc] out]
25+ ::
26+ %text
27+ :- ~
28+ ?: =('' text.g)
29+ ?~(acc out [(make-list acc) out])
30+ :_ ?~(acc out [(make-list acc) out])
31+ ^- manx
32+ ;p: {(trip text.g)}
33+ ::
34+ %link
35+ :- ~
36+ :_ ?~(acc out [(make-list acc) out])
37+ ^- manx
38+ ;p
39+ ;a/"{(trip link.g)}"
40+ ;+ ;/ ?~ text.g
41+ (trip link.g)
42+ (trip u.text.g)
43+ ==
44+ ==
45+ ::
46+ %code
47+ :- ~
48+ :_ ?~(acc out [(make-list acc) out])
49+ ^- manx
50+ ?~ alt.g
51+ ;pre: {(trip text.g)}
52+ ;pre(class (lang u.alt.g)): {(trip text.g)}
53+ ::
54+ %quot
55+ :- ~
56+ :_ ?~(acc out [(make-list acc) out])
57+ ^- manx
58+ ;blockquote: {(trip text.g)}
59+ ::
60+ %head
61+ :- ~
62+ :_ ?~(acc out [(make-list acc) out])
63+ ^- manx
64+ ?- lvl.g
65+ %1 ;h1: {(trip text.g)}
66+ %2 ;h2: {(trip text.g)}
67+ %3 ;h3: {(trip text.g)}
68+ ==
69+ ==
70+ :: convert codeblock alt-text to
71+ :: language class
72+ ::
73+ ++ lang
74+ |= =@t
75+ ^- tape
76+ %+ scan (cass (trip t))
77+ %+ funk "language-"
78+ (star ;~(pose aln (cold '-' next)))
79+ :: assemble individual bullet points
80+ ::
81+ ++ make-list
82+ |= items=(list @t)
83+ ^- manx
84+ ;ul
85+ ;* %+ turn items
86+ |= =@t
87+ ^- manx
88+ ;li: {(trip t)}
89+ ==
90+ --
91+ ::
92+ :: convert a %txt mark to a %docu mark
93+ ::
94+ ++ txt
95+ |= tex=wain
96+ ^- manx
97+ |^
98+ ;div
99+ ;* (turn (make-paras tex) reprocess)
100+ ==
101+ :: useful parsing short-hands
102+ ::
103+ ++ eof ;~(less next (easy ~))
104+ ++ white (mask "\09 ")
105+ ++ blank ;~(plug (star white) (just '\0a'))
106+ ++ hard-wrap (cold ' ' ;~(plug blank (star white)))
107+ ++ one-space (cold ' ' (plus white))
108+ :: first pass of text file, separating and
109+ :: processing paragraphs
110+ ::
111+ ++ make-paras
112+ |= tex=wain
113+ ^- (list tape)
114+ %+ rash
115+ (of-wain:format tex)
116+ ;~ pose
117+ (cold ~ (full empty))
118+ ;~(pfix (star empty) (plus para))
119+ ==
120+ :: separate paragraph from text body,
121+ :: rejoining hard-wrapped lines,
122+ :: collapsing whitespace and removing
123+ :: leading/trailing whitespace
124+ ::
125+ ++ para
126+ %+ ifix
127+ [(star white) empty]
128+ %- plus
129+ ;~ less
130+ empty
131+ ;~ pose
132+ hard-wrap
133+ one-space
134+ next
135+ ==
136+ ==
137+ :: breaks between paragraphs
138+ ::
139+ ++ empty
140+ ;~ pose
141+ ;~(plug blank (plus blank))
142+ ;~(plug (star white) eof)
143+ ;~(plug blank (star white) eof)
144+ ==
145+ :: reprocess a paragraph, distinguishing
146+ :: code and plaintext
147+ ::
148+ ++ reprocess
149+ |= =tape
150+ ^- manx
151+ ;p
152+ ;* %+ scan tape
153+ (plus ;~(pose code plain))
154+ ==
155+ :: inline plaintext
156+ ::
157+ ++ plain
158+ %+ cook
159+ |= =tape
160+ ^- manx
161+ ;/(tape)
162+ %- plus
163+ ;~ less
164+ code
165+ next
166+ ==
167+ :: convert atoms, arms and wings to inline code
168+ ::
169+ ++ code
170+ %+ cook
171+ |= =tape
172+ ^- manx
173+ ;code
174+ ;+ ;/(tape)
175+ ==
176+ ;~ pose
177+ :: ++arm +*of +$some:kind
178+ ::
179+ ;~ plug
180+ lus ;~(pose lus buc tar)
181+ low (star ;~(pose nud low hep col))
182+ ==
183+ :: non-constant atom
184+ ::
185+ %+ cook |=(=coin ~(rend co coin))
186+ ;~ simu
187+ ;~ pose
188+ sig dot hep
189+ ;~(plug (just '0') low)
190+ ==
191+ nuck:so
192+ ==
193+ :: .wing.refence
194+ ::
195+ %+ cook |=(=(list tape) `tape`(zing list))
196+ %- plus
197+ ;~(plug dot low (star ;~(pose nud low hep)))
198+ :: %constant
199+ ::
200+ %+ cook |=(=coin "%{~(rend co coin)}")
201+ ;~(pfix cen nuck:so)
202+ ==
203+ --
204+ --
205+--
docs-dev-desk/lib/gemtext.hoonnew file+190
@@ -0,0 +1,190 @@
1+/- gem=gemtext
2+|%
3+++ encode
4+ |= in=(list gmni:gem)
5+ ^- @t
6+ %+ roll in
7+ |= [=gmni:gem out=@t]
8+ ?- -.gmni
9+ %text ;:((cury cat 3) out text.gmni '\0a')
10+ %quot ;:((cury cat 3) out '> ' text.gmni '\0a')
11+ %list ;:((cury cat 3) out '* ' text.gmni '\0a')
12+ %link
13+ ?~ text.gmni
14+ ;:((cury cat 3) out '=> ' link.gmni '\0a')
15+ ;:((cury cat 3) out '=> ' link.gmni ' ' u.text.gmni '\0a')
16+ %head
17+ ?- lvl.gmni
18+ %1 ;:((cury cat 3) out '# ' text.gmni '\0a')
19+ %2 ;:((cury cat 3) out '## ' text.gmni '\0a')
20+ %3 ;:((cury cat 3) out '### ' text.gmni '\0a')
21+ ==
22+ %code
23+ ?~ alt.gmni
24+ ;:((cury cat 3) out '```\0a' text.gmni '\0a```\0a')
25+ ;:((cury cat 3) out '```' u.alt.gmni '\0a' text.gmni '\0a```\0a')
26+ ==
27+++ decode
28+ |= in=cord
29+ |^ ^- (list gmni:gem)
30+ %+ rash in
31+ (star ;~(pose quote bullet link codeblock header plain))
32+ ::
33+ ++ tab (jest '\09')
34+ ++ crlf (jest '\0d\0a')
35+ ++ lf (jest '\0a')
36+ ++ white ;~(pose ace tab)
37+ ++ le ;~(pose crlf lf)
38+ ++ plus-line
39+ (plus ;~(less le-or-end next))
40+ ++ star-line
41+ (star ;~(less le-or-end next))
42+ ++ le-or-end
43+ ;~ plug
44+ (star white)
45+ ;~(pose le ;~(less next (easy ~)))
46+ ==
47+ ++ url-text
48+ %- plus
49+ ;~ pose
50+ ;~(pose aln hep cab dot sig)
51+ (mask "/?:#%&+!$'()*,;=@[]")
52+ ==
53+ ::
54+ ++ plain
55+ ;~ less
56+ ;~(less next (easy ~))
57+ (stag %text ;~(sfix (cook crip star-line) le-or-end))
58+ ==
59+ ::
60+ ++ quote
61+ %+ stag %quot
62+ %+ ifix
63+ [;~(plug gar (star white)) le-or-end]
64+ (cook crip plus-line)
65+ ::
66+ ++ bullet
67+ %+ stag %list
68+ %+ ifix
69+ [;~(plug tar (plus white)) le-or-end]
70+ (cook crip plus-line)
71+ ::
72+ ++ header
73+ %+ stag %head
74+ ;~ plug
75+ ;~ pose
76+ (cold %3 (jest '###'))
77+ (cold %2 (jest '##'))
78+ (cold %1 hax)
79+ ==
80+ %+ ifix
81+ [(star white) le-or-end]
82+ (cook crip (plus ;~(less le-or-end next)))
83+ ==
84+ ::
85+ ++ link
86+ %+ stag %link
87+ ;~ pfix
88+ ;~(plug tis gar (plus white))
89+ ;~ plug
90+ (cook crip url-text)
91+ ;~ pose
92+ (cold ~ le-or-end)
93+ (stag ~ (ifix [(plus white) le-or-end] (cook crip plus-line)))
94+ ==
95+ ==
96+ ==
97+ ::
98+ ++ codeblock
99+ %+ stag %code
100+ %+ ifix
101+ :- ;~(plug (jest '```') (star white))
102+ ;~ pose
103+ ;~(plug (jest '```') star-line le-or-end)
104+ ;~(plug le (jest '```') star-line le-or-end)
105+ ;~(less next (easy ~))
106+ ==
107+ ;~ plug
108+ ;~ pose
109+ (cold ~ le-or-end)
110+ (stag ~ ;~(sfix (cook crip plus-line) le-or-end))
111+ ==
112+ ;~ pose
113+ ;~(simu ;~(plug (jest '```') star-line le-or-end) (easy ''))
114+ %+ cook crip
115+ %- star
116+ ;~ less
117+ ;~ pose
118+ ;~(plug le (jest '```') star-line le-or-end)
119+ ;~(less next (easy ~))
120+ ==
121+ ;~(pose (cold '\0a' crlf) next)
122+ ==
123+ ==
124+ ==
125+ --
126+::
127+++ gmi-join
128+ |= [ali=(urge:clay gmni:gem) bob=(urge:clay gmni:gem)]
129+ ^- (unit (urge:clay gmni:gem))
130+ |^
131+ =. ali (clean ali)
132+ =. bob (clean bob)
133+ |- ^- (unit (urge:clay gmni:gem))
134+ ?~ ali `bob
135+ ?~ bob `ali
136+ ?- -.i.ali
137+ %&
138+ ?- -.i.bob
139+ %&
140+ ?: =(p.i.ali p.i.bob)
141+ %+ bind $(ali t.ali, bob t.bob)
142+ |=(cud=(urge:clay gmni:gem) [i.ali cud])
143+ ?: (gth p.i.ali p.i.bob)
144+ %+ bind $(p.i.ali (sub p.i.ali p.i.bob), bob t.bob)
145+ |=(cud=(urge:clay gmni:gem) [i.bob cud])
146+ %+ bind $(ali t.ali, p.i.bob (sub p.i.bob p.i.ali))
147+ |=(cud=(urge:clay gmni:gem) [i.ali cud])
148+ ::
149+ %|
150+ ?: =(p.i.ali (lent p.i.bob))
151+ %+ bind $(ali t.ali, bob t.bob)
152+ |=(cud=(urge:clay gmni:gem) [i.bob cud])
153+ ?: (gth p.i.ali (lent p.i.bob))
154+ %+ bind $(p.i.ali (sub p.i.ali (lent p.i.bob)), bob t.bob)
155+ |=(cud=(urge:clay gmni:gem) [i.bob cud])
156+ ~
157+ ==
158+ ::
159+ %|
160+ ?- -.i.bob
161+ %|
162+ ?. =(i.ali i.bob)
163+ ~
164+ %+ bind $(ali t.ali, bob t.bob)
165+ |=(cud=(urge:clay gmni:gem) [i.ali cud])
166+ ::
167+ %&
168+ ?: =(p.i.bob (lent p.i.ali))
169+ %+ bind $(ali t.ali, bob t.bob)
170+ |=(cud=(urge:clay gmni:gem) [i.ali cud])
171+ ?: (gth p.i.bob (lent p.i.ali))
172+ %+ bind $(ali t.ali, p.i.bob (sub p.i.bob (lent p.i.ali)))
173+ |=(cud=(urge:clay gmni:gem) [i.ali cud])
174+ ~
175+ ==
176+ ==
177+ ++ clean
178+ |= wig=(urge:clay gmni:gem)
179+ ^- (urge:clay gmni:gem)
180+ ?~ wig ~
181+ ?~ t.wig wig
182+ ?: ?=(%& -.i.wig)
183+ ?: ?=(%& -.i.t.wig)
184+ $(wig [[%& (add p.i.wig p.i.t.wig)] t.t.wig])
185+ [i.wig $(wig t.wig)]
186+ ?: ?=(%| -.i.t.wig)
187+ $(wig [[%| (welp p.i.wig p.i.t.wig) (welp q.i.wig q.i.t.wig)] t.t.wig])
188+ [i.wig $(wig t.wig)]
189+ --
190+--
docs-dev-desk/mar/clue.hoonnew file+28
@@ -0,0 +1,28 @@
1+/- docs
2+|_ clu=wain
3+++ grab
4+ |%
5+ ++ mime
6+ |= [=mite len=@ud tex=@]
7+ ^- wain
8+ =+ !<(clue:docs (slap !>(~) (ream tex)))
9+ (to-wain:format tex)
10+ ++ noun
11+ |= non=*
12+ ^- wain
13+ (wain non)
14+ ++ txt
15+ |= tex=wain
16+ ^- wain
17+ tex
18+ --
19+++ grow
20+ |%
21+ ++ mime [/text/x-clue (as-octs:mimes:html (of-wain:format clu))]
22+ ++ noun clu
23+ ++ txt clu
24+ --
25+++ grad %txt
26+--
27+
28+
docs-dev-desk/mar/docu.hoonnew file+23
@@ -0,0 +1,23 @@
1+/- *gemtext
2+/+ *docs, cram
3+|_ dcu=manx
4+++ grab
5+ |%
6+ ++ docu dcu
7+ ++ elem |=(a=manx a)
8+ ++ htm |=(a=manx a)
9+ ++ hymn |=(a=manx a)
10+ ++ x-htm |=(a=manx a)
11+ ++ x-htm-elem |=(a=manx a)
12+ ++ gmi |=(gem=(list gmni) (gmi:to-docu gem))
13+ ++ html |=(htm=@t (need (de-xml:^html htm)))
14+ ++ noun |=(non=* (manx non))
15+ ++ txt |=(tex=wain (txt:to-docu tex))
16+ ++ udon |=(mud=@t elm:(static:cram (ream mud)))
17+ --
18+++ grow
19+ |%
20+ ++ noun dcu
21+ --
22+++ grad %noun
23+--
docs-dev-desk/mar/elem.hoonnew file+18
@@ -0,0 +1,18 @@
1+::
2+:::: /hoon/elem/mar
3+ ::
4+/? 310
5+=, mimes:html
6+=, html
7+|_ own=manx
8+::
9+++ grad %noun
10+++ grow :: convert to
11+ |%
12+ ++ hymn ;html:(head body:"+{own}") :: convert to %hymn
13+ ++ html (crip (en-xml hymn)) :: convert to %html
14+ ++ mime [/text/html (as-octs html)] :: convert to %mime
15+ --
16+++ grab |% :: convert from
17+ ++ noun manx :: clam from %noun
18+-- --
docs-dev-desk/mar/gmi-diff.hoonnew file+13
@@ -0,0 +1,13 @@
1+/- gem=gemtext
2+|_ dif=(urge:clay gmni:gem)
3+++ grab
4+ |%
5+ ++ noun (urge:clay gmni:gem)
6+ --
7+++ grow
8+ |%
9+ ++ noun dif
10+ --
11+++ grad %noun
12+--
13+
docs-dev-desk/mar/gmi.hoonnew file+37
@@ -0,0 +1,37 @@
1+/- gem=gemtext
2+/+ libgem=gemtext
3+|_ gmi=(list gmni:gem)
4+++ grow
5+ |%
6+ ++ mime [/text/gemini (as-octs:mimes:html (encode:libgem gmi))]
7+ ++ noun gmi
8+ --
9+++ grab
10+ |%
11+ ++ mime |=((pair mite octs) (decode:libgem q.q))
12+ ++ noun (list gmni:gem)
13+ --
14+++ grad
15+ |%
16+ ++ form %gmi-diff
17+ ++ diff
18+ |= bob=(list gmni:gem)
19+ ^- (urge:clay gmni:gem)
20+ (lusk:differ gmi bob (loss:differ gmi bob))
21+ ++ pact
22+ |= dif=(urge:clay gmni:gem)
23+ ^- (list gmni:gem)
24+ (lurk:differ gmi dif)
25+ ++ join
26+ |= [ali=(urge:clay gmni:gem) bob=(urge:clay gmni:gem)]
27+ ^- (unit (urge:clay gmni:gem))
28+ (gmi-join:libgem ali bob)
29+ ++ mash
30+ |= $: [ship desk (urge:clay gmni:gem)]
31+ [ship desk (urge:clay gmni:gem)]
32+ ==
33+ ^- (urge:clay gmni:gem)
34+ ~|(%gmi-mash !!)
35+ --
36+--
37+
docs-dev-desk/mar/toc.hoonnew file+16
@@ -0,0 +1,16 @@
1+/- docs
2+|_ tab=@t
3+++ grab
4+ |%
5+ ++ mime |=([=mite len=@ud tex=@] `@t`tex)
6+ ++ noun @t
7+ ++ txt of-wain:format
8+ --
9+++ grow
10+ |%
11+ ++ mime [/text/x-toc (as-octs:mimes:html tab)]
12+ ++ noun tab
13+ ++ txt (to-wain:format tab)
14+ --
15+++ grad %txt
16+--
docs-dev-desk/mar/x-htm.hoonnew file+12
@@ -0,0 +1,12 @@
1+|_ a=manx
2+++ grad %noun
3+++ grab
4+ |%
5+ ++ noun manx
6+ --
7+++ grow
8+ |%
9+ ++ htm a
10+ ++ noun a
11+ --
12+--
docs-dev-desk/sur/docs.hoonnew file+9
@@ -0,0 +1,9 @@
1+|%
2++$ scroll [toc=(unit manx) content=manx]
3++$ kind ?(%usr %dev)
4++$ doc [=file title=@t]
5++$ file [name=@ta =mark ~]
6++$ usr (list doc)
7++$ dev (list [agent=@tas docs=(list doc)])
8++$ clue [=usr =dev]
9+--
docs-dev-desk/sur/gemtext.hoonnew file+10
@@ -0,0 +1,10 @@
1+|%
2++$ gmni
3+ $% [%text text=@t]
4+ [%link link=@t text=(unit @t)]
5+ [%code alt=(unit @t) text=@t]
6+ [%head lvl=?(%1 %2 %3) text=@t]
7+ [%quot text=@t]
8+ [%list text=@t]
9+ ==
10+--
flake.locknew file+27
@@ -0,0 +1,27 @@
1+{
2+ "nodes": {
3+ "nixpkgs": {
4+ "locked": {
5+ "lastModified": 1793341285,
6+ "narHash": "sha256-Y1CiZ4SoBUjLQyQM/pY+rS2dXD3CHLPstF0lmOc73D4=",
7+ "owner": "NixOS",
8+ "repo": "nixpkgs",
9+ "rev": "1c89083e451e891152365b799595cf7790a43325",
10+ "type": "github"
11+ },
12+ "original": {
13+ "owner": "NixOS",
14+ "ref": "nixos-unstable",
15+ "repo": "nixpkgs",
16+ "type": "github"
17+ }
18+ },
19+ "root": {
20+ "inputs": {
21+ "nixpkgs": "nixpkgs"
22+ }
23+ }
24+ },
25+ "root": "root",
26+ "version": 7
27+}
flake.nixnew file+73
@@ -0,0 +1,73 @@
1+{
2+ description = "Astrolabe Urbit desk";
3+
4+ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
5+
6+ outputs = { self, nixpkgs }:
7+ let
8+ systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
9+ forAllSystems = nixpkgs.lib.genAttrs systems;
10+ in
11+ {
12+ packages = forAllSystems (system:
13+ let
14+ pkgs = import nixpkgs { inherit system; };
15+ nounPy = pkgs.fetchurl {
16+ name = "noun.py";
17+ url = "https://raw.githubusercontent.com/urbit/tools/f9cda82a7699cad0c1c0f8c031a59e93f028e548/pkg/pynoun/noun.py";
18+ hash = "sha256-vSKQ/UuSmGTANfW5c3I9lQcUFycvqmu5gqysFuP5SAA=";
19+ };
20+ ui = pkgs.buildNpmPackage {
21+ pname = "astrolabe-ui";
22+ version = "0.3.0";
23+ src = ./ui;
24+ nodejs = pkgs.nodejs_22;
25+ npmDepsHash = "sha256-jCkfgNgBmq0mKjmXBbPwdpzBR/E09OgS9XD6O9hRHfw=";
26+ makeCacheWritable = true;
27+ npmFlags = [ "--legacy-peer-deps" ];
28+ npmBuildScript = "build";
29+
30+ installPhase = ''
31+ runHook preInstall
32+ cp -r dist $out
33+ runHook postInstall
34+ '';
35+ };
36+ in
37+ rec {
38+ inherit ui;
39+ desk = pkgs.stdenvNoCC.mkDerivation {
40+ pname = "astrolabe-desk";
41+ version = "1.0.7";
42+ dontUnpack = true;
43+ nativeBuildInputs = [ pkgs.rsync ];
44+
45+ installPhase = ''
46+ runHook preInstall
47+ mkdir -p $out
48+ rsync -r --copy-links --exclude-from=${./config/ignore_files_on_install.txt} ${./base-desk}/ $out/
49+ rsync -r --copy-links --exclude-from=${./config/ignore_files_on_install.txt} ${./docs-dev-desk}/ $out/
50+ rsync -r --copy-links --exclude-from=${./config/ignore_files_on_install.txt} ${./desk}/ $out/
51+ runHook postInstall
52+ '';
53+ };
54+ glob = pkgs.stdenvNoCC.mkDerivation {
55+ pname = "astrolabe-glob";
56+ version = "0.3.0";
57+ dontUnpack = true;
58+ nativeBuildInputs = [
59+ (pkgs.python3.withPackages (ps: [ ps.mmh3 ps.bitstring ]))
60+ ];
61+
62+ installPhase = ''
63+ runHook preInstall
64+ mkdir -p $TMPDIR/pynoun
65+ ln -s ${nounPy} $TMPDIR/pynoun/noun.py
66+ PYTHONPATH=$TMPDIR/pynoun python ${./bin/make_glob.py} ${ui} $out
67+ runHook postInstall
68+ '';
69+ };
70+ default = desk;
71+ });
72+ };
73+}
install.shdeleted−39
@@ -1,39 +0,0 @@
1−#!/bin/bash
2−#
3−# Taken from: https://github.com/timlucmiptev/btc-agents/blob/master/install.sh
4−# by ~timluc-miptev
5−#
6−usage() { printf "Usage: $0 [-w] URBIT_PIER_DIRECTORY \n(-w: flag to watch and live copy code)\n" 1>&2; exit 1; }
7−
8−if [ $# -eq 0 ]; then
9− usage
10− exit 2
11−fi
12−PIER=$1
13−EXCLUDE_FILE=ignore_files.txt
14−
15−while getopts "w" opt; do
16− case ${opt} in
17− w) WATCH_MODE="true"
18− PIER=$2
19− ;;
20− *) usage
21− ;;
22− esac
23−done
24−
25−if [ -z "$WATCH_MODE" ]; then
26− echo "Installed %astrolabe"
27− # rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $PIER/
28− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $PIER/
29−
30−else
31− echo "Watching for changes to copy to ${PIER}..."
32− while [ 0 ]
33− do
34− sleep 0.8
35− # rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $PIER/
36− rsync -r --copy-links --exclude-from=$EXCLUDE_FILE ./desk/* $PIER/
37−
38− done
39−fi
ui/.envnew file+3
@@ -0,0 +1,3 @@
1+VITE_SHIP_URL=http://localhost:8081
2+ASTROLABE_DESK=~/urbit/zod/astrolabe
3+GLOBBER_DESK=~/urbit/zod/globber
ui/index.html+7−3
@@ -2,12 +2,16 @@
22<html lang="en">
33 <head>
44 <meta charset="UTF-8" />
5− <link rel="icon" href="/favicon.ico" />
5+ <!-- <link rel="icon" href="/favicon.ico" /> -->
6+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
7+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
8+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
9+ <link rel="manifest" href="/site.webmanifest">
610 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7− <title>Astrolabe</title>
11+ <meta name="color-scheme" content="light only">
812 </head>
913 <body>
10− <div id="app"></div>
14+ <div id="app" class="h-full"></div>
1115 <script type="module" src="/src/main.ts"></script>
1216 </body>
1317</html>
ui/package-lock.json+401−55
@@ -1,17 +1,25 @@
11{
22 "name": "urbit-astrolabe",
3− "version": "0.0.0",
3+ "version": "0.3.0",
44 "lockfileVersion": 2,
55 "requires": true,
66 "packages": {
77 "": {
88 "name": "urbit-astrolabe",
9− "version": "0.0.0",
9+ "version": "0.3.0",
1010 "dependencies": {
11+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
1112 "@tlon/phonemes-js": "^1.0.0",
1213 "@tlon/sigil-js": "^1.4.5",
1314 "@urbit/api": "^2.1.0",
1415 "@urbit/http-api": "^2.1.0",
16+ "bn.js": "^5.2.0",
17+ "graphology": "^0.24.1",
18+ "lodash": "^4.17.21",
19+ "sigma-jh": "^2.4.2",
20+ "svelte-copy-to-clipboard": "^0.2.5",
21+ "svelte-markdown": "^0.2.3",
22+ "tippy.js": "^6.3.7",
1523 "urbit-ob": "^5.0.1"
1624 },
1725 "devDependencies": {
@@ -21,8 +29,10 @@
2129 "autoprefixer": "^10.4.2",
2230 "concurrently": "^7.0.0",
2331 "cross-env": "^7.0.3",
32+ "dotenv-cli": "^6.0.0",
2433 "postcss-cli": "^9.1.0",
25− "svelte": "^3.44.0",
34+ "sass": "^1.50.0",
35+ "svelte": "^3.48.0",
2636 "svelte-check": "^2.2.7",
2737 "svelte-preprocess": "^4.9.8",
2838 "svelte-spa-router": "^3.2.0",
@@ -32,6 +42,28 @@
3242 "vite": "^2.7.2"
3343 }
3444 },
45+ "../../sigil-js": {
46+ "name": "@johnhyde/sigil-js",
47+ "version": "1.4.6",
48+ "extraneous": true,
49+ "license": "MIT",
50+ "dependencies": {
51+ "invariant": "^2.2.4",
52+ "svgson": "^4.0.0",
53+ "transformation-matrix": "2.1.1"
54+ },
55+ "devDependencies": {
56+ "@types/invariant": "^2.2.31",
57+ "@types/react": "^17.0.20",
58+ "husky": "^4.2.3",
59+ "prettier": "^1.19.1",
60+ "tsdx": "^0.14.1",
61+ "typescript": "^3.8.3"
62+ },
63+ "peerDependencies": {
64+ "react": "^16 || ^17"
65+ }
66+ },
3567 "node_modules/@babel/code-frame": {
3668 "version": "7.16.7",
3769 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@@ -140,6 +172,19 @@
140172 "node": ">=6.9.0"
141173 }
142174 },
175+ "node_modules/@johnhyde/sigil-js": {
176+ "version": "1.4.6",
177+ "resolved": "git+ssh://git@github.com/johnhyde/sigil-js.git#f89ce78c34b5811ea19ddea4a6964ea8360e7b4d",
178+ "license": "MIT",
179+ "dependencies": {
180+ "invariant": "^2.2.4",
181+ "svgson": "^4.0.0",
182+ "transformation-matrix": "2.1.1"
183+ },
184+ "peerDependencies": {
185+ "react": "^16 || ^17"
186+ }
187+ },
143188 "node_modules/@microsoft/fetch-event-source": {
144189 "version": "2.0.1",
145190 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -180,6 +225,15 @@
180225 "node": ">= 8"
181226 }
182227 },
228+ "node_modules/@popperjs/core": {
229+ "version": "2.11.5",
230+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
231+ "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
232+ "funding": {
233+ "type": "opencollective",
234+ "url": "https://opencollective.com/popperjs"
235+ }
236+ },
183237 "node_modules/@rollup/pluginutils": {
184238 "version": "4.1.2",
185239 "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz",
@@ -244,6 +298,11 @@
244298 "integrity": "sha512-aqkICXbM1oX5FfgZd2qSSAGdyo/NRxjWCamxoyi3T8iVQnzGge19HhDYzZ6NrVOW7bhcWNSq9XexWFtMzbB24A==",
245299 "dev": true
246300 },
301+ "node_modules/@types/marked": {
302+ "version": "4.0.7",
303+ "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
304+ "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw=="
305+ },
247306 "node_modules/@types/node": {
248307 "version": "17.0.8",
249308 "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
@@ -304,6 +363,11 @@
304363 "vite-plugin-rewrite-all": "^0.1.2"
305364 }
306365 },
366+ "node_modules/@yomguithereal/helpers": {
367+ "version": "1.1.1",
368+ "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz",
369+ "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="
370+ },
307371 "node_modules/acorn": {
308372 "version": "7.4.1",
309373 "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -442,9 +506,9 @@
442506 }
443507 },
444508 "node_modules/bn.js": {
445− "version": "4.12.0",
446− "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
447− "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
509+ "version": "5.2.0",
510+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
511+ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
448512 },
449513 "node_modules/brace-expansion": {
450514 "version": "1.1.11",
@@ -754,7 +818,7 @@
754818 "node_modules/deep-rename-keys": {
755819 "version": "0.2.1",
756820 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
757− "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
821+ "integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==",
758822 "dependencies": {
759823 "kind-of": "^3.0.2",
760824 "rename-keys": "^1.1.2"
@@ -828,6 +892,39 @@
828892 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
829893 "dev": true
830894 },
895+ "node_modules/dotenv": {
896+ "version": "16.0.1",
897+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
898+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
899+ "dev": true,
900+ "engines": {
901+ "node": ">=12"
902+ }
903+ },
904+ "node_modules/dotenv-cli": {
905+ "version": "6.0.0",
906+ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
907+ "integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
908+ "dev": true,
909+ "dependencies": {
910+ "cross-spawn": "^7.0.3",
911+ "dotenv": "^16.0.0",
912+ "dotenv-expand": "^8.0.1",
913+ "minimist": "^1.2.5"
914+ },
915+ "bin": {
916+ "dotenv": "cli.js"
917+ }
918+ },
919+ "node_modules/dotenv-expand": {
920+ "version": "8.0.3",
921+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
922+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==",
923+ "dev": true,
924+ "engines": {
925+ "node": ">=12"
926+ }
927+ },
831928 "node_modules/electron-to-chromium": {
832929 "version": "1.4.38",
833930 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -1132,7 +1229,15 @@
11321229 "node_modules/eventemitter3": {
11331230 "version": "2.0.3",
11341231 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
1135− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
1232+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
1233+ },
1234+ "node_modules/events": {
1235+ "version": "3.3.0",
1236+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
1237+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
1238+ "engines": {
1239+ "node": ">=0.8.x"
1240+ }
11361241 },
11371242 "node_modules/fast-glob": {
11381243 "version": "3.2.9",
@@ -1248,7 +1353,7 @@
12481353 "node_modules/get-value": {
12491354 "version": "2.0.6",
12501355 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
1251− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
1356+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
12521357 "engines": {
12531358 "node": ">=0.10.0"
12541359 }
@@ -1311,6 +1416,32 @@
13111416 "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
13121417 "dev": true
13131418 },
1419+ "node_modules/graphology": {
1420+ "version": "0.24.1",
1421+ "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.24.1.tgz",
1422+ "integrity": "sha512-6lNz1PNTAe9Q6ioHKrXu0Lp047sgvOoHa4qmP/8mnJWCGv2iIZPQkuHPUb2/OWDWCqHpw2hKgJLJ55X/66xmHg==",
1423+ "dependencies": {
1424+ "events": "^3.3.0",
1425+ "obliterator": "^2.0.2"
1426+ },
1427+ "peerDependencies": {
1428+ "graphology-types": ">=0.24.0"
1429+ }
1430+ },
1431+ "node_modules/graphology-types": {
1432+ "version": "0.24.4",
1433+ "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.4.tgz",
1434+ "integrity": "sha512-CSgmycWiviCctMFO86YoUTJN1t4/PLKC5Pos2Hite+7kCUXTr+mGlDUAOgpcKG1IfFaeL9VDmTjFpzs2rTnPWw==",
1435+ "peer": true
1436+ },
1437+ "node_modules/graphology-utils": {
1438+ "version": "2.5.2",
1439+ "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
1440+ "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==",
1441+ "peerDependencies": {
1442+ "graphology-types": ">=0.23.0"
1443+ }
1444+ },
13141445 "node_modules/has": {
13151446 "version": "1.0.3",
13161447 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -1335,7 +1466,7 @@
13351466 "node_modules/has-value": {
13361467 "version": "0.3.1",
13371468 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
1338− "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
1469+ "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
13391470 "dependencies": {
13401471 "get-value": "^2.0.3",
13411472 "has-values": "^0.1.4",
@@ -1348,7 +1479,7 @@
13481479 "node_modules/has-value/node_modules/isobject": {
13491480 "version": "2.1.0",
13501481 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
1351− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
1482+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
13521483 "dependencies": {
13531484 "isarray": "1.0.0"
13541485 },
@@ -1359,7 +1490,7 @@
13591490 "node_modules/has-values": {
13601491 "version": "0.1.4",
13611492 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
1362− "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
1493+ "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==",
13631494 "engines": {
13641495 "node": ">=0.10.0"
13651496 }
@@ -1382,6 +1513,12 @@
13821513 "url": "https://opencollective.com/immer"
13831514 }
13841515 },
1516+ "node_modules/immutable": {
1517+ "version": "4.0.0",
1518+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
1519+ "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
1520+ "dev": true
1521+ },
13851522 "node_modules/import-fresh": {
13861523 "version": "3.3.0",
13871524 "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -1510,7 +1647,7 @@
15101647 "node_modules/isarray": {
15111648 "version": "1.0.0",
15121649 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
1513− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
1650+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
15141651 },
15151652 "node_modules/isexe": {
15161653 "version": "2.0.0",
@@ -1521,7 +1658,7 @@
15211658 "node_modules/isobject": {
15221659 "version": "3.0.1",
15231660 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
1524− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
1661+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
15251662 "engines": {
15261663 "node": ">=0.10.0"
15271664 }
@@ -1552,7 +1689,7 @@
15521689 "node_modules/kind-of": {
15531690 "version": "3.2.2",
15541691 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
1555− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
1692+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
15561693 "dependencies": {
15571694 "is-buffer": "^1.1.5"
15581695 },
@@ -1587,8 +1724,7 @@
15871724 "node_modules/lodash": {
15881725 "version": "4.17.21",
15891726 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1590− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
1591− "dev": true
1727+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
15921728 },
15931729 "node_modules/lodash.chunk": {
15941730 "version": "4.2.0",
@@ -1620,6 +1756,17 @@
16201756 "sourcemap-codec": "^1.4.4"
16211757 }
16221758 },
1759+ "node_modules/marked": {
1760+ "version": "4.2.3",
1761+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
1762+ "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==",
1763+ "bin": {
1764+ "marked": "bin/marked.js"
1765+ },
1766+ "engines": {
1767+ "node": ">= 12"
1768+ }
1769+ },
16231770 "node_modules/merge2": {
16241771 "version": "1.4.1",
16251772 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -1664,9 +1811,9 @@
16641811 }
16651812 },
16661813 "node_modules/minimist": {
1667− "version": "1.2.5",
1668− "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
1669− "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
1814+ "version": "1.2.6",
1815+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
1816+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
16701817 "dev": true
16711818 },
16721819 "node_modules/mkdirp": {
@@ -1735,7 +1882,7 @@
17351882 "node_modules/object-assign": {
17361883 "version": "4.1.1",
17371884 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1738− "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
1885+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
17391886 "peer": true,
17401887 "engines": {
17411888 "node": ">=0.10.0"
@@ -1750,10 +1897,15 @@
17501897 "node": ">= 6"
17511898 }
17521899 },
1900+ "node_modules/obliterator": {
1901+ "version": "2.0.4",
1902+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
1903+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
1904+ },
17531905 "node_modules/omit-deep": {
17541906 "version": "0.3.0",
17551907 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
1756− "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
1908+ "integrity": "sha512-Lbl/Ma59sss2b15DpnWnGmECBRL8cRl/PjPbPMVW+Y8zIQzRrwMaI65Oy6HvxyhYeILVKBJb2LWeG81bj5zbMg==",
17571909 "dependencies": {
17581910 "is-plain-object": "^2.0.1",
17591911 "unset-value": "^0.1.1"
@@ -2277,6 +2429,23 @@
22772429 "rimraf": "^2.5.2"
22782430 }
22792431 },
2432+ "node_modules/sass": {
2433+ "version": "1.50.0",
2434+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
2435+ "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
2436+ "dev": true,
2437+ "dependencies": {
2438+ "chokidar": ">=3.0.0 <4.0.0",
2439+ "immutable": "^4.0.0",
2440+ "source-map-js": ">=0.6.2 <2.0.0"
2441+ },
2442+ "bin": {
2443+ "sass": "sass.js"
2444+ },
2445+ "engines": {
2446+ "node": ">=12.0.0"
2447+ }
2448+ },
22802449 "node_modules/shebang-command": {
22812450 "version": "2.0.0",
22822451 "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2298,6 +2467,16 @@
22982467 "node": ">=8"
22992468 }
23002469 },
2470+ "node_modules/sigma-jh": {
2471+ "version": "2.4.2",
2472+ "resolved": "https://registry.npmjs.org/sigma-jh/-/sigma-jh-2.4.2.tgz",
2473+ "integrity": "sha512-viB4M5Y79N5rAgs0z3M4Gff4HX53S+UMwblPDEH/Nkwwe7AcmcWwsEYsGv/xL/TNQzLIBBb0NaNAg5ZhnbEm8g==",
2474+ "dependencies": {
2475+ "@yomguithereal/helpers": "^1.1.1",
2476+ "events": "^3.3.0",
2477+ "graphology-utils": "^2.5.0"
2478+ }
2479+ },
23012480 "node_modules/slash": {
23022481 "version": "4.0.0",
23032482 "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -2418,10 +2597,9 @@
24182597 }
24192598 },
24202599 "node_modules/svelte": {
2421− "version": "3.45.0",
2422− "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.45.0.tgz",
2423− "integrity": "sha512-6AWftH2eqqKLYH1HvKpuUWe8OfjflarhegN57P/Cqwc2Rb2F5oIdDtANU/jMscyZMzG0v6nTQox0siZR9cmRVQ==",
2424− "dev": true,
2600+ "version": "3.48.0",
2601+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
2602+ "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ==",
24252603 "engines": {
24262604 "node": ">= 8"
24272605 }
@@ -2449,6 +2627,11 @@
24492627 "svelte": "^3.24.0"
24502628 }
24512629 },
2630+ "node_modules/svelte-copy-to-clipboard": {
2631+ "version": "0.2.5",
2632+ "resolved": "https://registry.npmjs.org/svelte-copy-to-clipboard/-/svelte-copy-to-clipboard-0.2.5.tgz",
2633+ "integrity": "sha512-EAULxib4pRa59nyz25dDKITgSAVkrNVojRZVCSO8MIFd5GJljghJaCTIe/7R+O3GxKYIwJ3XHDNoY1hfjJeyEg=="
2634+ },
24522635 "node_modules/svelte-hmr": {
24532636 "version": "0.14.9",
24542637 "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.9.tgz",
@@ -2458,6 +2641,18 @@
24582641 "svelte": ">=3.19.0"
24592642 }
24602643 },
2644+ "node_modules/svelte-markdown": {
2645+ "version": "0.2.3",
2646+ "resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.2.3.tgz",
2647+ "integrity": "sha512-2h680NzTXnAD0CXhxe3GeHl6W+ayG4iKQRl+BIDRw+R0mUE0OiNxP1Vt8Rn+aWevB/LBiBIPCAwvL+0BkG057A==",
2648+ "dependencies": {
2649+ "@types/marked": "^4.0.1",
2650+ "marked": "^4.0.10"
2651+ },
2652+ "peerDependencies": {
2653+ "svelte": "^3.0.0"
2654+ }
2655+ },
24612656 "node_modules/svelte-preprocess": {
24622657 "version": "4.10.1",
24632658 "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
@@ -2603,6 +2798,14 @@
26032798 "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==",
26042799 "dev": true
26052800 },
2801+ "node_modules/tippy.js": {
2802+ "version": "6.3.7",
2803+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
2804+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
2805+ "dependencies": {
2806+ "@popperjs/core": "^2.9.0"
2807+ }
2808+ },
26062809 "node_modules/to-regex-range": {
26072810 "version": "5.0.1",
26082811 "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2660,7 +2863,7 @@
26602863 "node_modules/unset-value": {
26612864 "version": "0.1.2",
26622865 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
2663− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
2866+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
26642867 "dependencies": {
26652868 "has-value": "^0.3.1",
26662869 "isobject": "^3.0.0"
@@ -2679,6 +2882,11 @@
26792882 "lodash.isequal": "^4.5.0"
26802883 }
26812884 },
2885+ "node_modules/urbit-ob/node_modules/bn.js": {
2886+ "version": "4.12.0",
2887+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
2888+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
2889+ },
26822890 "node_modules/util-deprecate": {
26832891 "version": "1.0.2",
26842892 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2790,7 +2998,7 @@
27902998 "node_modules/xml-lexer": {
27912999 "version": "0.2.2",
27923000 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
2793− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
3001+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
27943002 "dependencies": {
27953003 "eventemitter3": "^2.0.0"
27963004 }
@@ -2798,7 +3006,7 @@
27983006 "node_modules/xml-reader": {
27993007 "version": "2.4.3",
28003008 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
2801− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
3009+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
28023010 "dependencies": {
28033011 "eventemitter3": "^2.0.0",
28043012 "xml-lexer": "^0.2.2"
@@ -2946,6 +3154,15 @@
29463154 "regenerator-runtime": "^0.13.4"
29473155 }
29483156 },
3157+ "@johnhyde/sigil-js": {
3158+ "version": "git+ssh://git@github.com/johnhyde/sigil-js.git#f89ce78c34b5811ea19ddea4a6964ea8360e7b4d",
3159+ "from": "@johnhyde/sigil-js@github:johnhyde/sigil-js",
3160+ "requires": {
3161+ "invariant": "^2.2.4",
3162+ "svgson": "^4.0.0",
3163+ "transformation-matrix": "2.1.1"
3164+ }
3165+ },
29493166 "@microsoft/fetch-event-source": {
29503167 "version": "2.0.1",
29513168 "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
@@ -2977,6 +3194,11 @@
29773194 "fastq": "^1.6.0"
29783195 }
29793196 },
3197+ "@popperjs/core": {
3198+ "version": "2.11.5",
3199+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
3200+ "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
3201+ },
29803202 "@rollup/pluginutils": {
29813203 "version": "4.1.2",
29823204 "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz",
@@ -3022,6 +3244,11 @@
30223244 "integrity": "sha512-aqkICXbM1oX5FfgZd2qSSAGdyo/NRxjWCamxoyi3T8iVQnzGge19HhDYzZ6NrVOW7bhcWNSq9XexWFtMzbB24A==",
30233245 "dev": true
30243246 },
3247+ "@types/marked": {
3248+ "version": "4.0.7",
3249+ "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
3250+ "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw=="
3251+ },
30253252 "@types/node": {
30263253 "version": "17.0.8",
30273254 "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz",
@@ -3082,6 +3309,11 @@
30823309 "vite-plugin-rewrite-all": "^0.1.2"
30833310 }
30843311 },
3312+ "@yomguithereal/helpers": {
3313+ "version": "1.1.1",
3314+ "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz",
3315+ "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="
3316+ },
30853317 "acorn": {
30863318 "version": "7.4.1",
30873319 "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -3174,9 +3406,9 @@
31743406 "dev": true
31753407 },
31763408 "bn.js": {
3177− "version": "4.12.0",
3178− "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
3179− "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
3409+ "version": "5.2.0",
3410+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
3411+ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
31803412 },
31813413 "brace-expansion": {
31823414 "version": "1.1.11",
@@ -3392,7 +3624,7 @@
33923624 "deep-rename-keys": {
33933625 "version": "0.2.1",
33943626 "resolved": "https://registry.npmjs.org/deep-rename-keys/-/deep-rename-keys-0.2.1.tgz",
3395− "integrity": "sha1-7eeFN9emaivmFRfir5Vtf1ij8dg=",
3627+ "integrity": "sha512-RHd9ABw4Fvk+gYDWqwOftG849x0bYOySl/RgX0tLI9i27ZIeSO91mLZJEp7oPHOMFqHvpgu21YptmDt0FYD/0A==",
33963628 "requires": {
33973629 "kind-of": "^3.0.2",
33983630 "rename-keys": "^1.1.2"
@@ -3448,6 +3680,30 @@
34483680 "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
34493681 "dev": true
34503682 },
3683+ "dotenv": {
3684+ "version": "16.0.1",
3685+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
3686+ "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
3687+ "dev": true
3688+ },
3689+ "dotenv-cli": {
3690+ "version": "6.0.0",
3691+ "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
3692+ "integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
3693+ "dev": true,
3694+ "requires": {
3695+ "cross-spawn": "^7.0.3",
3696+ "dotenv": "^16.0.0",
3697+ "dotenv-expand": "^8.0.1",
3698+ "minimist": "^1.2.5"
3699+ }
3700+ },
3701+ "dotenv-expand": {
3702+ "version": "8.0.3",
3703+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
3704+ "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==",
3705+ "dev": true
3706+ },
34513707 "electron-to-chromium": {
34523708 "version": "1.4.38",
34533709 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.38.tgz",
@@ -3640,7 +3896,12 @@
36403896 "eventemitter3": {
36413897 "version": "2.0.3",
36423898 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
3643− "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
3899+ "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
3900+ },
3901+ "events": {
3902+ "version": "3.3.0",
3903+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
3904+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
36443905 },
36453906 "fast-glob": {
36463907 "version": "3.2.9",
@@ -3724,7 +3985,7 @@
37243985 "get-value": {
37253986 "version": "2.0.6",
37263987 "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
3727− "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
3988+ "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="
37283989 },
37293990 "glob": {
37303991 "version": "7.2.0",
@@ -3769,6 +4030,27 @@
37694030 "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
37704031 "dev": true
37714032 },
4033+ "graphology": {
4034+ "version": "0.24.1",
4035+ "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.24.1.tgz",
4036+ "integrity": "sha512-6lNz1PNTAe9Q6ioHKrXu0Lp047sgvOoHa4qmP/8mnJWCGv2iIZPQkuHPUb2/OWDWCqHpw2hKgJLJ55X/66xmHg==",
4037+ "requires": {
4038+ "events": "^3.3.0",
4039+ "obliterator": "^2.0.2"
4040+ }
4041+ },
4042+ "graphology-types": {
4043+ "version": "0.24.4",
4044+ "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.4.tgz",
4045+ "integrity": "sha512-CSgmycWiviCctMFO86YoUTJN1t4/PLKC5Pos2Hite+7kCUXTr+mGlDUAOgpcKG1IfFaeL9VDmTjFpzs2rTnPWw==",
4046+ "peer": true
4047+ },
4048+ "graphology-utils": {
4049+ "version": "2.5.2",
4050+ "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
4051+ "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==",
4052+ "requires": {}
4053+ },
37724054 "has": {
37734055 "version": "1.0.3",
37744056 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -3787,7 +4069,7 @@
37874069 "has-value": {
37884070 "version": "0.3.1",
37894071 "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
3790− "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
4072+ "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
37914073 "requires": {
37924074 "get-value": "^2.0.3",
37934075 "has-values": "^0.1.4",
@@ -3797,7 +4079,7 @@
37974079 "isobject": {
37984080 "version": "2.1.0",
37994081 "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
3800− "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
4082+ "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
38014083 "requires": {
38024084 "isarray": "1.0.0"
38034085 }
@@ -3807,7 +4089,7 @@
38074089 "has-values": {
38084090 "version": "0.1.4",
38094091 "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
3810− "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
4092+ "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ=="
38114093 },
38124094 "ignore": {
38134095 "version": "5.2.0",
@@ -3820,6 +4102,12 @@
38204102 "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz",
38214103 "integrity": "sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA=="
38224104 },
4105+ "immutable": {
4106+ "version": "4.0.0",
4107+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
4108+ "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
4109+ "dev": true
4110+ },
38234111 "import-fresh": {
38244112 "version": "3.3.0",
38254113 "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3921,7 +4209,7 @@
39214209 "isarray": {
39224210 "version": "1.0.0",
39234211 "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
3924− "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
4212+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
39254213 },
39264214 "isexe": {
39274215 "version": "2.0.0",
@@ -3932,7 +4220,7 @@
39324220 "isobject": {
39334221 "version": "3.0.1",
39344222 "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
3935− "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
4223+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
39364224 },
39374225 "js-tokens": {
39384226 "version": "4.0.0",
@@ -3958,7 +4246,7 @@
39584246 "kind-of": {
39594247 "version": "3.2.2",
39604248 "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
3961− "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
4249+ "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
39624250 "requires": {
39634251 "is-buffer": "^1.1.5"
39644252 }
@@ -3984,8 +4272,7 @@
39844272 "lodash": {
39854273 "version": "4.17.21",
39864274 "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
3987− "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
3988− "dev": true
4275+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
39894276 },
39904277 "lodash.chunk": {
39914278 "version": "4.2.0",
@@ -4014,6 +4301,11 @@
40144301 "sourcemap-codec": "^1.4.4"
40154302 }
40164303 },
4304+ "marked": {
4305+ "version": "4.2.3",
4306+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz",
4307+ "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw=="
4308+ },
40174309 "merge2": {
40184310 "version": "1.4.1",
40194311 "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4046,9 +4338,9 @@
40464338 }
40474339 },
40484340 "minimist": {
4049− "version": "1.2.5",
4050− "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
4051− "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
4341+ "version": "1.2.6",
4342+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
4343+ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
40524344 "dev": true
40534345 },
40544346 "mkdirp": {
@@ -4099,7 +4391,7 @@
40994391 "object-assign": {
41004392 "version": "4.1.1",
41014393 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
4102− "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
4394+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
41034395 "peer": true
41044396 },
41054397 "object-hash": {
@@ -4108,10 +4400,15 @@
41084400 "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
41094401 "dev": true
41104402 },
4403+ "obliterator": {
4404+ "version": "2.0.4",
4405+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
4406+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
4407+ },
41114408 "omit-deep": {
41124409 "version": "0.3.0",
41134410 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
4114− "integrity": "sha1-IcivNJm8rdKWUaIyy8rLxSRF6+w=",
4411+ "integrity": "sha512-Lbl/Ma59sss2b15DpnWnGmECBRL8cRl/PjPbPMVW+Y8zIQzRrwMaI65Oy6HvxyhYeILVKBJb2LWeG81bj5zbMg==",
41154412 "requires": {
41164413 "is-plain-object": "^2.0.1",
41174414 "unset-value": "^0.1.1"
@@ -4459,6 +4756,17 @@
44594756 "rimraf": "^2.5.2"
44604757 }
44614758 },
4759+ "sass": {
4760+ "version": "1.50.0",
4761+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
4762+ "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
4763+ "dev": true,
4764+ "requires": {
4765+ "chokidar": ">=3.0.0 <4.0.0",
4766+ "immutable": "^4.0.0",
4767+ "source-map-js": ">=0.6.2 <2.0.0"
4768+ }
4769+ },
44624770 "shebang-command": {
44634771 "version": "2.0.0",
44644772 "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4474,6 +4782,16 @@
44744782 "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
44754783 "dev": true
44764784 },
4785+ "sigma-jh": {
4786+ "version": "2.4.2",
4787+ "resolved": "https://registry.npmjs.org/sigma-jh/-/sigma-jh-2.4.2.tgz",
4788+ "integrity": "sha512-viB4M5Y79N5rAgs0z3M4Gff4HX53S+UMwblPDEH/Nkwwe7AcmcWwsEYsGv/xL/TNQzLIBBb0NaNAg5ZhnbEm8g==",
4789+ "requires": {
4790+ "@yomguithereal/helpers": "^1.1.1",
4791+ "events": "^3.3.0",
4792+ "graphology-utils": "^2.5.0"
4793+ }
4794+ },
44774795 "slash": {
44784796 "version": "4.0.0",
44794797 "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -4561,10 +4879,9 @@
45614879 "dev": true
45624880 },
45634881 "svelte": {
4564− "version": "3.45.0",
4565− "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.45.0.tgz",
4566− "integrity": "sha512-6AWftH2eqqKLYH1HvKpuUWe8OfjflarhegN57P/Cqwc2Rb2F5oIdDtANU/jMscyZMzG0v6nTQox0siZR9cmRVQ==",
4567− "dev": true
4882+ "version": "3.48.0",
4883+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.48.0.tgz",
4884+ "integrity": "sha512-fN2YRm/bGumvjUpu6yI3BpvZnpIm9I6A7HR4oUNYd7ggYyIwSA/BX7DJ+UXXffLp6XNcUijyLvttbPVCYa/3xQ=="
45684885 },
45694886 "svelte-check": {
45704887 "version": "2.2.11",
@@ -4583,6 +4900,11 @@
45834900 "typescript": "*"
45844901 }
45854902 },
4903+ "svelte-copy-to-clipboard": {
4904+ "version": "0.2.5",
4905+ "resolved": "https://registry.npmjs.org/svelte-copy-to-clipboard/-/svelte-copy-to-clipboard-0.2.5.tgz",
4906+ "integrity": "sha512-EAULxib4pRa59nyz25dDKITgSAVkrNVojRZVCSO8MIFd5GJljghJaCTIe/7R+O3GxKYIwJ3XHDNoY1hfjJeyEg=="
4907+ },
45864908 "svelte-hmr": {
45874909 "version": "0.14.9",
45884910 "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.9.tgz",
@@ -4590,6 +4912,15 @@
45904912 "dev": true,
45914913 "requires": {}
45924914 },
4915+ "svelte-markdown": {
4916+ "version": "0.2.3",
4917+ "resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.2.3.tgz",
4918+ "integrity": "sha512-2h680NzTXnAD0CXhxe3GeHl6W+ayG4iKQRl+BIDRw+R0mUE0OiNxP1Vt8Rn+aWevB/LBiBIPCAwvL+0BkG057A==",
4919+ "requires": {
4920+ "@types/marked": "^4.0.1",
4921+ "marked": "^4.0.10"
4922+ }
4923+ },
45934924 "svelte-preprocess": {
45944925 "version": "4.10.1",
45954926 "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.1.tgz",
@@ -4668,6 +4999,14 @@
46684999 "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==",
46695000 "dev": true
46705001 },
5002+ "tippy.js": {
5003+ "version": "6.3.7",
5004+ "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz",
5005+ "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==",
5006+ "requires": {
5007+ "@popperjs/core": "^2.9.0"
5008+ }
5009+ },
46715010 "to-regex-range": {
46725011 "version": "5.0.1",
46735012 "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -4709,7 +5048,7 @@
47095048 "unset-value": {
47105049 "version": "0.1.2",
47115050 "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-0.1.2.tgz",
4712− "integrity": "sha1-UGgQuGfyfCpabpsEgzYx9t5Y0xA=",
5051+ "integrity": "sha512-yhv5I4TsldLdE3UcVQn0hD2T5sNCPv4+qm/CTUpRKIpwthYRIipsAPdsrNpOI79hPQa0rTTeW22Fq6JWRcTgNg==",
47135052 "requires": {
47145053 "has-value": "^0.3.1",
47155054 "isobject": "^3.0.0"
@@ -4723,6 +5062,13 @@
47235062 "bn.js": "^4.11.8",
47245063 "lodash.chunk": "^4.2.0",
47255064 "lodash.isequal": "^4.5.0"
5065+ },
5066+ "dependencies": {
5067+ "bn.js": {
5068+ "version": "4.12.0",
5069+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
5070+ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
5071+ }
47265072 }
47275073 },
47285074 "util-deprecate": {
@@ -4789,7 +5135,7 @@
47895135 "xml-lexer": {
47905136 "version": "0.2.2",
47915137 "resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
4792− "integrity": "sha1-UYGTpKozTVj8fSSLVJB5uJkH4EY=",
5138+ "integrity": "sha512-G0i98epIwiUEiKmMcavmVdhtymW+pCAohMRgybyIME9ygfVu8QheIi+YoQh3ngiThsT0SQzJT4R0sKDEv8Ou0w==",
47935139 "requires": {
47945140 "eventemitter3": "^2.0.0"
47955141 }
@@ -4797,7 +5143,7 @@
47975143 "xml-reader": {
47985144 "version": "2.4.3",
47995145 "resolved": "https://registry.npmjs.org/xml-reader/-/xml-reader-2.4.3.tgz",
4800− "integrity": "sha1-n4EMr3xCWlqvuEixxFEDyecddTA=",
5146+ "integrity": "sha512-xWldrIxjeAMAu6+HSf9t50ot1uL5M+BtOidRCWHXIeewvSeIpscWCsp4Zxjk8kHHhdqFBrfK8U0EJeCcnyQ/gA==",
48015147 "requires": {
48025148 "eventemitter3": "^2.0.0",
48035149 "xml-lexer": "^0.2.2"
ui/package.json+28−7
@@ -1,16 +1,27 @@
11{
22 "name": "urbit-astrolabe",
3− "version": "0.0.0",
43 "type": "module",
4+ "version": "0.3.0",
55 "scripts": {
6− "dev:only": "vite --port 4000",
7− "build:only": "vite build",
6+ "dev:vite": "vite --port 4000 --host",
7+ "build:vite": "vite build",
88 "tailwind:watch": "cross-env TAILWIND_MODE=watch cross-env NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/tailwind-output.css -w",
99 "tailwind:build": "cross-env TAILWIND_MODE=build cross-env NODE_ENV=production postcss src/styles/tailwind.css -o src/styles/tailwind-output.css",
10− "dev": "concurrently \"npm run dev:only\" \"npm run tailwind:watch\"",
11− "build": "npm run tailwind:build && npm run build:only",
10+ "dev": "concurrently \"npm run dev:vite\" \"npm run tailwind:watch\"",
11+ "build": "npm run tailwind:build && npm run build:vite",
1212 "preview": "vite preview",
13− "check": "svelte-check --tsconfig ./tsconfig.json"
13+ "check": "svelte-check --tsconfig ./tsconfig.json",
14+ "_install:glob": "../bin/install.sh -g $GLOBBER_DESK",
15+ "_install:desk": "../bin/install.sh $ASTROLABE_DESK",
16+ "_install:desk:watch": "../bin/install.sh -w $ASTROLABE_DESK",
17+ "install:glob": "dotenv -c local npm run _install:glob",
18+ "install:desk": "dotenv -c local npm run _install:desk",
19+ "install:desk:watch": "dotenv -c local npm run _install:desk:watch",
20+ "release": "npm run build && npm run install:glob",
21+ "_get-glob": "../bin/get_glob.sh $GLOBBER_DESK",
22+ "get-glob": "dotenv -c local npm run _get-glob",
23+ "_commit": "../bin/commit.sh $ASTROLABE_DESK",
24+ "commit": "dotenv -c local npm run _commit"
1425 },
1526 "devDependencies": {
1627 "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
@@ -19,8 +30,10 @@
1930 "autoprefixer": "^10.4.2",
2031 "concurrently": "^7.0.0",
2132 "cross-env": "^7.0.3",
33+ "dotenv-cli": "^6.0.0",
2234 "postcss-cli": "^9.1.0",
23− "svelte": "^3.44.0",
35+ "sass": "^1.50.0",
36+ "svelte": "^3.48.0",
2437 "svelte-check": "^2.2.7",
2538 "svelte-preprocess": "^4.9.8",
2639 "svelte-spa-router": "^3.2.0",
@@ -30,10 +43,18 @@
3043 "vite": "^2.7.2"
3144 },
3245 "dependencies": {
46+ "@johnhyde/sigil-js": "github:johnhyde/sigil-js",
3347 "@tlon/phonemes-js": "^1.0.0",
3448 "@tlon/sigil-js": "^1.4.5",
3549 "@urbit/api": "^2.1.0",
3650 "@urbit/http-api": "^2.1.0",
51+ "bn.js": "^5.2.0",
52+ "graphology": "^0.24.1",
53+ "lodash": "^4.17.21",
54+ "sigma-jh": "^2.4.2",
55+ "svelte-copy-to-clipboard": "^0.2.5",
56+ "svelte-markdown": "^0.2.3",
57+ "tippy.js": "^6.3.7",
3758 "urbit-ob": "^5.0.1"
3859 }
3960}
ui/public/android-chrome-192x192.pngnew file

Binary file not shown.

ui/public/android-chrome-512x512.pngnew file

Binary file not shown.

ui/public/apple-touch-icon.pngnew file

Binary file not shown.

ui/public/favicon-16x16.pngnew file

Binary file not shown.

ui/public/favicon-32x32.pngnew file

Binary file not shown.

ui/public/favicon.ico

Binary file not shown.

ui/public/sigil-example.pngnew file

Binary file not shown.

ui/public/site.webmanifestnew file+1
@@ -0,0 +1 @@
1+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
ui/scraps/tirrel-graph-main.jsnew file+198
@@ -0,0 +1,198 @@
1+// Let's first initialize sigma:
2+//
3+
4+let fetchArray = [];
5+for (let i = 0; i < 256; i++) {
6+ fetchArray.push(
7+ fetch(`./galaxies/${i.toString()}.json`).then(r => r.json())
8+ );
9+}
10+
11+let searchButton = document.getElementById('searchButton');
12+
13+Promise.all(fetchArray).then(values => {
14+ sigma.settings.minEdgeSize = 0;
15+ sigma.settings.maxEdgeSize = 0;
16+ sigma.settings.minNodeSize = 0;
17+ sigma.settings.maxNodeSize = 0;
18+ sigma.settings.zoomMin = 0.00001;
19+ sigma.settings.labelThreshold = 20;
20+ sigma.settings.font = "monospace";
21+
22+ let s = new sigma({
23+ renderer: {
24+ container: document.getElementById('container'),
25+ type: 'webgl'
26+ }
27+ });
28+ let i = 0;
29+
30+ let offsetX = 0;
31+ let offsetY = 0;
32+
33+ let a = 120000000;
34+ let b = 8000000;
35+ let theta = 0;
36+ while (i < values.length) {
37+ offsetX = 0 + (a + b * theta) * Math.cos(theta);
38+ offsetY = 0 + (a + b * theta) * Math.sin(theta);
39+
40+ galaxy(values[i], s, offsetX, offsetY);
41+
42+ theta = 0.12 * (i+1);
43+ i++;
44+ }
45+
46+ s.refresh();
47+
48+ let headerDiv = document.getElementById('header');
49+ headerDiv.style = 'opacity:1;';
50+
51+ let search = document.getElementById('header');
52+ let searchInput = document.getElementById('searchInput');
53+
54+ let searchFunc = () => {
55+ let ob = window['urbit-ob'].default;
56+ let point = s.graph.nodes(searchInput.value);
57+ if (!point) {
58+ return;
59+ }
60+
61+ let ratio = 0.05;
62+ if (ob.clan(searchInput.value) === 'star') {
63+ ratio = 0.018;
64+ } else if (ob.clan(searchInput.value) === 'planet') {
65+ ratio = 0.003;
66+ }
67+
68+ s.cameras['0'].goTo({
69+ x: point['read_cam0:x'],
70+ y: point['read_cam0:y'],
71+ ratio: ratio,
72+ angle: 0
73+ });
74+ };
75+
76+ searchButton.addEventListener('click', searchFunc);
77+ searchInput.addEventListener('keyup', (e) => {
78+ if (e.key === 'Enter') {
79+ searchFunc();
80+ }
81+ });
82+});
83+
84+const galaxy = (data, s, offsetX = 0, offsetY = 0) => {
85+ // Then, let's add some data to display:
86+ let ob = window['urbit-ob'].default;
87+
88+ for (let [key, val] of Object.entries(data)) {
89+ s.graph.addNode({
90+ id: ob.patp(key),
91+ label: ob.patp(key),
92+ size: 4,
93+ color: '#999',
94+ x: 0 + offsetX,
95+ y: 0 + offsetY
96+ });
97+
98+ let numOfStars = Object.entries(val).length;
99+ let starIter = 0;
100+ for (let [starKey, starVal] of Object.entries(val)) {
101+ const circle = 2 * Math.PI;
102+ let line = 40000 * 128;
103+ let starX = 0 + Math.cos(circle * starIter / numOfStars) * line;
104+ let starY = 0 + Math.sin(circle * starIter / numOfStars) * line;
105+
106+ s.graph.addNode({
107+ id: ob.patp(starKey),
108+ label: ob.patp(starKey),
109+ size: 1.5,
110+ color: '#444',
111+ x: starX + offsetX,
112+ y: starY + offsetY
113+ }).addEdge({
114+ id: `e-star${starKey}`,
115+ source: ob.patp(key),
116+ target: ob.patp(starKey),
117+ color: '#444',
118+ size: 0.2
119+ });
120+
121+ let starAngle = Math.atan2(starY, starX);
122+ let numOfPlanets = Object.entries(starVal).length;
123+ let planetIter = 0;
124+
125+ for (let [planetKey, planetVal] of Object.entries(starVal)) {
126+ let planetAngle = starAngle + (Math.PI * 2 * (planetIter / numOfPlanets));
127+
128+ let lineLen = 0;
129+ let lineColor = '#000';
130+ let nodeColor = '#000';
131+
132+ if (planetIter % 4 === 0) {
133+ lineLen = 19000;
134+ lineColor = '#000';
135+ nodeColor = '#000';
136+ } else if (planetIter % 4 === 1) {
137+ lineLen = 16000;
138+ lineColor = '#ccc';
139+ nodeColor = '#ccc';
140+ } else if (planetIter % 4 === 2) {
141+ lineLen = 13000;
142+ lineColor = '#555';
143+ nodeColor = '#555';
144+ } else if (planetIter % 4 === 3) {
145+ lineLen = 16000;
146+ lineColor = '#ccc';
147+ nodeColor = '#ccc';
148+ }
149+
150+ if (numOfStars < 5) {
151+ lineLen = lineLen * 144;
152+ } else if (numOfStars < 7) {
153+ lineLen = lineLen * 128;
154+ } else if (numOfStars < 9) {
155+ lineLen = lineLen * 96;
156+ } else if (numOfStars < 11) {
157+ lineLen = lineLen * 80;
158+ } else if (numOfStars < 13) {
159+ lineLen = lineLen * 64;
160+ } else if (numOfStars < 15) {
161+ lineLen = lineLen * 56;
162+ } else if (numOfStars < 17) {
163+ lineLen = lineLen * 48;
164+ } else if (numOfStars < 19) {
165+ lineLen = lineLen * 40;
166+ } else if (numOfStars < 21) {
167+ lineLen = lineLen * 36;
168+ } else if (numOfStars < 23) {
169+ lineLen = lineLen * 32;
170+ } else {
171+ lineLen = lineLen * 24;
172+ }
173+
174+ let planetX = starX + (lineLen * Math.cos(planetAngle));
175+ let planetY = starY + (lineLen * Math.sin(planetAngle));
176+
177+ s.graph.addNode({
178+ id: ob.patp(planetVal),
179+ label: ob.patp(planetVal),
180+ size: 0.2,
181+ color: nodeColor,
182+ x: planetX + offsetX,
183+ y: planetY + offsetY
184+ }).addEdge({
185+ id: `e-planet${planetVal}`,
186+ source: ob.patp(starKey),
187+ target: ob.patp(planetVal),
188+ size: 0.05,
189+ color: lineColor
190+ });
191+
192+ planetIter++;
193+ }
194+
195+ starIter++;
196+ }
197+ }
198+};
ui/src/App.svelte+63−13
@@ -1,26 +1,76 @@
11<script lang="ts">
22 import Router from 'svelte-spa-router';
3− import Urbit from '@urbit/http-api';
4− import { Charges, ChargeUpdateInitial, scryCharges } from '@urbit/api';
3+ import SigilPage from './routes/SigilPage.svelte';
4+ import HelpPage from './routes/HelpPage.svelte';
55 import Home from './routes/Home.svelte';
6− import ShipViewPage from './routes/ShipViewPage.svelte';
7− import "./styles/tailwind-output.css";
6+ import HelpModal from '@/docs/HelpModal.svelte';
7+ import "./styles/styles.scss";
8+ import docs from 'stores/docs';
9+ import keys from 'stores/keys';
10+
11+ // const routes = {
12+ // '/help/:path?': HelpPage,
13+ // // '/:patp?': Home,
14+ // '/ship/:patp?': Home,
15+ // '/search/:search?': Home,
16+ // '*': Home,
17+ // }
818
919 const routes = {
10− '/': Home,
11− '/ship/:patp': ShipViewPage,
12− '*': Home,
20+ '/sigil': SigilPage,
21+ '/help/:path?': HelpPage,
22+ // '/:patp?': Home,
23+ // '/ship/:patp?': Home,
24+ // '/search/:search?': Home,
25+ '/:mode?/:arg?': Home,
26+ }
27+
28+ import pageName from './stores/pageName';
29+
30+ $: title = $pageName ? `Astrolabe - ${$pageName}` : 'Astrolabe';
31+
32+ $: {
33+ document.title = title
34+ }
35+
36+ function inputEvent(event) {
37+ if (event.target.tagName == 'INPUT') return true;
38+ if (event.target.tagName == 'TEXTAREA') return true;
39+ return false;
40+ }
41+
42+ function handleKeyDown(event: KeyboardEvent) {
43+ keys.runCallbacks($keys.globalkeydown, event);
44+ if (!inputEvent(event)) {
45+ keys.runCallbacks($keys.keydown, event);
46+ }
47+ }
48+
49+ function handleKeyUp(event: KeyboardEvent) {
50+ keys.runCallbacks($keys.globalkeyup, event);
51+ if (!inputEvent(event)) {
52+ keys.runCallbacks($keys.keyup, event);
53+ }
54+ }
55+
56+ $:{
57+ $keys.keyup.get('?').set('app', () => docs.show(''));
58+ $keys.keyup.get('shift+?').set('app', () => docs.show(''));
59+ $keys.globalkeyup.get('escape').set('app', (event) => {
60+ if (inputEvent(event)) {
61+ event.target.blur();
62+ }
63+ });
1364 }
1465</script>
1566
16−<main>
67+<main class="h-full overflow-y-auto">
68+ <HelpModal />
1769 <Router {routes} />
1870</main>
1971
2072<svelte:head>
21− <style>
22− body {
23− background-color: #1f243c;
24− }
25− </style>
73+ <title>{title}</title>
2674</svelte:head>
75+
76+<svelte:body on:keydown={handleKeyDown} on:keyup={handleKeyUp} />
ui/src/actions/docLinks.tsnew file+26
@@ -0,0 +1,26 @@
1+const docLinkRegex = /\/~\/scry\/docs\/usr\/astrolabe\/([-~._\w\d]+).html/;
2+
3+function convertDocLinks(node, onPathChange) {
4+ const links = node.querySelectorAll('a');
5+ for (let link of links) {
6+ let match = link.href.match(docLinkRegex);
7+ if (match) {
8+ let path = match[1];
9+ link.href="javascript:void(0)";
10+ link.addEventListener('click', () => onPathChange(path));
11+ // const button = document.createElement('button');
12+ // while(link.firstChild) {
13+ // button.appendChild(link.firstChild);
14+ // }
15+ // link.parentNode.replaceChild(button, link);
16+ // debugger;
17+ }
18+ }
19+}
20+
21+export default function docLinks(node, onPathChange: Function) {
22+ convertDocLinks(node, onPathChange)
23+ return {
24+ update: (onPathChange) => convertDocLinks(node, onPathChange),
25+ };
26+};
ui/src/actions/symbolPartClick.tsnew file+27
@@ -0,0 +1,27 @@
1+function listenOnSvgElements(node, onPathChange) {
2+ const parts = node.querySelectorAll('path, circle, line');
3+ for (let part of parts) {
4+ if (part.getAttribute('dataisgeon') === 'true') continue;
5+ if (onPathChange) {
6+ part.onclick = () => {
7+ onPathChange(part.getAttribute('part-key'));
8+ };
9+ } else {
10+ part.onclick = null;
11+ }
12+ }
13+}
14+
15+export default function svgClick(node: HTMLElement, { onPartClick, enabled = true, svgString }) {
16+ if (enabled) listenOnSvgElements(node, onPartClick);
17+
18+ return {
19+ update: ({ onPartClick, enabled = true, svgString }) => {
20+ if (enabled) {
21+ listenOnSvgElements(node, onPartClick);
22+ } else {
23+ listenOnSvgElements(node, undefined);
24+ }
25+ },
26+ };
27+}
ui/src/actions/tooltip.tsnew file+73
@@ -0,0 +1,73 @@
1+import tippy from 'tippy.js';
2+// import DocLink from '@/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
3+// const app = new DocLink({
4+// target: document.getElementBySelector('.tippy')
5+// })
6+
7+const defaultParams = {
8+ theme: 'astrolabe',
9+ delay: [500, 0],
10+};
11+
12+function getParams(paramsOrContent:any = {}, overrides = {}) {
13+ let params = paramsOrContent;
14+ if (typeof paramsOrContent === 'string') {
15+ params = { content: paramsOrContent };
16+ }
17+ return {
18+ ...defaultParams,
19+ ...params,
20+ ...overrides,
21+ };
22+}
23+
24+export default function tooltip(node, paramsOrContent: any = {}) {
25+ const onTrigger = () => {
26+ // console.log('triggered');
27+ node.dispatchEvent(new CustomEvent("tooltipTriggered"));
28+ };
29+ const onUntrigger = () => {
30+ // console.log('untriggered');
31+ node.dispatchEvent(new CustomEvent("tooltipUntriggered"));
32+ };
33+ const onClickOutside = (_, e) => {
34+ e.stopPropagation();
35+ };
36+ const listeners = {
37+ onTrigger,
38+ onUntrigger,
39+ onClickOutside,
40+ }
41+
42+ const params = getParams(paramsOrContent, listeners);
43+ const { content } = params;
44+
45+ // Let's make sure the "aria-label" attribute
46+ // is set so our element is accessible:
47+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute
48+ if (!node.getAttribute("aria-label")) node.setAttribute("aria-label", content);
49+
50+
51+ // Support any of the Tippy props by forwarding all "params":
52+ // https://atomiks.github.io/tippyjs/v6/all-props/
53+ const tip: any = tippy(node, params);
54+ const hideTip = (e) => {
55+ if (tip.state.isShown) {
56+ tip.hide();
57+ // e.stopPropagation();
58+ }
59+ }
60+ node.addEventListener('click', hideTip);
61+
62+
63+ return {
64+ // If the props change, let's update the Tippy instance:
65+ update: (newParams) => tip.setProps(getParams(newParams)),
66+
67+ // Clean up the Tippy instance on unmount:
68+ destroy: () => {
69+ node.removeEventListener('click', hideTip);
70+ tip.destroy();
71+ }
72+ };
73+};
ui/src/assets/astrolabe-tile.svgnew file+23
@@ -0,0 +1,23 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+<svg width="440mm" height="440mm" version="1.1" viewBox="-100 -100 440 440" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
4+ <defs>
5+ <linearGradient id="gold">
6+ <stop stop-color="#fcd34d" offset="0"/>
7+ </linearGradient>
8+ <linearGradient id="linearGradient3024" x1="95" x2="105" y1="47.5" y2="47.5" gradientTransform="translate(20,-30)" gradientUnits="userSpaceOnUse" xlink:href="#gold"/>
9+ <linearGradient id="linearGradient3030" x2="200" y1="150" y2="150" gradientTransform="translate(20,-30)" gradientUnits="userSpaceOnUse" xlink:href="#gold"/>
10+ <linearGradient id="linearGradient3036" x1="60" x2="180" y1="170" y2="170" gradientTransform="matrix(1.0909 0 0 1.0909 4.0909 -50.455)" gradientUnits="userSpaceOnUse" xlink:href="#gold"/>
11+ <linearGradient id="linearGradient3066" x1="27.226" x2="161.28" y1="154.82" y2="154.82" gradientTransform="rotate(19 199.64 194.76)" gradientUnits="userSpaceOnUse" xlink:href="#gold"/>
12+ <linearGradient id="linearGradient3072" x1="90" x2="110" y1="150" y2="150" gradientTransform="translate(20,-30)" gradientUnits="userSpaceOnUse" xlink:href="#gold"/>
13+ <linearGradient id="linearGradient4362" x2="240" y1="120" y2="120" gradientTransform="matrix(1.8333 0 0 1.8333 -100 -100)" gradientUnits="userSpaceOnUse">
14+ <stop stop-color="#1f243c" offset="0"/>
15+ </linearGradient>
16+ </defs>
17+ <rect x="-100" y="-100" width="440" height="440" fill="url(#linearGradient4362)"/>
18+ <circle cx="120" cy="120" r="95" fill="none" stroke="url(#linearGradient3030)" stroke-width="10"/>
19+ <circle cx="135" cy="135" r="60" fill="none" stroke="url(#linearGradient3036)" stroke-width="10"/>
20+ <circle cx="120" cy="120" r="10" fill="url(#linearGradient3072)"/>
21+ <path d="m35.978 152.25 140.04-53.755-5.7521 12.92-46.679 17.918-7.1673-18.672-74.686 28.669z" fill="url(#linearGradient3066)"/>
22+ <path d="m120 25v-20" fill="none" stroke="url(#linearGradient3024)" stroke-linecap="round" stroke-width="10"/>
23+</svg>
ui/src/assets/astrolabe.svgnew file+206
@@ -0,0 +1,206 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3+
4+<svg
5+ width="240mm"
6+ height="240mm"
7+ viewBox="0 0 240 240"
8+ version="1.1"
9+ id="svg5"
10+ inkscape:version="1.1.1 (c3084ef, 2021-09-22)"
11+ sodipodi:docname="astrolabe.svg"
12+ inkscape:export-filename="/Users/john/ws/urbit-apps/astrolabe/extras/astrolabe.png"
13+ inkscape:export-xdpi="96"
14+ inkscape:export-ydpi="96"
15+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
16+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
17+ xmlns:xlink="http://www.w3.org/1999/xlink"
18+ xmlns="http://www.w3.org/2000/svg"
19+ xmlns:svg="http://www.w3.org/2000/svg">
20+ <sodipodi:namedview
21+ id="namedview7"
22+ pagecolor="#ffffff"
23+ bordercolor="#999999"
24+ borderopacity="1"
25+ inkscape:pageshadow="0"
26+ inkscape:pageopacity="0"
27+ inkscape:pagecheckerboard="0"
28+ inkscape:document-units="mm"
29+ showgrid="false"
30+ inkscape:zoom="0.4798268"
31+ inkscape:cx="452.24652"
32+ inkscape:cy="416.81707"
33+ inkscape:window-width="1549"
34+ inkscape:window-height="1080"
35+ inkscape:window-x="490"
36+ inkscape:window-y="130"
37+ inkscape:window-maximized="0"
38+ inkscape:current-layer="layer1" />
39+ <defs
40+ id="defs2">
41+ <linearGradient
42+ id="navy"
43+ inkscape:swatch="solid">
44+ <stop
45+ style="stop-color:#1f243c;stop-opacity:1;"
46+ offset="0"
47+ id="stop4358" />
48+ </linearGradient>
49+ <linearGradient
50+ id="linearGradient3070"
51+ inkscape:swatch="solid">
52+ <stop
53+ style="stop-color:#ffff00;stop-opacity:1;"
54+ offset="0"
55+ id="stop3068" />
56+ </linearGradient>
57+ <linearGradient
58+ id="linearGradient3064"
59+ inkscape:swatch="solid">
60+ <stop
61+ style="stop-color:#ff3500;stop-opacity:1;"
62+ offset="0"
63+ id="stop3062" />
64+ </linearGradient>
65+ <linearGradient
66+ id="linearGradient3040"
67+ inkscape:swatch="solid">
68+ <stop
69+ style="stop-color:#fcd34d;stop-opacity:1;"
70+ offset="0"
71+ id="stop3038" />
72+ </linearGradient>
73+ <linearGradient
74+ id="linearGradient3034"
75+ inkscape:swatch="solid">
76+ <stop
77+ style="stop-color:#f2c11c;stop-opacity:1;"
78+ offset="0"
79+ id="stop3032" />
80+ </linearGradient>
81+ <linearGradient
82+ id="linearGradient3028"
83+ inkscape:swatch="solid">
84+ <stop
85+ style="stop-color:#f2e41c;stop-opacity:1;"
86+ offset="0"
87+ id="stop3026" />
88+ </linearGradient>
89+ <linearGradient
90+ id="gold"
91+ inkscape:swatch="solid">
92+ <stop
93+ style="stop-color:#fcd34d;stop-opacity:1;"
94+ offset="0"
95+ id="stop3020" />
96+ </linearGradient>
97+ <linearGradient
98+ inkscape:collect="always"
99+ xlink:href="#gold"
100+ id="linearGradient3024"
101+ x1="95"
102+ y1="47.5"
103+ x2="105"
104+ y2="47.5"
105+ gradientUnits="userSpaceOnUse"
106+ gradientTransform="translate(20,-30)" />
107+ <linearGradient
108+ inkscape:collect="always"
109+ xlink:href="#gold"
110+ id="linearGradient3030"
111+ x1="0"
112+ y1="150"
113+ x2="200"
114+ y2="150"
115+ gradientUnits="userSpaceOnUse"
116+ gradientTransform="translate(20,-30)" />
117+ <linearGradient
118+ inkscape:collect="always"
119+ xlink:href="#gold"
120+ id="linearGradient3036"
121+ x1="60.000027"
122+ y1="170"
123+ x2="179.99998"
124+ y2="170"
125+ gradientUnits="userSpaceOnUse"
126+ gradientTransform="matrix(1.0909095,0,0,1.0909095,4.090855,-50.454621)" />
127+ <linearGradient
128+ inkscape:collect="always"
129+ xlink:href="#gold"
130+ id="linearGradient3066"
131+ x1="27.225779"
132+ y1="154.82091"
133+ x2="161.28355"
134+ y2="154.82091"
135+ gradientUnits="userSpaceOnUse"
136+ gradientTransform="rotate(19,199.63646,194.75765)" />
137+ <linearGradient
138+ inkscape:collect="always"
139+ xlink:href="#gold"
140+ id="linearGradient3072"
141+ x1="90"
142+ y1="150"
143+ x2="110"
144+ y2="150"
145+ gradientUnits="userSpaceOnUse"
146+ gradientTransform="translate(20,-30)" />
147+ <linearGradient
148+ inkscape:collect="always"
149+ xlink:href="#navy"
150+ id="linearGradient4362"
151+ x1="0"
152+ y1="120"
153+ x2="240"
154+ y2="120"
155+ gradientUnits="userSpaceOnUse"
156+ gradientTransform="matrix(1.8333333,0,0,1.8333333,-99.999996,-99.999996)" />
157+ </defs>
158+ <g
159+ inkscape:label="Layer 1"
160+ inkscape:groupmode="layer"
161+ id="layer1">
162+ <rect
163+ style="display:none;fill:url(#linearGradient4362);fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
164+ id="rect4185"
165+ width="440"
166+ height="440"
167+ x="-100"
168+ y="-100"
169+ inkscape:label="bg" />
170+ <circle
171+ style="display:inline;fill:none;fill-rule:evenodd;stroke:url(#linearGradient3030);stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
172+ id="path31"
173+ cx="120"
174+ cy="120"
175+ r="95"
176+ inkscape:label="mater plate" />
177+ <circle
178+ style="display:inline;fill:none;fill-rule:evenodd;stroke:url(#linearGradient3036);stroke-width:9.99995;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
179+ id="circle996"
180+ cx="135"
181+ cy="134.99998"
182+ r="60.000023"
183+ inkscape:label="rete" />
184+ <circle
185+ style="fill:url(#linearGradient3072);fill-opacity:1;stroke:none;stroke-width:9.99999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
186+ id="path1284"
187+ cx="120"
188+ cy="120"
189+ r="10"
190+ inkscape:label="center" />
191+ <path
192+ style="display:inline;fill:url(#linearGradient3066);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
193+ d="m 35.977829,152.25317 140.036991,-53.75521 -5.75214,12.9195 -46.679,17.91834 -7.16733,-18.67158 -74.686466,28.66941 z"
194+ id="path1819"
195+ sodipodi:nodetypes="ccccccc"
196+ inkscape:transform-center-y="5.3755585"
197+ inkscape:transform-center-x="14.003679"
198+ inkscape:label="alidade" />
199+ <path
200+ style="fill:none;stroke:url(#linearGradient3024);stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
201+ d="M 120,25 V 5"
202+ id="path2712"
203+ sodipodi:nodetypes="cc"
204+ inkscape:label="tab" />
205+ </g>
206+</svg>
ui/src/assets/up-arrow.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="arrow-ios-upward"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M18 15a1 1 0 0 1-.64-.23L12 10.29l-5.37 4.32a1 1 0 0 1-1.41-.15 1 1 0 0 1 .15-1.41l6-4.83a1 1 0 0 1 1.27 0l6 5a1 1 0 0 1 .13 1.41A1 1 0 0 1 18 15z"/></g></g></svg>
ui/src/assets/x.svgnew file+1
@@ -0,0 +1 @@
1+<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g data-name="Layer 2"><g data-name="close"><rect width="24" height="24" transform="rotate(180 12 12)" opacity="0"/><path d="M13.41 12l4.3-4.29a1 1 0 1 0-1.42-1.42L12 10.59l-4.29-4.3a1 1 0 0 0-1.42 1.42l4.3 4.29-4.3 4.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l4.29-4.3 4.29 4.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"/></g></g></svg>
ui/src/components/CollapsibleContent.sveltenew file+18
@@ -0,0 +1,18 @@
1+<script lang="ts">
2+ import { slide } from 'svelte/transition';
3+ export let open: boolean = true;
4+</script>
5+
6+<div class="mt-2">
7+ <button on:click={() => (open = !open)}>
8+ <span class="font-mono inline-block transition duration-150 {open ? 'rotate-90' : ''}">▶</span>
9+ &nbsp;
10+ <slot name="title"></slot>
11+ </button>
12+ {#if open}
13+ <!-- turns out it's important not to put an await in the content -->
14+ <div class="ml-[22px]" transition:slide|local="{{duration: 150 }}">
15+ <slot name="content"></slot>
16+ </div>
17+ {/if}
18+</div>
ui/src/components/ExtensionWindow.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ export let name: string;
3+ export let open = true;
4+ export let halfWidth = false;
5+ $: containerClasses = open ? ('w-full ' + (halfWidth ? 'md:w-1/2' : '')) : 'w-min justify-self-start';
6+ $: boxClasses = open ? 'border-gray-500' : 'border-gray-400 bg-gray-400';
7+ $: buttonClasses = open ? 'text-gray-600' : 'text-white';
8+</script>
9+
10+<div class="p-1 {containerClasses}">
11+ <div class="border rounded-md {boxClasses}" on:click={() => { if (!open) open = true; }}>
12+ <div class="flex flex-wrap">
13+ <button class="px-2 text-sm {buttonClasses}" on:click|stopPropagation={() => open = !open}>
14+ {name}
15+ </button>
16+ {#if open}
17+ <slot name="top-bar"/>
18+ {/if}
19+ </div>
20+ {#if open}
21+ <slot/>
22+ {/if}
23+ </div>
24+</div>
25+
ui/src/components/HomeScreenTiles.sveltenew file+42
@@ -0,0 +1,42 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import { link } from 'svelte-spa-router'
4+ import DocLink from './docs/DocLink.svelte';
5+
6+ const dispatch = createEventDispatcher();
7+ function navigate(path) {
8+ dispatch('navigate', path);
9+ }
10+
11+ function convertLinks(html: string): string {
12+ let converted = html;
13+ const externalLinkRegex = /href="http/g;
14+ converted = converted.replace(externalLinkRegex, 'target="_blank" href="http');
15+ return converted;
16+ }
17+</script>
18+
19+<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
20+ <div class="rounded-lg bg-white grow max-w-xs">
21+ <DocLink modal path="astrolabe">
22+ <div class="p-5 text-xl text-center">
23+ About Astrolabe
24+ </div>
25+ </DocLink>
26+ </div>
27+ <div class="rounded-lg bg-white grow max-w-xs">
28+ <DocLink modal path="">
29+ <div class="p-5 text-xl text-center">
30+ About Urbit IDs
31+ </div>
32+ </DocLink>
33+ </div>
34+ <a class="rounded-lg bg-navy border border-gold grow max-w-s" use:link href="/chart">
35+ <p class="p-5 text-xl text-center text-white">
36+ <br/>
37+ Star Chart
38+ <br/>
39+ <br/>
40+ </p>
41+ </a>
42+</div>
ui/src/components/SearchBar.sveltedeleted−54
@@ -1,54 +0,0 @@
1−<script lang="ts">
2− import ValidationProblems from './ValidationProblems.svelte';
3−
4− import { SearchAnalysis, analyzeSearch } from '../lib/id';
5−
6− export let patp: string = undefined;
7− export let searchQuery: RegExp = /~der/;
8− let searchText = 'der';
9− let analysis: SearchAnalysis;
10−
11− $: {
12− analysis = analyzeSearch(searchText);
13− if (analysis.queryIsValid) {
14− searchQuery = analysis.query;
15− } else {
16− searchQuery = undefined;
17− }
18− if (analysis.patpIsValid) {
19− patp = analysis.patp;
20− } else {
21− patp = undefined;
22− }
23− }
24−</script>
25−
26−<div class="flex justify-center">
27− <div class="relative grow max-w-md rounded-full">
28− <input type="text" class="p-4 py-2 w-full rounded-full"
29− spellcheck="false"
30− placeholder="Enter an ID or Azimuth number. '*' is wildcard."
31− bind:value={searchText}
32− />
33− <button class="absolute right-0 w-10 h-10 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"></button>
34− </div>
35−</div>
36−<div class="bg-gray-300 p-2 rounded-lg">
37− <p>
38− text: {searchText}
39− </p>
40− <p>
41− query: {searchQuery}
42− </p>
43−</div>
44−{#if searchText !== '' && analysis && analysis.queryProblems.length > 0}
45− <div class="flex justify-center">
46− <ValidationProblems type="Query Problems" problems={analysis.queryProblems}></ValidationProblems>
47− </div>
48−{/if}
49−
50−<!-- {#if searchText !== '' && analysis && analysis.patpProblems.length > 0}
51− <div class="flex justify-center">
52− <ValidationProblems type="ID Problems" problems={analysis.patpProblems}></ValidationProblems>
53− </div>
54−{/if} -->
ui/src/components/SearchResults.sveltedeleted−68
@@ -1,68 +0,0 @@
1−<script lang="ts">
2− import { Groups, Rolodex } from '@urbit/api';
3− import store from '../stores/store';
4− import ShipListing from './ShipListing.svelte';
5− import SearchResultNavButtons from './SearchResultNavButtons.svelte';
6− import ShipView from './ShipView.svelte';
7− export let searchQuery : RegExp;
8− export let patp : string;
9− let contactsList = [];
10− let selectedShipIndex: number;
11−
12− $: {
13− if (!patp) {
14− store.search(searchQuery)
15− }
16− // if (searchQuery) {
17− // store.search(searchQuery)
18− // } else {
19− // store.reset();
20− // }
21− }
22− $: {
23− contactsList = Object.entries($store?.contacts).map(([patp, ship]) => {
24− return {
25− ...ship,
26− patp,
27− };
28− });
29− if (contactsList.length === 1) {
30− selectedShipIndex = 0;
31− } else {
32− selectedShipIndex = null;
33− }
34− }
35− $: selectedShip = contactsList[selectedShipIndex]
36−
37− function updateIndex(e) {
38− selectedShipIndex = e.detail.index;
39− }
40−</script>
41−
42−<!-- should actually switch on whether field is empty -->
43−<!-- searchQuery is undefined when query is invalid -->
44−<!-- {#if searchQuery === undefined}
45− <p class="p-6 rounded-lg bg-white">
46− Hello, ~{window.ship}, would you like to search?
47− </p>
48−{/if} -->
49−{#if patp}
50− <ShipView {patp} />
51−{:else if selectedShip}
52− {#if contactsList.length > 1}
53− <SearchResultNavButtons
54− index={selectedShipIndex}
55− totalResults={contactsList.length}
56− on:updateIndex={updateIndex}
57− />
58− {/if}
59− <ShipView patp={selectedShip.patp} />
60−{:else}
61− <div class="flex justify-center">
62− <div class="max-w-md w-full rounded-lg bg-white">
63− {#each contactsList as contact, i (contact.patp)}
64− <ShipListing ship={contact} on:click={() => selectedShipIndex = i}/>
65− {/each}
66− </div>
67− </div>
68−{/if}
ui/src/components/ShipListing.svelte+37−11
@@ -1,16 +1,42 @@
11<script lang="ts">
2− import Sigil from "./Sigil.svelte";
2+
3+ import Sigil from "@/common/Sigil.svelte";
4+ import PalStatusIndicator from '@/pals/PalStatusIndicator.svelte';
5+ import { push } from 'svelte-spa-router'
6+ import { normalizeId } from 'lib/id';
7+ import { linkToShip as generateLinkToShip } from 'lib/link';
8+ import { getPalStatus } from 'lib/pals';
9+ import store from 'stores/store';
310 export let ship: any;
11+ export let linkToShip: boolean = false;
12+ export let differentiateContacts = false;
13+
14+ $: patp = normalizeId(ship.patp);
15+ $: contact = $store.contacts[ship.patp];
16+ $: borderClass = differentiateContacts ? (
17+ 'border-l-8 ' + (contact ? 'border-gold' : 'border-gray-300')
18+ ) : '';
19+
20+ function handleClick() {
21+ if (linkToShip) {
22+ push(generateLinkToShip(patp));
23+ }
24+ }
425</script>
526
6−<div class="flex flex-wrap p-4 border-b last:border-none items-center"
7− on:click
8− >
9− <Sigil patp={ship.patp} size={48} />
10− <!-- {#if ship.avatar}
11− <img src={ship.avatar} class="h-12 w-12 mr-4" />
12− {/if} -->
13− <p class="ml-4 break-words" >
14− {ship.patp}<br/>{ship.nickname ? `(${ship.nickname})` : ''}
15− </p>
27+<div class="hover:bg-gray-200 {borderClass} cursor-pointer"
28+on:click={handleClick}
29+on:click
30+>
31+ <div class="flex p-4 items-center gap-x-4">
32+ <Sigil {patp} size={48} useNew noShrink />
33+ <p class="break-words grow">
34+ <span class="align-middle mr-1">
35+ {patp}
36+ </span>
37+ <PalStatusIndicator status={getPalStatus($store.pals, patp)} class="inline" />
38+ <br/>
39+ {contact?.nickname ? `(${contact.nickname})` : ''}
40+ </p>
41+ </div>
1642</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.sveltedeleted−58
@@ -1,58 +0,0 @@
1−<script lang="ts">
2− import { clan, sein, patp2dec } from 'urbit-ob';
3− import { link } from 'svelte-spa-router'
4− import { linkToShip } from '../lib/link';
5− import Sigil from "./Sigil.svelte";
6− export let patp: string;
7− let parentChain: any[];
8−
9− $: shipClass = clan(patp);
10− $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
11− $: {
12− let parents = [patp];
13− let galaxyFound = false;
14− let count = 10;
15− while (!galaxyFound && count > 0) {
16− count--;
17− if (count == 0) {
18− throw new Error("You've got an infinite loop in building the parent chain");
19− }
20− let firstItem = parents[0];
21− if (clan(firstItem) == 'galaxy') {
22− galaxyFound = true;
23− } else {
24− parents.unshift(sein(firstItem))
25− }
26− }
27− parents.pop();
28− parentChain = parents.map(parent => {
29− return { patp: parent, link: linkToShip(parent) };
30− });
31− }
32−</script>
33−
34−<div class="flex justify-center">
35− <div class="p-8 max-w-md w-full rounded-lg bg-white">
36− <div class="mx-auto">
37− <Sigil patp={patp} size={256} />
38− </div>
39− <h2 class="text-2xl text-center">{patp}</h2>
40− <p>
41− Ship Type: {shipClass}
42− </p>
43− {#if azPoint}
44− <p class="break-all">
45− Azimuth #: {patp2dec(patp)}
46− </p>
47− {/if}
48− {#if parentChain.length > 0}
49− <p>
50− Parent:
51− {#each parentChain as parent, index}
52− {#if index > 0}{' > '} {/if}
53− <a use:link={parent.link}>{parent.patp}</a>
54− {/each}
55− </p>
56− {/if}
57− </div>
58−</div>
ui/src/components/Sigil.sveltedeleted−36
@@ -1,36 +0,0 @@
1−<script lang="ts">
2− import { clan } from 'urbit-ob';
3− import { sigil, stringRenderer } from '@tlon/sigil-js';
4− import moonPng from '../assets/moon.png';
5− import cometSvg from '../assets/comet.svg';
6−
7− export let patp: string;
8− export let size: number;
9− let svgString: any;
10− let imgSrc: any;
11−
12− $: shipClass = clan(patp);
13− $: displaySigil = ['galaxy', 'star', 'planet'].includes(shipClass);
14− $: {
15− if (displaySigil) {
16− svgString = sigil({
17− patp: patp,
18− renderer: stringRenderer,
19− size,
20− colors: ['black', 'white'],
21− });
22− } else if (shipClass === 'moon') {
23− imgSrc = moonPng;
24− } else {
25− imgSrc = cometSvg;
26− }
27− }
28−</script>
29−
30−<span style="height: {size}px; width: {size}px;" >
31− {#if displaySigil}
32− {@html svgString}
33− {:else}
34− <img src={imgSrc} alt={shipClass} style="height: {size}px; width: {size}px;" />
35− {/if}
36−</span>
ui/src/components/StarChart.sveltenew file+233
@@ -0,0 +1,233 @@
1+<script lang="ts">
2+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
3+
4+ import { push } from 'svelte-spa-router';
5+ import { onDestroy } from 'svelte';
6+
7+ import Sigma from 'sigma-jh';
8+ import Graph from 'graphology';
9+
10+ import { clan } from 'urbit-ob';
11+ import DocLink from './docs/DocLink.svelte';
12+ import store from 'stores/store';
13+ import keys from 'stores/keys';
14+ import { parseChartData, populateGraph } from 'lib/graph';
15+ import { getChartData } from 'lib/api';
16+ import { linkToShip } from 'lib/link';
17+
18+ import InlineSelect from './buttons/InlineSelect.svelte';
19+
20+ export let patp: string;
21+ let s;
22+ let g = new Graph();
23+ let highlightedPatp;
24+
25+ let firstRender = true;
26+ let mouseDown = false;
27+ let isDragging = false;
28+ let stoppedDragging = Date.now();
29+
30+ function sigma(container: HTMLElement, params: any = {}) {
31+ try {
32+ window['s'] = s = new Sigma(g, container, {
33+ renderLabels: false,
34+ // hideEdgesOnMove: true,
35+ allowInvalidContainer: true,
36+ // labelColor: { color: WHITE },
37+ labelFont: 'monospace',
38+ labelRenderedSizeThreshold: 2.2,
39+ zIndex: true,
40+ maxCameraRatio: 10,
41+ });
42+
43+ s.on('clickNode', ({ node }) => {
44+ if (!isDragging && 100 < Date.now() - stoppedDragging) {
45+ push(linkToShip(node));
46+ }
47+ });
48+ s.on('afterRender', () => {
49+ if (firstRender) {
50+ firstRender = false;
51+ searchFunc();
52+ }
53+ });
54+ const mc = s.getMouseCaptor();
55+ mc.on("mousedown", () => {
56+ mouseDown = true;
57+ });
58+ mc.on('mousemove', () => {
59+ if (mouseDown) {
60+ isDragging = true;
61+ }
62+ });
63+ mc.on('mouseup', () => {
64+ if (!isDragging) {
65+ clearHighlight();
66+ } else {
67+ stoppedDragging = Date.now();
68+ }
69+ mouseDown = false;
70+ isDragging = false;
71+ })
72+
73+ } catch(e) {
74+ console.error('error in initializing sigma:', e);
75+ if (!params.retried) {
76+ setTimeout(() => sigma(container, { retried: true }), 50);
77+ }
78+ }
79+
80+ return {
81+ destroy() {
82+ s?.kill();
83+ }
84+ }
85+ }
86+
87+ let chartDataPromise: any;
88+ let rawChartData: any = null;
89+ $: {
90+ rawChartData = null;
91+ chartDataPromise = getChartData();
92+ chartDataPromise.then((info) => {
93+ rawChartData = info;
94+ }).catch((error) => {
95+ rawChartData = { error };
96+ });
97+ }
98+ $: parsedChartData = parseChartData(rawChartData, filterSet);
99+ $: [parsedChartData, childKey], populateGraph(g, parsedChartData, childKey, () => {
100+ searchFunc();
101+ });
102+
103+ function clearHighlight() {
104+ const id = highlightedPatp;
105+ if (id) {
106+ setTimeout(() => {
107+ s.graph.removeNodeAttribute(id, 'highlighted');
108+ if (id == highlightedPatp)
109+ highlightedPatp = null;
110+ });
111+ }
112+ }
113+
114+ function searchFunc() {
115+ // console.log('searching for ' + patp);
116+ if (!s) return;
117+ if (highlightedPatp !== patp) clearHighlight();
118+ if (!patp) return;
119+ let point = s.getNodeDisplayData(patp);
120+ if (!point) return;
121+ s.graph.setNodeAttribute(patp, 'highlighted', true);
122+ highlightedPatp = patp;
123+
124+ let ratio = 0.03;
125+ if (clan(patp) === 'star') {
126+ ratio = 0.008;
127+ } else if (clan(patp) === 'planet') {
128+ ratio = 0.0005;
129+ }
130+
131+ s.camera.animate({
132+ x: point.x,
133+ y: point.y,
134+ ratio: ratio,
135+ angle: 0
136+ }, {
137+ duration: 500,
138+ });
139+ };
140+
141+ $: [s, patp], searchFunc();
142+
143+ let helpBoxExpanded = false;
144+ let helpBoxClasses = '';
145+ $: {
146+ helpBoxClasses = helpBoxExpanded ?
147+ "w-full" :
148+ "border-r";
149+ }
150+ let childKey: ('sponsees' | 'children') = 'sponsees';
151+ let filterSet: Set<string> = null;
152+ $: peersSet = new Set($store.peers);
153+ $: palsSet = new Set(Object.keys($store.pals.outgoing));
154+
155+ function closeChart() {
156+ push('/');
157+ }
158+
159+ $: {
160+ $keys.keyup.get('escape').set('starChart', closeChart);
161+ }
162+
163+ onDestroy(() => {
164+ $keys.keyup.get('escape').delete('starChart');
165+ });
166+</script>
167+
168+<div class="border border-gold bg-navy w-full grow flex flex-col relative">
169+ <div class="flex flex-wrap absolute {helpBoxClasses} border-b border-gold bg-navy text-white z-20 cursor-pointer justify-items-center px-2 py-1 gap-4"
170+ on:click={() => helpBoxExpanded = !helpBoxExpanded }>
171+ {#if helpBoxExpanded}
172+ <DocLink modal path="astrolabe">
173+ <div class="text-center justify-self-start">
174+ About Astrolabe
175+ </div>
176+ </DocLink>
177+ <div class="flex flex-wrap items-center gap-2">
178+ <div class="w-3 h-3 rounded-full bg-white"></div>
179+ Galaxy
180+ </div>
181+ <div class="flex flex-wrap items-center gap-2">
182+ <div class="w-3 h-3 rounded-full bg-gold"></div>
183+ Star
184+ </div>
185+ <div class="flex flex-wrap items-center gap-2">
186+ <div class="w-3 h-3 rounded-full bg-l1-blue"></div>
187+ Planet (L1)
188+ </div>
189+ <div class="flex flex-wrap items-center gap-2">
190+ <div class="w-3 h-3 rounded-full bg-l2-green"></div>
191+ Planet (L2)
192+ </div>
193+ {#if s.nodeDataCache['~' + window.ship]}
194+ <div class="flex flex-wrap items-center gap-2">
195+ <div class="w-3 h-3 rounded-full bg-you"></div>
196+ You
197+ </div>
198+ {/if}
199+ {:else}
200+ ?
201+ {/if}
202+ </div>
203+ <div class="flex flex-wrap absolute right-0 border-l border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
204+ on:click={() => push('/') }>
205+ X
206+ </div>
207+ <div class="flex absolute -bottom-2 inset-x-0 text-white z-10 2xs:-bottom-3.5">
208+ <!-- <div class="ml-8 px-4 py-1 flex gap-x-1 items-center text-white"> -->
209+ <!-- <span>Arrange by</span> -->
210+ <InlineSelect bind:value={childKey}
211+ options={[
212+ ['Sponsors', 'sponsees'],
213+ ['Parents', 'children']
214+ ]}
215+ ></InlineSelect>
216+ <InlineSelect bind:value={filterSet}
217+ options={[
218+ ['All', null],
219+ ['Peers', peersSet],
220+ ['Pals', palsSet],
221+ ]}
222+ ></InlineSelect>
223+ <!-- </div> -->
224+ </div>
225+ {#await chartDataPromise}
226+ <div class="flex items-center justify-center absolute inset-0">
227+ <LoadingSpinner/>
228+ </div>
229+ {/await}
230+ <div class="grow flex">
231+ <div class="w-full min-h-[20px]" use:sigma></div>
232+ </div>
233+</div>
ui/src/components/ValidationProblems.svelte+4−2
@@ -1,9 +1,11 @@
11<script lang="ts">
2+ import DocLink from './docs/DocLink.svelte';
3+
24 export let problems: Array<string> = [];
35 export let type: string = undefined;
46</script>
57
6−<div class="p-4 max-w-md rounded-lg bg-white">
8+<div class="p-4 max-w-md rounded-lg bg-white w-full">
79 {#if type}<b class="w-full text-center">{type}</b>{/if}
810 <p>
911 Sorry, the ID you entered is invalid:
@@ -15,7 +17,7 @@
1517 {/each}
1618 <p>
1719 <!-- TODO: add link to doc -->
18− You can read more here.
20+ You can <DocLink modal path="patpee">read more here.</DocLink>
1921 </p>
2022</div>
2123
ui/src/components/buttons/CloseButton.sveltenew file+4
@@ -0,0 +1,4 @@
1+<button on:click class="relative w-7 h-7">
2+ <div class="absolute -mt-0.5 h-1 w-full bg-navy rounded rotate-45" />
3+ <div class="absolute -mt-0.5 h-1 w-full bg-navy rounded -rotate-45" />
4+</button>
ui/src/components/buttons/Hamburger.sveltenew file+5
@@ -0,0 +1,5 @@
1+<button on:click class="w-7 space-y-1 p-1">
2+ <div class="h-1 w-full bg-navy rounded" />
3+ <div class="h-1 w-full bg-navy rounded" />
4+ <div class="h-1 w-full bg-navy rounded" />
5+</button>
ui/src/components/buttons/InlineSelect.sveltenew file+16
@@ -0,0 +1,16 @@
1+<script lang="ts">
2+ export let options: [string, any][];
3+ export let value: any;
4+ export let borderClass = "border-gold";
5+ export let onClass = "bg-navy";
6+ export let offClass = "bg-white";
7+</script>
8+
9+<div class="flex mx-auto border {borderClass} rounded divide-x divide-gold bg-navy">
10+ {#each options as option}
11+ <div class="px-1.5 cursor-pointer" class:text-gray-400={value !== option[1]} on:click={() => value = option[1]}>
12+ {option[0]}
13+ </div>
14+ {/each}
15+</div>
16+
ui/src/components/buttons/ToggleButton.sveltenew file+11
@@ -0,0 +1,11 @@
1+<script lang="ts">
2+ export let on: boolean = true;
3+ export let borderClass = "border-navy";
4+ export let onClass = "bg-navy";
5+ export let offClass = "bg-white";
6+</script>
7+
8+<button on:click
9+ class={`w-4 h-4 rounded-full border ${borderClass} ${on ? onClass : offClass}`}
10+/>
11+
ui/src/components/common/ChartLink.sveltenew file+11
@@ -0,0 +1,11 @@
1+<script lang="ts">
2+ import { link } from 'svelte-spa-router'
3+ import { linkToChart } from 'lib/link';
4+
5+ export let patp = null;
6+</script>
7+
8+<a href={linkToChart(patp)} use:link
9+ class="bg-navy text-white px-2 py-1 rounded no-underline">
10+ <slot/>
11+</a>
ui/src/components/common/EthAddressLink.sveltenew file+37
@@ -0,0 +1,37 @@
1+<script lang="ts">
2+ import CopyToClipboard from 'svelte-copy-to-clipboard';
3+ import tooltip from 'actions/tooltip';
4+
5+ let overrideText = null;
6+
7+ const handleSuccessfullyCopied = (e) => {
8+ overrideText = 'Copied!';
9+ setTimeout(() => {
10+ overrideText = null;
11+ }, 2000);
12+ }
13+
14+ const handleFailedCopy = () => {
15+ alert('failed to copy :(');
16+ }
17+
18+ export let address: string = '';
19+
20+ $: abbrevAddress = address ? address.slice(0, 6) + '...' + address.slice(-4) : '';
21+</script>
22+
23+{#if address}
24+ <span use:tooltip={address}>
25+ <a href={`https://etherscan.io/address/${address}`} target="_blank">
26+ {overrideText || abbrevAddress}
27+ </a>
28+
29+ <CopyToClipboard text={address} on:copy={handleSuccessfullyCopied} on:fail={handleFailedCopy} let:copy>
30+ <button on:click={copy} class="ml-1 text-3xl align-sub leading-4">⎘</button>
31+ </CopyToClipboard>
32+ </span>
33+{:else}
34+ <slot>
35+ Address empty
36+ </slot>
37+{/if}
ui/src/components/common/GoldBadge.sveltenew file+10
@@ -0,0 +1,10 @@
1+<script lang="ts">
2+ import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
3+ export let title: string = '';
4+ export let doc: string = '';
5+
6+</script>
7+
8+<TooltipAndDocLink text={title} {doc} class="inline-block py-0.5 px-2 rounded-md bg-gold">
9+ <slot></slot>
10+</TooltipAndDocLink>
ui/src/components/common/Legil.sveltenew file+47
@@ -0,0 +1,47 @@
1+<script lang="ts">
2+ import { clan } from 'urbit-ob';
3+ import { legil, stringRenderer } from '@johnhyde/legil-js';
4+ import { sigilScalingFunction } from 'lib/sigil';
5+ import moonPng from 'assets/moon.png';
6+ import cometSvg from 'assets/comet.svg';
7+
8+ export let patp: string;
9+ export let size: number;
10+ export let fgColor: string = 'white';
11+ export let bgColor: string = 'black';
12+ export let altBgColor: string = undefined;
13+ let svgString: any;
14+ let imgSrc: any;
15+
16+ $: shipClass = clan(patp);
17+ $: displaySigil = ['galaxy', 'star', 'planet'].includes(shipClass);
18+ $: {
19+ if (displaySigil) {
20+ let svgAST;
21+ svgAST = legil({
22+ patp: patp,
23+ size,
24+ colors: [bgColor, fgColor],
25+ autoScaleStrokes: true,
26+ strokeScalingFunctionV2: sigilScalingFunction,
27+ });
28+ svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
29+ svgString = stringRenderer(svgAST);
30+ if (altBgColor) {
31+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${altBgColor}"`);
32+ }
33+ } else if (shipClass === 'moon') {
34+ imgSrc = moonPng;
35+ } else {
36+ imgSrc = cometSvg;
37+ }
38+ }
39+</script>
40+
41+<div class="grow" style:max-width="{size}px">
42+ {#if displaySigil}
43+ {@html svgString}
44+ {:else}
45+ <img src={imgSrc} alt={shipClass} class="mx-auto" />
46+ {/if}
47+</div>
ui/src/components/common/LoadingSpinner.sveltenew file+8
@@ -0,0 +1,8 @@
1+<script lang="ts">
2+ export let spinnerClass = "text-white";
3+</script>
4+
5+<svg class="animate-spin h-5 w-5 mx-auto {spinnerClass}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
6+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
7+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
8+</svg>
ui/src/components/common/ShipLink.sveltenew file+10
@@ -0,0 +1,10 @@
1+<script lang="ts">
2+ import { link } from 'svelte-spa-router'
3+ import { cite, normalizeId } from 'lib/id';
4+ import { linkToShip } from 'lib/link';
5+ export let patp: string;
6+ $: normalizedId = normalizeId(patp);
7+</script>
8+
9+<a use:link={linkToShip(normalizedId)}>{cite(normalizedId)}</a>
10+
ui/src/components/common/Sigil.sveltenew file+65
@@ -0,0 +1,65 @@
1+<script lang="ts">
2+ import { clan } from 'urbit-ob';
3+ // import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
4+ import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
5+ import { sigilScalingFunction, crowdedSigilScalingFunction } from 'lib/sigil';
6+ import moonPng from 'assets/moon.png';
7+ import cometSvg from 'assets/comet.svg';
8+
9+ export let patp: string;
10+ export let size: number;
11+ export let useNew: boolean = false;
12+ export let fgColor: string = 'white';
13+ export let bgColor: string = 'black';
14+ export let altBgColor: string = undefined;
15+ export let noShrink: boolean = false;
16+ let svgString: any;
17+ let imgSrc: any;
18+
19+ $: noShrinkClasses = noShrink ? 'shrink-0' : '';
20+ $: noShrinkWidth = noShrink ? size+'px' : 'initial';
21+
22+ $: shipClass = clan(patp);
23+ $: crowded = ['comet', 'moon'].includes(shipClass);
24+ $: {
25+ // if (displaySigil) {
26+ // let sigil = sigil1;
27+ // let sr = sr1;
28+ // let svgAST;
29+ // if (useNew) {
30+ let sigil = sigil2;
31+ let sr = sr2;
32+ let svgAST = sigil({
33+ patp: patp,
34+ size,
35+ colors: [bgColor, fgColor],
36+ autoScaleStrokes: true,
37+ strokeScalingFunctionV2: crowded ? crowdedSigilScalingFunction : sigilScalingFunction,
38+ });
39+ // } else {
40+ // svgAST = sigil({
41+ // patp: patp,
42+ // size,
43+ // colors: [bgColor, fgColor],
44+ // });
45+ // }
46+ svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
47+ svgString = sr(svgAST);
48+ if (altBgColor) {
49+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${altBgColor}"`);
50+ }
51+ // } else if (shipClass === 'moon') {
52+ // imgSrc = moonPng;
53+ // } else {
54+ // imgSrc = cometSvg;
55+ // }
56+ }
57+</script>
58+
59+<div class="grow {noShrinkClasses}" style:max-width="{size}px" style:width={noShrinkWidth}>
60+ <!-- {#if displaySigil} -->
61+ {@html svgString}
62+ <!-- {:else}
63+ <img src={imgSrc} alt={shipClass} class="mx-auto" />
64+ {/if} -->
65+</div>
ui/src/components/common/Tooltip.sveltenew file+37
@@ -0,0 +1,37 @@
1+<script lang="ts">
2+ import tooltip from 'actions/tooltip';
3+ export let text: string = '';
4+ let className: string = '';
5+ export { className as class };
6+ export let enabled: boolean = true;
7+ let tooltipActive: boolean = false;
8+
9+ let timer;
10+
11+ const debounce = v => {
12+ clearTimeout(timer);
13+ timer = setTimeout(() => {
14+ tooltipActive = v;
15+ }, 50);
16+ }
17+
18+ function onTrigger() {
19+ debounce(true);
20+ }
21+
22+ function onUntrigger() {
23+ debounce(false);
24+ }
25+
26+</script>
27+
28+{#if enabled}
29+ <div use:tooltip={text} on:tooltipTriggered={onTrigger} on:tooltipUntriggered={onUntrigger} class={className}>
30+ <slot {tooltipActive} />
31+ </div>
32+{:else}
33+ <div class={className}>
34+ <slot tooltipActive={false} />
35+ </div>
36+{/if}
37+
ui/src/components/common/TooltipAndDocLink.sveltenew file+14
@@ -0,0 +1,14 @@
1+<script lang="ts">
2+ import Tooltip from "./Tooltip.svelte";
3+ import DocLink from '@/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something
4+ export let text: string = '';
5+ export let doc: string = '';
6+ let className: string = '';
7+ export { className as class };
8+</script>
9+
10+<Tooltip text={text} enabled={text !== ''} let:tooltipActive={tooltipActive} class={className}>
11+ <DocLink modal path={doc} enabled={doc !== '' && tooltipActive}>
12+ <slot></slot>
13+ </DocLink>
14+</Tooltip>
ui/src/components/docs/Doc.sveltenew file+50
@@ -0,0 +1,50 @@
1+<script lang="ts">
2+ import { createEventDispatcher, onDestroy } from 'svelte';
3+ import DocContent from './DocContent.svelte';
4+ import DocHome from './DocHome.svelte';
5+ import Hamburger from '@/buttons/Hamburger.svelte';
6+ import CloseButton from '@/buttons/CloseButton.svelte';
7+ import keys from 'stores/keys';
8+
9+ export let path: string;
10+
11+ $: homePage = path === '';
12+
13+ const dispatch = createEventDispatcher();
14+ function close() {
15+ dispatch('close');
16+ }
17+
18+ function navigate(path) {
19+ dispatch('navigate', path);
20+ }
21+
22+ $: {
23+ $keys.globalkeyup.get('escape').set('doc', close);
24+ }
25+
26+ onDestroy(() => {
27+ $keys.globalkeyup.get('escape').delete('doc');
28+ });
29+</script>
30+
31+<div class="rounded-lg bg-white">
32+ <div class="flex p-4 border-navy">
33+ {#if !homePage}
34+ <Hamburger on:click={() => navigate('')} />
35+ {:else}
36+ <div class="w-7 h-7" />
37+ {/if}
38+ <div class="grow text-center text-xl">
39+ Help
40+ </div>
41+ <CloseButton on:click={close} />
42+ </div>
43+ <div class="p-8 pt-0">
44+ {#if !homePage}
45+ <DocContent {path} on:navigate />
46+ {:else}
47+ <DocHome on:navigate />
48+ {/if}
49+ </div>
50+</div>
ui/src/components/docs/DocContent.sveltenew file+37
@@ -0,0 +1,37 @@
1+<script lang="ts">
2+
3+ import { createEventDispatcher } from 'svelte';
4+
5+ import docLinks from "actions/docLinks";
6+ import docs from "stores/docs";
7+
8+ import LoadingSpinner from "@/common/LoadingSpinner.svelte";
9+
10+ export let path:string;
11+
12+ const dispatch = createEventDispatcher();
13+ function navigate(path) {
14+ dispatch('navigate', path);
15+ }
16+
17+ function convertLinks(html: string): string {
18+ let converted = html;
19+ const externalLinkRegex = /href="http/g;
20+ converted = converted.replace(externalLinkRegex, 'target="_blank" href="http');
21+ return converted;
22+ }
23+</script>
24+
25+<div class="doc">
26+ {#await docs.getDoc(path)}
27+ <LoadingSpinner spinnerClass="text-black" />
28+ {:then docHtml}
29+ <div use:docLinks={navigate}>
30+ {@html convertLinks(docHtml)}
31+ </div>
32+ {:catch error}
33+ <h1>Oops!</h1>
34+ <h2>Couldn't load the doc at /{path}</h2>
35+ <p>Please DM ~midlev-mindyr so he can fix this.</p>
36+ {/await}
37+</div>
ui/src/components/docs/DocHome.sveltenew file+76
@@ -0,0 +1,76 @@
1+<script lang="ts">
2+ import DocLink from "./DocLink.svelte";
3+
4+ function convertLinks(html: string): string {
5+ let converted = html;
6+ const externalLinkRegex = /href="http/g;
7+ converted = converted.replace(externalLinkRegex, 'target="_blank" href="http');
8+ return converted;
9+ }
10+</script>
11+
12+<div class="doc w-auto mx-auto">
13+ <h1 class="text-center">Table of Contents</h1>
14+ <DocLink path="urbit-id" on:navigate>
15+ <h2>What is an Urbit ID?</h2>
16+ </DocLink>
17+ <!-- <h2>Ship Classes</h2> -->
18+ <ul>
19+ <li>
20+ <DocLink path="galaxy" on:navigate>
21+ <h3>Galaxies</h3>
22+ </DocLink>
23+ </li>
24+ <li>
25+ <DocLink path="star" on:navigate>
26+ <h3>Stars</h3>
27+ </DocLink>
28+ </li>
29+ <li>
30+ <DocLink path="planet" on:navigate>
31+ <h3>Planets</h3>
32+ </DocLink>
33+ </li>
34+ <li>
35+ <DocLink path="moon" on:navigate>
36+ <h3>Moons</h3>
37+ </DocLink>
38+ </li>
39+ <li>
40+ <DocLink path="comet" on:navigate>
41+ <h3>Comets</h3>
42+ </DocLink>
43+ </li>
44+ </ul>
45+ <DocLink path="patpee" on:navigate>
46+ <h2>What is a @p?</h2>
47+ </DocLink>
48+ <ul>
49+ <li>
50+ <DocLink path="phonemes" on:navigate>
51+ <h3>Phonemes - Prefixes & Suffixes</h3>
52+ </DocLink>
53+ </li>
54+ </ul>
55+ <DocLink path="sigil" on:navigate>
56+ <h2>What is a Sigil?</h2>
57+ </DocLink>
58+ <DocLink path="azimuth" on:navigate>
59+ <h2>What is Azimuth?</h2>
60+ </DocLink>
61+ <ul>
62+ <li>
63+ <DocLink path="l2" on:navigate>
64+ <h3>Layer 1 and Layer 2</h3>
65+ </DocLink>
66+ </li>
67+ <li>
68+ <DocLink path="keys" on:navigate>
69+ <h3>Networking Keys</h3>
70+ </DocLink>
71+ </li>
72+ </ul>
73+ <DocLink path="astrolabe" on:navigate>
74+ <h2>About Astrolabe</h2>
75+ </DocLink>
76+</div>
ui/src/components/docs/DocLink.sveltenew file+26
@@ -0,0 +1,26 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import docs from 'stores/docs';
4+
5+ export let path:string;
6+ export let modal:boolean = false;
7+ export let asLink:boolean = true;
8+ export let enabled:boolean = true;
9+
10+ const dispatch = createEventDispatcher();
11+ function navigate() {
12+ if (modal) {
13+ docs.show(path);
14+ } else {
15+ dispatch('navigate', path);
16+ }
17+ }
18+</script>
19+
20+{#if enabled}
21+ <a class:no-underline={!asLink} href="javascript:void(0)" on:click={navigate}>
22+ <slot />
23+ </a>
24+{:else}
25+ <slot />
26+{/if}
ui/src/components/docs/HelpModal.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import docs from 'stores/docs';
3+ import Doc from './Doc.svelte';
4+
5+ function close() {
6+ docs.hide()
7+ }
8+
9+ function navigate({ detail: path }) {
10+ docs.show(path);
11+ }
12+</script>
13+
14+{#if $docs.show}
15+ <div class="absolute w-full h-full bg-navy opacity-50 cursor-pointer z-50" on:click/>
16+ <div class="absolute w-full h-full z-50 overflow-y-auto" on:click={close}>
17+ <div class="mx-4 2xs:mx-8 md:max-w-xl md:mx-auto">
18+ <div class="py-4 2xs:py-8">
19+ <div on:click|stopPropagation>
20+ <Doc path={$docs.path} on:close={close} on:navigate={navigate} />
21+ </div>
22+ </div>
23+ </div>
24+ </div>
25+{/if}
ui/src/components/extensions/AppIcon.sveltenew file+61
@@ -0,0 +1,61 @@
1+<script lang="ts">
2+ import CopyToClipboard from 'svelte-copy-to-clipboard';
3+ import { uxToHex } from 'lib/utils';
4+ import { getAppByPath } from 'lib/apps';
5+ import store from 'stores/store';
6+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
7+
8+ export let path: string;
9+
10+ $: desk = path.split('/')[1];
11+ $: appPromise = getAppByPath(path);
12+ $: appUrbitGraphLink = `web+urbitgraph://${path}`;
13+ $: appLink = `/apps/grid/perma?ext=${appUrbitGraphLink}`;
14+
15+ let overrideText = null;
16+
17+ const handleSuccessfullyCopied = (e) => {
18+ overrideText = 'Copied!';
19+ setTimeout(() => {
20+ overrideText = null;
21+ }, 1000);
22+ }
23+
24+ const handleFailedCopy = () => {
25+ alert('failed to copy :(');
26+ }
27+</script>
28+
29+<div class="w-20 text-sm text-center">
30+ <a class="block w-20 h-20 border border-gray-300 bg-gray-600 text-white rounded-lg overflow-hidden"
31+ href={appLink} target="_blank">
32+ {#await appPromise}
33+ <div class="flex h-full items-center">
34+ <LoadingSpinner />
35+ </div>
36+ {:then app}
37+ <div style:background-color={uxToHex(app.color)} class="h-full w-full text-sm">
38+ {#if app.image}
39+ <img src={app.image} alt="app icon"
40+ class="aspect-square object-cover w-full h-full"
41+ />
42+ {/if}
43+ </div>
44+ {:catch}
45+ {path}
46+ {/await}
47+ </a>
48+ <CopyToClipboard text={path} on:copy={handleSuccessfullyCopied} on:fail={handleFailedCopy} let:copy>
49+ <span on:click={copy}>
50+ {#if overrideText}
51+ {overrideText}
52+ {:else}
53+ {#await appPromise}
54+ {desk}
55+ {:then app}
56+ {app.title}
57+ {/await}
58+ {/if}
59+ </span>
60+ </CopyToClipboard>
61+</div>
ui/src/components/extensions/AppsShipViewExtension.sveltenew file+56
@@ -0,0 +1,56 @@
1+<script lang="ts">
2+
3+ import { sein } from 'urbit-ob';
4+ import uniq from 'lodash/uniq';
5+ import tooltip from 'actions/tooltip';
6+ import { normalizeId, filterIdsForChildMoons } from 'lib/id';
7+ import { filterObject } from 'lib/utils';
8+ import { addAllies, delAllies } from 'lib/apps';
9+ import store from 'stores/store';
10+ import ExtensionWindow from '@/ExtensionWindow.svelte';
11+ import AppIcon from './AppIcon.svelte';
12+
13+ export let patp: string;
14+ $: moons = filterIdsForChildMoons($store.peers, patp);
15+ $: possibleAllies = [patp, ...moons];
16+ $: knownApps = uniq(Object.values(filterObject($store.apps.allies, (ally) => {
17+ ally = normalizeId(ally);
18+ return patp === ally || sein(ally) === patp;
19+ })).flat());
20+ $: nonAllies = possibleAllies.filter((ally) => !$store.apps.allies[ally]);
21+ $: emptyAllies = possibleAllies.filter((ally) => {
22+ let apps = $store.apps.allies[ally];
23+ return apps && !apps.length;
24+ });
25+</script>
26+<ExtensionWindow name="Apps" halfWidth>
27+ <svelte:fragment slot="top-bar">
28+ <div class="grow m-2 flex justify-end align-center">
29+ {#if nonAllies.length > 0}
30+ <button class="bg-gray-200 ml-3 px-2 rounded"
31+ on:click={() => addAllies(nonAllies)}
32+ >
33+ Check for More
34+ </button>
35+ {/if}
36+ {#if emptyAllies.length > 0}
37+ <button class="bg-gray-200 ml-3 px-2 rounded"
38+ on:click={() => delAllies(emptyAllies)}
39+ >
40+ Stop Watching
41+ <span use:tooltip={emptyAllies.join(', ')}>
42+ {emptyAllies.length} Ship{emptyAllies.length > 1 ? 's': ''}
43+ </span>
44+ </button>
45+ {/if}
46+ </div>
47+ </svelte:fragment>
48+ {#if knownApps.length > 0}
49+ <div class="flex flex-wrap p-2 gap-2 justify-center">
50+ {#each knownApps as app}
51+ <AppIcon path={app} />
52+ {/each}
53+ </div>
54+ {/if}
55+</ExtensionWindow>
56+
ui/src/components/extensions/GroupsShipViewExtension.sveltenew file+15
@@ -0,0 +1,15 @@
1+<script lang="ts">
2+ import { uxToHex } from 'lib/utils';
3+ import { normalizeId } from 'lib/id';
4+ import store from 'stores/store';
5+ import ExtensionWindow from '@/ExtensionWindow.svelte';
6+
7+ export let patp: string;
8+</script>
9+
10+{#if patp}
11+ <ExtensionWindow name="Shared Groups">
12+ TODO: implement for Groups 2, don't bother with Groups 1
13+ </ExtensionWindow>
14+{/if}
15+
ui/src/components/extensions/ProfileShipViewExtension.sveltenew file+52
@@ -0,0 +1,52 @@
1+<script lang="ts">
2+ import SvelteMarkdown from 'svelte-markdown';
3+ import { uxToHex } from 'lib/utils';
4+ import { normalizeId } from 'lib/id';
5+ import store from 'stores/store';
6+ import Sigil from "@/common/Sigil.svelte";
7+ import ExtensionWindow from '@/ExtensionWindow.svelte';
8+
9+ export let patp: string;
10+ $: contact = $store.contacts[normalizeId(patp)];
11+ $: color = contact && uxToHex(contact.color);
12+ $: somethingToShow = contact && (contact.nickname || contact.cover || contact.avatar || contact.bio);
13+</script>
14+
15+{#if somethingToShow}
16+ <ExtensionWindow name="Groups Profile">
17+ <svelte:fragment slot="top-bar">
18+ {#if contact.nickname}
19+ <div class="grow m-1 flex justify-center align-center">
20+ {contact.nickname}
21+ </div>
22+ {/if}
23+ </svelte:fragment>
24+ <div class="flex flex-wrap py-2">
25+ {#if contact.cover}
26+ <img src={contact.cover} alt="profile cover image"
27+ class="m-1 -mb-16 w-full h-48 border border-black rounded-lg object-cover min-w-0"
28+ />
29+ {/if}
30+ <div class="flex flex-wrap md:flex-nowrap justify-center">
31+ <div class="mx-8 max-w-[128px] min-w-[128px] h-fit grow overflow-hidden rounded-lg">
32+ {#if contact.avatar}
33+ <img src={contact.avatar} alt="custom avatar"
34+ class="aspect-square object-cover max-w-[128px] min-w-0"
35+ />
36+ {:else}
37+ <Sigil patp={patp} size={128} bgColor={color} useNew />
38+ {/if}
39+ </div>
40+ {#if contact.bio}
41+ <div class="text-sm p-3 bg-white rounded-tl-lg rounded-br-lg whitespace-pre-line">
42+ <SvelteMarkdown source={contact.bio} />
43+ </div>
44+ {/if}
45+ </div>
46+ <!-- <p class="whitespace-pre-line">
47+ {contact.bio}
48+ </p> -->
49+ </div>
50+ </ExtensionWindow>
51+{/if}
52+
ui/src/components/pals/PalStatusIndicator.sveltenew file+191
@@ -0,0 +1,191 @@
1+<script lang="ts">
2+ import type { PalStatus } from 'lib/pals';
3+ import tooltip from 'actions/tooltip';
4+ export let status: PalStatus = 'unconnected';
5+ export let acked: boolean = true;
6+ let className: string = '';
7+ export { className as class };
8+
9+ let statusDescription = '', viewBox = '', width = '25';
10+ $: {
11+ switch (status) {
12+ case 'mutual':
13+ statusDescription = 'you are pals'
14+ viewBox = '12 12 100 100';
15+ width = '25';
16+ break;
17+ case 'outgoing':
18+ statusDescription = 'you added them in pals'
19+ viewBox = '0 12 68 100';
20+ width = '17';
21+ if (!acked) {
22+ statusDescription += ' (pending)'
23+ }
24+ break;
25+ case 'incoming':
26+ statusDescription = 'they added you in pals'
27+ viewBox = '24 12 76 100';
28+ width = '19';
29+ break;
30+ }
31+ }
32+</script>
33+{#if status !== 'unconnected'}
34+ <svg width={width} height="25" {viewBox} fill="none" xmlns="http://www.w3.org/2000/svg"
35+ use:tooltip={statusDescription}
36+ class={className}
37+ >
38+
39+ {#if status == 'mutual'}
40+ <path d="M108 50
41+ C108 61.9348 103.259 73.3807 94.8198 81.8198
42+ C86.3807 90.2589 74.9347 95 63 95
43+ C51.0653 95 39.6193 90.2589 31.1802 81.8198
44+ C22.7411 73.3807 18 61.9347 18 50" stroke="black" stroke-width="10"></path><path d="M87.5114 31.5994
45+ C87.5114 28.4354 84.9226 25.8466 81.7585 25.8466
46+ C78.5945 25.8466 76.0057 28.4354 76.0057 31.5994
47+ C76.0057 34.7635 78.5945 37.3523 81.7585 37.3523
48+ C84.9226 37.3523 87.5114 34.7635 87.5114 31.5994
49+ Z
50+ M51.0767 31.5994
51+ C51.0767 28.4354 48.4879 25.8466 45.3239 25.8466
52+ C42.1598 25.8466 39.571 28.4354 39.571 31.5994
53+ C39.571 34.7635 42.1598 37.3523 45.3239 37.3523
54+ C48.4879 37.3523 51.0767 34.7635 51.0767 31.5994
55+ Z" fill="black"></path>
56+ {:else if status == 'outgoing' }
57+ {#if acked}
58+ <path d="M50 95
59+ C44.0905 95 38.2389 93.836 32.7792 91.5746
60+ C27.3196 89.3131 22.3588 85.9984 18.1802 81.8198
61+ C14.0016 77.6412 10.6869 72.6804 8.42542 67.2208
62+ C6.16396 61.7611 5 55.9095 5 50" stroke="black" stroke-width="10"></path><path d="M30.5546 65.9099
63+ L28.7868 67.6777
64+ L32.3223 71.2132
65+ L34.0901 69.4454
66+ L30.5546 65.9099
67+ Z
68+ M67.6777 32.3223
69+ L39.7938 39.7938
70+ L60.2062 60.2062
71+ L67.6777 32.3223
72+ Z
73+ M34.0901 69.4454
74+ L53.5355 50
75+ L50 46.4645
76+ L30.5546 65.9099
77+ L34.0901 69.4454
78+ Z" fill="black"></path>
79+ {:else}
80+ <path d="M50 95
81+ C44.0905 95 38.2389 93.836 32.7792 91.5746
82+ C27.3196 89.3131 22.3588 85.9984 18.1802 81.8198
83+ C14.0016 77.6412 10.6869 72.6804 8.42542 67.2208
84+ C6.16396 61.7611 5 55.9095 5 50" stroke="black" stroke-width="10"></path><path d="M31.4384 66.7938
85+ L30.5546 67.6777
86+ L32.3223 69.4454
87+ L33.2062 68.5616
88+ L31.4384 66.7938
89+ Z
90+ M67.6777 32.3223
91+ L53.7357 36.0581
92+ L63.9419 46.2643
93+ L67.6777 32.3223
94+ Z
95+ M33.2062 68.5616
96+ L34.974 66.7938
97+ L33.2062 65.026
98+ L31.4384 66.7938
99+ L33.2062 68.5616
100+ Z
101+ M38.5095 63.2583
102+ L42.045 59.7227
103+ L40.2773 57.955
104+ L36.7417 61.4905
105+ L38.5095 63.2583
106+ Z
107+ M45.5806 56.1872
108+ L49.1161 52.6517
109+ L47.3483 50.8839
110+ L43.8128 54.4194
111+ L45.5806 56.1872
112+ Z
113+ M52.6516 49.1161
114+ L56.1872 45.5806
115+ L54.4194 43.8128
116+ L50.8839 47.3484
117+ L52.6516 49.1161
118+ Z
119+ M59.7227 42.0451
120+ L63.2583 38.5095
121+ L61.4905 36.7418
122+ L57.9549 40.2773
123+ L59.7227 42.0451
124+ Z
125+ M30.5546 65.9099
126+ L28.7868 67.6777
127+ L32.3223 71.2132
128+ L34.0901 69.4454
129+ L30.5546 65.9099
130+ Z
131+ M67.6777 32.3223
132+ L39.7938 39.7938
133+ L60.2062 60.2062
134+ L67.6777 32.3223
135+ Z
136+ M34.0901 69.4454
137+ L35.8579 67.6777
138+ L32.3223 64.1421
139+ L30.5546 65.9099
140+ L34.0901 69.4454
141+ Z
142+ M39.3934 64.1421
143+ L42.9289 60.6066
144+ L39.3934 57.0711
145+ L35.8579 60.6066
146+ L39.3934 64.1421
147+ Z
148+ M46.4645 57.0711
149+ L50 53.5355
150+ L46.4645 50
151+ L42.9289 53.5355
152+ L46.4645 57.0711
153+ Z
154+ M53.5355 50
155+ L57.0711 46.4645
156+ L53.5355 42.9289
157+ L50 46.4645
158+ L53.5355 50
159+ Z
160+ M60.6066 42.9289
161+ L64.1421 39.3934
162+ L60.6066 35.8579
163+ L57.0711 39.3934
164+ L60.6066 42.9289
165+ Z" fill="black"></path>
166+ {/if}
167+ {:else if status == 'incoming' }
168+ <path d="M95 50
169+ C95 55.9095 93.836 61.7611 91.5746 67.2208
170+ C89.3131 72.6804 85.9984 77.6412 81.8198 81.8198
171+ C77.6412 85.9984 72.6804 89.3131 67.2208 91.5746
172+ C61.7611 93.836 55.9095 95 50 95" stroke="black" stroke-width="10"></path><path d="M67.2383 68.7383
173+ L59.7669 40.8545
174+ L39.3545 61.2669
175+ L67.2383 68.7383
176+ Z
177+ M31.5294 29.4939
178+ L29.7617 27.7262
179+ L26.2261 31.2617
180+ L27.9939 33.0295
181+ L31.5294 29.4939
182+ Z
183+ M53.0962 51.0607
184+ L31.5294 29.4939
185+ L27.9939 33.0295
186+ L49.5607 54.5962
187+ L53.0962 51.0607
188+ Z" fill="black"></path>
189+ {/if}
190+ </svg>
191+{/if}
ui/src/components/pals/PalTagger.sveltenew file+40
@@ -0,0 +1,40 @@
1+<script lang="ts">
2+ import { getPal, tagPal, untagPal } from 'lib/pals';
3+ import store from 'stores/store';
4+
5+ export let patp: string;
6+
7+ $: pal = getPal($store.pals, patp);
8+
9+ let newTagText = '';
10+ $: newTags = newTagText.split(',').map(t => t.trim());
11+ $: sanitizedNewTags = newTags.map(t => t.toLocaleLowerCase().replaceAll(/[^-_.~a-z0-9]+/g, '_'));
12+ $: newTagsAreValid = newTags.every(tagIsValid);
13+ $: validationClasses = newTagsAreValid ? '' : 'border-red-500 bg-red-100'
14+
15+ function tagIsValid(tag: string): boolean {
16+ return /^[-_.~a-z0-9]*$/.test(tag);
17+ }
18+
19+ async function onTagSubmit({ key }) {
20+ if (newTagText && key == 'Enter') {
21+ await tagPal(patp, sanitizedNewTags);
22+ newTagText = '';
23+ }
24+ }
25+</script>
26+
27+<div class="flex flex-wrap gap-1 text-sm justify-center">
28+ {#each pal.tags as tag}
29+ <div class="flex bg-gray-200 rounded divide-gray-500 divide-x items-center">
30+ <span class="px-1">
31+ {tag}
32+ </span>
33+ <button class="px-1 text-gray-500" on:click={() => untagPal(patp, tag)}>
34+ X
35+ </button>
36+ </div>
37+ {/each}
38+</div>
39+<input type="text" class="px-1 text-sm border border-gray-400 rounded {validationClasses}"
40+ on:keydown={onTagSubmit} bind:value={newTagText} placeholder="add_tags, like_this" />
ui/src/components/pals/PalsShipViewExtension.sveltenew file+34
@@ -0,0 +1,34 @@
1+<script lang="ts">
2+ import { getPal, addPal, removePal } from 'lib/pals';
3+ import store from 'stores/store';
4+ import ExtensionWindow from '@/ExtensionWindow.svelte';
5+ import PalStatusIndicator from './PalStatusIndicator.svelte';
6+ import PalTagger from './PalTagger.svelte';
7+
8+ export let patp: string;
9+
10+ $: pal = getPal($store.pals, patp);
11+ $: palAdded = pal.status == 'outgoing' || pal.status == 'mutual';
12+ $: [buttonText, buttonFunc] = palAdded ? ['Remove Pal', removePal] : ['Add Pal', addPal];
13+ $: itsUs = patp.substring(1) === window.ship;
14+</script>
15+
16+{#if $store.palsInstalled && !itsUs}
17+ <ExtensionWindow name="%pals" halfWidth>
18+ <svelte:fragment slot="top-bar">
19+ <div class="grow ml-1 my-2 flex justify-center align-center">
20+ <PalStatusIndicator status={pal.status} acked={pal.ack} />
21+ <button class="bg-gray-200 ml-3 px-2 rounded" on:click={() => buttonFunc(patp)}>
22+ {buttonText}
23+ </button>
24+ </div>
25+ </svelte:fragment>
26+
27+ {#if palAdded}
28+ <div class="flex flex-wrap justify-center gap-3 my-3 conteents">
29+ <PalTagger {patp}></PalTagger>
30+ </div>
31+ {/if}
32+ </ExtensionWindow>
33+{/if}
34+
ui/src/components/search/SearchBar.sveltenew file+152
@@ -0,0 +1,152 @@
1+<script lang="ts">
2+ import SigilInput from './sigil/SigilInput.svelte';
3+ import Symbol from './sigil/Symbol.svelte';
4+
5+ import docs from 'stores/docs';
6+ import keys from 'stores/keys';
7+ import { SearchAnalysis } from 'lib/id';
8+ import { SigilQuery } from 'lib/sigil';
9+
10+ export let patp: string = undefined;
11+ export let analysis: SearchAnalysis = new SearchAnalysis();
12+ export let search = '';
13+ export let sigilQuery: SigilQuery = new SigilQuery('planet');
14+ sigilQuery.urlString = search;
15+ export let searchMode: ('patp' | 'sigil') = 'patp';
16+ export let allowSigilSearch: boolean = true;
17+ let defaultPlaceholderText = "Search by @p or #. '*' matches any character";
18+ export let placeholderText = defaultPlaceholderText;
19+
20+ $: {
21+ placeholderText = placeholderText ? placeholderText : defaultPlaceholderText;
22+ }
23+ $: {
24+ searchMode = allowSigilSearch ? searchMode : 'patp';
25+ }
26+ $: showSigilInput = searchMode == 'sigil';
27+
28+ let bouncySearch: string = search;
29+ let debouncedSearch: string = bouncySearch;
30+
31+ let timer;
32+ $: {
33+ if (!showSigilInput){
34+ if (bouncySearch !== search) {
35+ clearTimeout(timer);
36+ timer = setTimeout(() => {
37+ search = bouncySearch;
38+ }, 300);
39+ }
40+ }
41+ }
42+
43+ function updateSigilUrlString() {
44+ if (showSigilInput) {
45+ if (search !== sigilQuery.urlString) {
46+ sigilQuery.urlString = search;
47+ }
48+ }
49+ }
50+
51+ function updateSigilSearch() {
52+ if (showSigilInput) {
53+ if (sigilQuery.isNotEmpty) {
54+ search = sigilQuery.urlString;
55+ } else {
56+ search = '';
57+ }
58+ }
59+ }
60+
61+ $: search, updateSigilUrlString();
62+ $: sigilQuery.urlString, updateSigilSearch();
63+
64+ $: {
65+ if (showSigilInput) {
66+ if (sigilQuery.isDefinitive) {
67+ patp = sigilQuery.patp;
68+ } else {
69+ patp = undefined;
70+ }
71+ } else {
72+ analysis = new SearchAnalysis(search);
73+ if (analysis.patpIsValid) {
74+ patp = analysis.patp;
75+ } else {
76+ patp = undefined;
77+ }
78+ bouncySearch = search = analysis.search;
79+ }
80+ }
81+
82+ let searchInput;
83+ let searchFocused = false;
84+ function onFocus() { searchFocused = true; }
85+ function onBlur() { searchFocused = false; }
86+ function focus() { if (searchInput) searchInput.focus() }
87+ function blur() { if (searchInput) searchInput.blur() }
88+ $:{
89+ $keys.keyup.get('/').set('searchBar', focus);
90+ // generic binding in App.svelte takes care of blur
91+ $keys.keyup.get('p').set('searchBar', closeSigilInput);
92+ $keys.keyup.get('s').set('searchBar', openSigilInput);
93+ }
94+
95+ function openSigilInput() {
96+ if (allowSigilSearch)
97+ searchMode = 'sigil';
98+ }
99+
100+ function closeSigilInput() {
101+ searchMode = 'patp';
102+ bouncySearch = patp || analysis.search;
103+ debouncedSearch = bouncySearch;
104+ search = debouncedSearch;
105+ }
106+
107+ function showSigilDoc() {
108+ docs.show('sigil-input');
109+ }
110+
111+ const buttonClasses = 'rounded-full bg-black text-white text-sm transition-opacity opacity-50 hover:opacity-80';
112+</script>
113+
114+<div class="relative max-w-md w-full">
115+ {#if showSigilInput}
116+ <div class="rounded-2xl bg-white overflow-hidden">
117+ <SigilInput bind:sigilQuery></SigilInput>
118+ </div>
119+ <button
120+ on:click={showSigilDoc}
121+ class="absolute top-2 left-2 w-6 h-6 {buttonClasses}"
122+ >
123+ ?
124+ </button>
125+ <button
126+ on:click={closeSigilInput}
127+ class="absolute top-2 right-2 w-20 h-6 {buttonClasses}"
128+ >
129+ patp or #
130+ </button>
131+ {:else}
132+ <input type="text" class="p-4 py-2 w-full rounded-full"
133+ spellcheck="false"
134+ placeholder={placeholderText}
135+ on:input={(e) => bouncySearch = e.target.value}
136+ on:focus={onFocus} on:blur={onBlur}
137+ bind:this={searchInput}
138+ value={search}
139+ autofocus
140+ />
141+ {#if allowSigilSearch}
142+ <button
143+ on:click={openSigilInput}
144+ class="absolute top-1 right-1 w-8 h-8 {buttonClasses}"
145+ >
146+ <Symbol components={'gci.lvf.lhf.lbhb.atrf4.al3.cbl2lf.cbl2mb'.split('.')} size={32}
147+ altBgColor="transparent"
148+ />
149+ </button>
150+ {/if}
151+ {/if}
152+</div>
ui/src/components/SearchResultNavButtons.svelte → ui/src/components/search/SearchResultNavButtons.svelterenamed

The content is the same.

ui/src/components/search/SearchResults.sveltenew file+147
@@ -0,0 +1,147 @@
1+<script lang="ts">
2+
3+ import type { Patp, Rolodex } from '@urbit/api';
4+ import { searchPoints } from 'lib/api';
5+ import type { SigilQuery } from 'lib/sigil';
6+ import { searchedContacts } from 'stores/searchStores';
7+ import ShipListings from '@/ShipListings.svelte';
8+ import SearchSettings from './SearchSettings.svelte';
9+ import SearchResultsNavButtons from './SearchResultsNavButtons.svelte';
10+
11+ export let regexQuery: RegExp;
12+ export let sigilQuery: SigilQuery;
13+ export let patpSearch: string;
14+ export let searchMode: ('patp' | 'sigil') = 'patp';
15+
16+ let combinedSearchResults = [];
17+ let searchedPointsP: Promise<any>;
18+ let searchedPoints: Patp[] = [];
19+ let selectedShipIndex: number;
20+ let page: number = 0;
21+ let pageSize: number = 50;
22+ let totalPages, firstItemOnPage;
23+ type status = ('init' | 'prog' | 'done' | 'fail');
24+ let searchStatus: status = 'init';
25+ let notification: string = null;
26+
27+ let timer;
28+ $: {
29+ clearTimeout(timer);
30+ timer = setTimeout(() => {
31+ if (searchMode == 'patp') {
32+ searchedContacts.searchPatp(regexQuery);
33+ } else if (searchMode == 'sigil') {
34+ searchedContacts.searchSigil(sigilQuery.querySyls);
35+ }
36+ }, 1000);
37+ }
38+
39+ function doSearch() {
40+ const searchSnap = search;
41+ searchStatus = 'prog';
42+ console.log(`starting search for "${searchSnap}"`);
43+ searchedPointsP = searchPoints(searchSnap, searchMode);
44+ searchedPointsP.then((points) => {
45+ if (search === searchSnap) {
46+ searchedPoints = points;
47+ searchStatus = 'done';
48+ console.log(`done searching for "${searchSnap}"`);
49+ } else {
50+ console.log(`throwing out results for outdated search "${searchSnap}" (currently on ${search})`);
51+ }
52+ }).catch((err) => {
53+ if (search === searchSnap) {
54+ searchedPoints = [];
55+ searchStatus = 'fail';
56+ }
57+ });
58+ }
59+ $: search = (searchMode === 'sigil') ? sigilQuery.string : patpSearch;
60+ $: [search], doSearch();
61+
62+ $: {
63+ const contactSearchResults: Rolodex = JSON.parse(JSON.stringify($searchedContacts));
64+ const contacts = [];
65+ const nonContacts = [];
66+ searchedPoints.forEach(patp => {
67+ const contact = contactSearchResults[patp];
68+ if (contact) {
69+ contacts.push({ ...contact, patp });
70+ delete contactSearchResults[patp];
71+ } else {
72+ nonContacts.push({ patp });
73+ }
74+ });
75+ const nonAzimuthContacts = Object.entries(contactSearchResults).map(([patp, ship]) => {
76+ return {
77+ ...ship,
78+ patp,
79+ };
80+ });
81+ combinedSearchResults = [...contacts, ...nonAzimuthContacts, ...nonContacts];
82+ if (combinedSearchResults.length === 1) {
83+ selectedShipIndex = 0;
84+ } else {
85+ selectedShipIndex = null;
86+ }
87+ }
88+ $: {
89+ totalPages = Math.ceil(combinedSearchResults.length / pageSize);
90+ firstItemOnPage = page * pageSize;
91+ if (combinedSearchResults.length > 0) {
92+ if (firstItemOnPage >= combinedSearchResults.length) {
93+ page = totalPages - 1;
94+ } else if (firstItemOnPage < 0) {
95+ page = 0;
96+ }
97+ }
98+ }
99+ $: pageResults = combinedSearchResults.slice(firstItemOnPage, (page + 1) * pageSize);
100+ $: itemRangeText = pageResults.length == combinedSearchResults.length ? 'all' :
101+ `${firstItemOnPage + 1}-${firstItemOnPage + pageResults.length}`;
102+
103+ $: {
104+ if (searchStatus == 'prog') {
105+ notification = 'Searching all spawned points...';
106+ } else if (searchStatus == 'fail') {
107+ notification = 'Your search is too broad, please be more specific.';
108+ } else if (searchStatus == 'done' && combinedSearchResults.length === 0) {
109+ notification = 'No spawned ships match your search.'
110+ } else {
111+ notification = null;
112+ }
113+ }
114+</script>
115+
116+<SearchSettings/>
117+{#if notification}
118+<div class="rounded-lg bg-white overflow-hidden">
119+ <p class="py-4 px-6 text-center">
120+ {notification}
121+ </p>
122+</div>
123+{/if}
124+{#if combinedSearchResults.length > 0}
125+ <div class="max-w-md w-full rounded-lg bg-white overflow-hidden">
126+ <SearchResultsNavButtons {page} {pageSize} {totalPages}
127+ on:updatePage={({ detail }) => page = detail.page}
128+ >
129+ <p class="px-4 py-8 text-center">
130+ Showing {itemRangeText} of {combinedSearchResults.length} results
131+ </p>
132+ </SearchResultsNavButtons>
133+ <ShipListings
134+ ships={pageResults}
135+ linkToShips differentiateContacts
136+ />
137+ {#if pageResults.length >= 5}
138+ <SearchResultsNavButtons {page} {pageSize} {totalPages}
139+ on:updatePage={({ detail }) => page = detail.page}
140+ >
141+ <p class="px-4 py-8 text-center">
142+ Showing {itemRangeText} of {combinedSearchResults.length} results
143+ </p>
144+ </SearchResultsNavButtons>
145+ {/if}
146+ </div>
147+{/if}
ui/src/components/search/SearchResultsNavButtons.sveltenew file+54
@@ -0,0 +1,54 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ export let page: number;
4+ export let pageSize: number;
5+ export let totalPages: number;
6+ const dispatch = createEventDispatcher();
7+
8+ function updatePage(page: number) {
9+ dispatch('updatePage', { page });
10+ }
11+
12+ function visitPrevious() {
13+ updatePage(page - 1);
14+ }
15+
16+ function visitNext() {
17+ updatePage(page + 1);
18+ }
19+
20+ function seeList() {
21+ updatePage(null);
22+ }
23+</script>
24+
25+{#if totalPages > 1}
26+ <div class="flex items-center px-8 max-w-md w-full">
27+ <div class="justify-self-start h-6">
28+ <button
29+ on:click={visitPrevious}
30+ class="h-6 w-12 bg-gold"
31+ class:invisible={!(page > 0)}
32+ style="
33+ clip-path: polygon(25% 0%, 0% 50%, 25% 100%, 100% 100%, 75% 50%, 100% 0%);
34+ "
35+ >
36+ </button>
37+ </div>
38+ <div class="grow">
39+ <slot/>
40+ </div>
41+ <div class="justify-self-end h-6">
42+ <button
43+ on:click={visitNext}
44+ class="h-6 w-12 bg-gold"
45+ class:invisible={!(page < totalPages - 1)}
46+ style="
47+
48+ clip-path: polygon(75% 0%, 100% 50%, 75% 100%, 0% 100%, 25% 50%, 0% 0%);
49+ "
50+ >
51+ </button>
52+ </div>
53+ </div>
54+{/if}
ui/src/components/search/SearchSettings.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import ToggleButton from '../buttons/ToggleButton.svelte';
3+
4+ import { searchSettings } from 'stores/searchStores';
5+ import { toggleStoreKey } from 'lib/utils';
6+
7+ function toggleMoons() {
8+ toggleStoreKey(searchSettings, 'includeMoons');
9+ }
10+
11+ function toggleComets() {
12+ toggleStoreKey(searchSettings, 'includeComets');
13+ }
14+</script>
15+
16+<div class="max-w-md rounded-lg bg-gold overflow-hidden flex flex-wrap items-center justify-center">
17+ <div class="px-4 py-2 flex gap-x-2 items-center border-navy">
18+ <span>Include Moons</span>
19+ <ToggleButton on:click={toggleMoons} on={$searchSettings.includeMoons}></ToggleButton>
20+ </div>
21+ <div class="px-4 py-2 flex gap-x-2 items-center border-navy">
22+ <span>Include Comets</span>
23+ <ToggleButton on:click={toggleComets} on={$searchSettings.includeComets}></ToggleButton>
24+ </div>
25+</div>
ui/src/components/search/sigil/ClanSelect.sveltenew file+44
@@ -0,0 +1,44 @@
1+<script lang="ts">
2+ import ClanSelectOption from './ClanSelectOption.svelte';
3+
4+ import { capitalize } from 'lib/utils';
5+ import { CLANS, Clan } from 'types/sigil';
6+ import keys from 'stores/keys';
7+
8+ export let clan: Clan = 'planet';
9+
10+ let dropdownOpen = false;
11+
12+ function openDropdown() { dropdownOpen = true }
13+ function closeDropdown() { dropdownOpen = false }
14+
15+ function selectClan(itemClan: typeof clan) {
16+ clan = itemClan;
17+ dropdownOpen = false;
18+ }
19+
20+ $: {
21+ $keys.keyup.get('escape').set('clanSelect', closeDropdown);
22+ }
23+</script>
24+
25+<div class="text-xl text-center bg-white">
26+ <p
27+ on:click={openDropdown}
28+ class=" py-2"
29+ >
30+ {capitalize(clan)}
31+ <span class="font-mono">▼</span>
32+ </p>
33+ {#if dropdownOpen}
34+ <div class="absolute z-20 top-1 w-full flex flex-col mx-auto divide-y bg-white rounded-2xl border-b">
35+ <h3 class="py-2" on:click={closeDropdown}>
36+ Select Sigil Type
37+ </h3>
38+ {#each CLANS as itemClan}
39+ <ClanSelectOption clan={itemClan} on:click={() => selectClan(itemClan)} />
40+ {/each}
41+ </div>
42+ {/if}
43+</div>
44+
ui/src/components/search/sigil/ClanSelectOption.sveltenew file+17
@@ -0,0 +1,17 @@
1+<script lang="ts">
2+ import { capitalize } from 'lib/utils';
3+ import MiniFakeSigil from './MiniFakeSigil.svelte';
4+
5+ export let sigilSearch = {};
6+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
7+
8+ let dropdownOpen = true;
9+</script>
10+
11+<div class="flex items-center justify-center gap-2 py-2" on:click>
12+ <h3>
13+ {capitalize(clan)}:
14+ </h3>
15+ <MiniFakeSigil {clan} />
16+</div>
17+
ui/src/components/search/sigil/GeonSelector.sveltenew file+30
@@ -0,0 +1,30 @@
1+<script lang="ts">
2+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
3+
4+ import { searchSettings } from 'stores/searchStores';
5+ import type { SymbolQuery } from 'lib/sigil';
6+ import { GEONS } from 'types/sigil';
7+ import { arePartsPlausible } from 'lib/sigil';
8+
9+ export let symbolQuery: SymbolQuery;
10+ export let popMode: Function;
11+
12+ function setGeon(geon) {
13+ symbolQuery.geon = geon;
14+ popMode();
15+ }
16+
17+ $: componentsSansGeon = symbolQuery.components.filter(c => c[0] !== 'g');
18+</script>
19+
20+{#each GEONS as geon}
21+ {@const plausible = $searchSettings.allowFictionalSigils ||
22+ arePartsPlausible(componentsSansGeon, symbolQuery.symbolType, [geon])
23+ }
24+ <SymbolInputModeSelectorButton components={[geon]}
25+ focused={geon === symbolQuery.geon}
26+ enabled={plausible}
27+ on:click={() => setGeon(geon)}
28+ />
29+{/each}
30+
ui/src/components/search/sigil/MiniFakeSigil.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
3+
4+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
5+
6+ let size = 32;
7+ let squareDef = ['gci'];
8+ let square = symbolFromDef(squareDef);
9+ let sigilProps = {
10+ size,
11+ renderer: sr,
12+ colors: ['white', 'black'],
13+ icon: true,
14+ }
15+ let sigils = {
16+ galaxy: sigilFromSymbols([square], sigilProps),
17+ star: sigilFromSymbols([square, square], sigilProps),
18+ planet: sigilFromSymbols([square, square, square, square], sigilProps),
19+ }
20+ $: sigilWidth = (clan === 'galaxy' ? size / 2 : size);
21+</script>
22+
23+<div style:width="{sigilWidth}px">
24+ {@html sigils[clan]}
25+</div>
ui/src/components/search/sigil/SigilInput.sveltenew file+104
@@ -0,0 +1,104 @@
1+<script lang="ts">
2+ import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
4+ import SymbolInput from './SymbolInput.svelte';
5+ import ClanSelect from './ClanSelect.svelte';
6+ import ToggleButton from '@/buttons/ToggleButton.svelte';
7+
8+ import type { SigilQuery } from 'lib/sigil';
9+ import { toggleStoreKey } from 'lib/utils';
10+ import { searchSettings } from 'stores/searchStores';
11+ import keys from 'stores/keys';
12+
13+ export let sigilQuery: SigilQuery;
14+ let focusedSymbolIndex: number = undefined;
15+ let inputComponents: string[] = [];
16+
17+ $: gridClasses = (sigilQuery.clan === 'galaxy' ? 'max-w-[10rem] grid items-center' : 'max-w-[18rem] grid grid-cols-2 items-center');
18+
19+ function unfocusSymbol() {
20+ focusedSymbolIndex = undefined;
21+ }
22+ $: focusedSymbol = sigilQuery.activeSymbols[focusedSymbolIndex];
23+ $: {
24+ const numActiveSymbols = sigilQuery.activeSymbols.length;
25+ if (focusedSymbolIndex >= numActiveSymbols) {
26+ focusedSymbolIndex = numActiveSymbols - 1;
27+ }
28+ }
29+
30+ function toggleAllowFictional() {
31+ toggleStoreKey(searchSettings, 'allowFictionalSigils');
32+ }
33+
34+ function clear() {
35+ sigilQuery = sigilQuery.clearSymbols();
36+ }
37+
38+ $: {
39+ sigilQuery.allowFictional = $searchSettings.allowFictionalSigils;
40+ }
41+
42+ function keyboardClear() {
43+ if (!focusedSymbol) {
44+ clear();
45+ }
46+ }
47+
48+ $: {
49+ $keys.keyup.get('x').set('sigilInput', keyboardClear);
50+ $keys.keyup.get('backspace').set('sigilInput', keyboardClear);
51+ }
52+</script>
53+
54+<div>
55+ <ClanSelect bind:clan={sigilQuery.clan} />
56+ <div class="flex flex-col xs:flex-row">
57+ <div class="grow-[2] w-full xs:m-0 xs:max-w-[18rem]" on:click={unfocusSymbol}>
58+ <div class="min-h-[14rem] mx-auto p-4 {gridClasses} xs:rounded-bl-2xl">
59+ {#each sigilQuery.activeSymbols as symbol, index}
60+ <SymbolInput
61+ bind:symbolQuery={symbol}
62+ {focusedSymbolIndex}
63+ {index}
64+ clan={sigilQuery.clan}
65+ {inputComponents}
66+ on:click={() => focusedSymbolIndex = index}
67+ />
68+ {/each}
69+ </div>
70+ </div>
71+ <div class="px-4 py-2 border-t grow rounded-b-2xl xs:border-l xs:border-t-0 xs:rounded-bl-none">
72+ {#if focusedSymbol}
73+ <SymbolInputModeSelector
74+ bind:symbolQuery={focusedSymbol}
75+ bind:inputComponents
76+ unfocusSymbol={unfocusSymbol}
77+ />
78+ {:else}
79+ <SymbolInputNavButtons onClear={sigilQuery.isNotEmpty ? clear : null} />
80+ <p class="mt-2">
81+ Allow impossible Sigils:
82+ <ToggleButton on:click={toggleAllowFictional} on={$searchSettings.allowFictionalSigils} />
83+ </p>
84+ {/if}
85+ </div>
86+ </div>
87+ <!-- {#each sigilQuery.activeSymbols as symbol}
88+ {@const plausibleSyllables = symbol.plausibleSyllables}
89+ {@const plausibleParts = symbol.plausibleParts}
90+ <p>
91+ {#if plausibleSyllables.length < 20 && plausibleSyllables.length > 0}
92+ {plausibleSyllables.join(', ')}:
93+ {#if plausibleParts.length < 20 && plausibleParts.length > 0}
94+ {plausibleParts.join(', ')}
95+ {:else}
96+ {plausibleParts.length}
97+ {/if}
98+ {:else}
99+ {plausibleSyllables.length}
100+ {/if}
101+ </p>
102+ {/each} -->
103+ <!-- {sigilQuery.string} -->
104+</div>
ui/src/components/search/sigil/Symbol.sveltenew file+79
@@ -0,0 +1,79 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
4+ import { sigilScalingFunction } from 'lib/sigil';
5+ import symbolPartClick from 'actions/symbolPartClick';
6+
7+ export let components: string[];
8+ export let size: number;
9+ export let fgColor: string = 'black';
10+ export let bgColor: string = 'white';
11+ export let altBgColor: string = undefined;
12+ export let hoverColor: string = '#8f8';
13+ export let opacity: number = 1;
14+ export let inverted: boolean = false;
15+ export let interactive: boolean = false;
16+ export let strokeWidthFactor: number = 1;
17+ let svgString: any;
18+
19+ const dispatch = createEventDispatcher();
20+
21+ $: {
22+ const symbol = symbolFromDef(components);
23+ let colors = [bgColor, fgColor];
24+ let tempAltBgColor = altBgColor;
25+ if (inverted) {
26+ colors = [fgColor, bgColor];
27+ if (!tempAltBgColor) tempAltBgColor = bgColor;
28+ }
29+ const svgAST = sigilFromSymbols(
30+ [symbol],
31+ {
32+ size,
33+ colors,
34+ margin: false,
35+ autoScaleStrokes: true,
36+ strokeScalingFunctionV2: (s) => {
37+ return strokeWidthFactor * sigilScalingFunction(s);
38+ },
39+ },
40+ );
41+ svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
42+ svgString = sr(svgAST);
43+ if (tempAltBgColor) {
44+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${tempAltBgColor}"`);
45+ }
46+ }
47+
48+ function onPartClick(partId: string) {
49+ dispatch('partClick', partId);
50+ }
51+</script>
52+
53+<style lang="scss" global>
54+ .interactive {
55+ svg {
56+ pointer-events: none;
57+ }
58+
59+ path, circle, line {
60+ pointer-events: auto;
61+
62+ &:not([dataisgeon="true"]):hover {
63+ stroke: var(--hoverColor);
64+ }
65+ }
66+
67+ circle:not([dataisgeon="true"]):not([fill="none"]):hover {
68+ fill: var(--hoverColor);
69+ }
70+ }
71+</style>
72+
73+<div class="aspect-square" class:interactive style:max-width="{size}px"
74+ style="--hoverColor: {hoverColor}; opacity: {opacity};"
75+ on:click
76+ use:symbolPartClick={{ onPartClick, enabled: interactive, svgString }}
77+>
78+ {@html svgString}
79+</div>
ui/src/components/search/sigil/SymbolInput.sveltenew file+105
@@ -0,0 +1,105 @@
1+<script lang="ts">
2+ import Symbol from './Symbol.svelte';
3+ import without from 'lodash/without';
4+
5+ import { searchSettings } from 'stores/searchStores';
6+ import type { SymbolQuery } from 'lib/sigil';
7+ import { filterPartsByGeon } from 'lib/sigil';
8+
9+ export let symbolQuery: SymbolQuery;
10+ export let focusedSymbolIndex: number;
11+ export let index: number = 0;
12+ export let clan: string = 'galaxy';
13+ export let inputComponents: string[] = [];
14+ export let fgColor = 'black';
15+ export let bgColor = 'white';
16+ let size = 128;
17+
18+ let translateClass;
19+ $: {
20+ translateClass = 'translate-x-0 translate-y-0';
21+ if (focusedSymbolIndex !== undefined) {
22+ if (clan === 'star') {
23+ translateClass = index ? 'origin-right' : 'origin-left';
24+ } else if (clan === 'planet') {
25+ let cornerIndex = index;
26+ if (!focused) cornerIndex = focusedSymbolIndex;
27+ switch (cornerIndex) {
28+ case 0: translateClass = 'translate-x-1/4 translate-y-1/4'; break;
29+ case 1: translateClass = '-translate-x-1/4 translate-y-1/4'; break;
30+ case 2: translateClass = 'translate-x-1/4 -translate-y-1/4'; break;
31+ case 3:
32+ default: translateClass = '-translate-x-1/4 -translate-y-1/4';
33+ }
34+ }
35+ }
36+ }
37+ $: scaleClass = 'scale-150 ';
38+
39+ $: focused = index === focusedSymbolIndex;
40+ $: focusedClasses = (
41+ focused ? `${scaleClass} z-10`
42+ : (focusedSymbolIndex !== undefined ? 'scale-50' : '' )
43+ );
44+
45+ function onPartDelete({ detail: partId }) {
46+ symbolQuery = symbolQuery.removePart(partId);
47+ }
48+
49+ function onPartClick({ detail: partId }) {
50+ symbolQuery = symbolQuery.addPart(partId);
51+ }
52+
53+ let filteredInputComponents = [];
54+ let inputContainsStrokes;
55+ $: {
56+ if (inputComponents) {
57+ filteredInputComponents = without(inputComponents, ...symbolQuery.components);
58+ } else {
59+ filteredInputComponents = [];
60+ }
61+ filteredInputComponents = filterPartsByGeon(filteredInputComponents, symbolQuery.geon);
62+ }
63+ $: {
64+ inputContainsStrokes = undefined !== filteredInputComponents.find((c) => {
65+ return c[0] === 'l' || c[0] === 'a' || c[0] === 'b';
66+ });
67+ }
68+ $: plausible = $searchSettings.allowFictionalSigils || symbolQuery.isPlausible;
69+</script>
70+
71+<div class="aspect-square relative flex transition {translateClass} {focusedClasses}"
72+ style:max-width="{size}px"
73+ on:click|stopPropagation
74+>
75+ {#if symbolQuery.components.length > 0}
76+ <Symbol components={symbolQuery.components} {size}
77+ inverted={!symbolQuery.geon}
78+ interactive={focused}
79+ strokeWidthFactor={focused ? 2 : 1}
80+ fgColor={!plausible ? 'red' : (symbolQuery.isPerfectMatch ? 'black' : '#333')}
81+ bgColor="white"
82+ altBgColor="transparent"
83+ hoverColor="red"
84+ on:partClick={onPartDelete}
85+ />
86+ {:else}
87+ <div class="border-[1.5px] border-white grow bg-white">
88+ <div class="rounded-md border-4 border-gray-500 h-full"></div>
89+ </div>
90+ {/if}
91+ {#if focused && filteredInputComponents.length}
92+ <div class="absolute inset-0 pointer-events-none">
93+ <Symbol components={filteredInputComponents} {size}
94+ strokeWidthFactor={inputContainsStrokes ? 2.5 : 1}
95+ fgColor={symbolQuery.geon ? bgColor : fgColor}
96+ bgColor={!symbolQuery.geon ? bgColor : fgColor}
97+ altBgColor="transparent"
98+ opacity={0.5}
99+ inverted
100+ interactive
101+ on:partClick={onPartClick}
102+ />
103+ </div>
104+ {/if}
105+</div>
ui/src/components/search/sigil/SymbolInputModeSelector.sveltenew file+168
@@ -0,0 +1,168 @@
1+<script lang="ts">
2+ import SymbolInputNavButtons from './SymbolInputNavButtons.svelte';
3+
4+ import GeonSelector from './GeonSelector.svelte';
5+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
6+
7+ import { searchSettings } from 'stores/searchStores';
8+ import keys from 'stores/keys';
9+ import type { PartType, ModeDef, ModeDefs } from 'types/sigil';
10+ import type { SymbolQuery } from 'lib/sigil';
11+ import { MODE_DEFS } from 'types/sigil';
12+ import { filterPartsByGeon, getDeepParts } from 'lib/sigil';
13+
14+ export let symbolQuery: SymbolQuery = null;
15+ export let inputComponents: string[] = [];
16+ export let unfocusSymbol: Function = () => {};
17+
18+ let mode: string = null;
19+ let subMode: string = null;
20+ let modeDef: ModeDef = undefined;
21+ let unselectedSubModesExist: boolean = false;
22+ let modeDefs: ModeDefs = MODE_DEFS;
23+ let plausibleModeDefs: { name: string, def: ModeDef }[] = [];
24+
25+ function showGeonSelector() {
26+ if (!symbolQuery.components.length && !mode) {
27+ mode = 'geon';
28+ subMode = null;
29+ }
30+ }
31+
32+ $: symbolQuery.components, showGeonSelector();
33+
34+ function pushMode(newMode: string) {
35+ const subModes = modeDef?.modes;
36+ if (subModes || subMode) {
37+ subMode = newMode;
38+ } else {
39+ mode = newMode;
40+ subMode = null;
41+ }
42+ }
43+
44+ function popMode() {
45+ if (mode || subMode) {
46+ mode = null;
47+ subMode = null;
48+ } else {
49+ unfocusSymbol();
50+ }
51+ }
52+
53+ $: {
54+ let modeResolved = false;
55+ let count = 0;
56+ while (!modeResolved && count < 5) {
57+ modeDef = undefined;
58+ modeDefs = MODE_DEFS;
59+ if (mode) {
60+ modeDef = MODE_DEFS[mode];
61+ }
62+ unselectedSubModesExist = !!modeDef?.modes;
63+ if (unselectedSubModesExist) {
64+ modeDefs = modeDef.modes;
65+ if (subMode) {
66+ if (modeDefs[subMode]) {
67+ modeDef = modeDefs[subMode];
68+ } else {
69+ console.error(`Unknown subMode: ${subMode} of ${mode} in defs:`, modeDefs);
70+ subMode = null;
71+ }
72+ }
73+ }
74+ unselectedSubModesExist = !!modeDef?.modes;
75+ if (!isDefPlausible(modeDef) && mode !== 'geon') {
76+ if (subMode) {
77+ subMode = null;
78+ } else {
79+ mode = null;
80+ }
81+ } else {
82+ plausibleModeDefs = getPlausibleModeDefs(modeDefs);
83+ if (unselectedSubModesExist) {
84+ subMode = plausibleModeDefs[0].name;
85+ } else {
86+ modeResolved = true;
87+ }
88+ }
89+ count++;
90+ if (count >= 5 && !modeResolved) {
91+ console.error("sigil input mode resulotion failed");
92+ }
93+ }
94+ inputComponents = [];
95+ if (modeDef?.parts?.length) {
96+ inputComponents = modeDef.parts;
97+ if (!$searchSettings.allowFictionalSigils) {
98+ inputComponents = symbolQuery.filterPlausibleParts(inputComponents);
99+ }
100+ }
101+ }
102+
103+ function isDefPlausible(def: ModeDef): boolean {
104+ if (!def) return true;
105+ const deepParts = getDeepParts(def);
106+ return $searchSettings.allowFictionalSigils || symbolQuery.arePartsPlausible(deepParts);
107+ }
108+
109+ function getPlausibleModeDefs(modeDefs: ModeDefs) {
110+ return Object.entries(modeDefs).map(([modeName, def]) => {
111+ const plausible = isDefPlausible(def);
112+ if (!plausible && modeName !== 'geon') return null;
113+ const parts = filterPartsByGeon(def.displayParts || def.parts, symbolQuery.geon);
114+ return {
115+ name: modeName,
116+ def: {
117+ ...def,
118+ parts,
119+ },
120+ };
121+ }).filter(mode => mode !== null);
122+ }
123+
124+ function clear() {
125+ // If a geon is present it should be preserved, unless it's the only component
126+ // If there's only 1 component, it should be removed
127+ // Otherwise, at least one component might be a geon and should be preserved
128+ if (symbolQuery.components.length === 1) {
129+ symbolQuery = symbolQuery.clear();
130+ } else {
131+ symbolQuery = symbolQuery.clearExceptPartType('geon');
132+ }
133+ }
134+
135+ $: {
136+ $keys.keyup.get('x').set('sigilMode', clear);
137+ $keys.keyup.get('backspace').set('sigilMode', clear);
138+ $keys.keyup.get('escape').set('sigilMode', popMode);
139+ }
140+</script>
141+
142+<style lang="scss">
143+ .grid-cols-custom {
144+ grid-template-columns: repeat(auto-fill, 48px);
145+ }
146+</style>
147+
148+<div class="mx-auto flex justify-center xs:items-center xs:flex-col">
149+ <SymbolInputNavButtons onDone={popMode} onClear={clear} hideClearButton={symbolQuery.components.length == 0} />
150+ <div class="max-w-[15rem] h-full grid grid-cols-custom gap-1 justify-center xs:max-w-[8rem]">
151+ {#if mode === 'geon'}
152+ <GeonSelector bind:symbolQuery {popMode} />
153+ {:else}
154+ {#each plausibleModeDefs as { name, def: { parts } }}
155+ <SymbolInputModeSelectorButton components={parts} focused={mode === name || subMode === name}
156+ on:click={() => pushMode(name)}
157+ />
158+ {/each}
159+ {/if}
160+ </div>
161+ <!-- <p class="break-all w-24">
162+ mode: {mode}
163+ </p>
164+ <p class="break-all w-24">
165+ sub: {subMode}
166+ </p> -->
167+</div>
168+
ui/src/components/search/sigil/SymbolInputModeSelectorButton.sveltenew file+33
@@ -0,0 +1,33 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import Symbol from './Symbol.svelte';
4+
5+ export let components: string[] = [];
6+ export let focused: boolean = false;
7+ export let enabled: boolean = true;
8+ export let padding: number = undefined;
9+
10+ const dispatch = createEventDispatcher();
11+ let size = 48;
12+
13+ $: focusedClasses = focused ? 'border-2 border-gold-s1 p-[3px]' : 'border border-gray-500 p-[4px]';
14+ $: enabledClasses = enabled ? '' : 'opacity-50';
15+ $: includesGeon = components.find(c => c[0] === 'g') !== undefined;
16+ $: defaultPadding = includesGeon ? 0 : 0;
17+ $: actualPadding = padding !== undefined ? padding : defaultPadding;
18+
19+ function onClick(e) {
20+ if (enabled) dispatch('click', e);
21+ }
22+</script>
23+<div
24+class="bg-white rounded-md w-[48px] {focusedClasses} {enabledClasses}"
25+on:click={onClick}
26+>
27+<div class:cursor-pointer={enabled}
28+ style:padding="{actualPadding}px"
29+>
30+ <Symbol {components} {size} inverted={!includesGeon} />
31+</div>
32+</div>
33+
ui/src/components/search/sigil/SymbolInputNavButtons.sveltenew file+30
@@ -0,0 +1,30 @@
1+<script lang="ts">
2+ import upArrow from 'assets/up-arrow.svg';
3+ import xIcon from 'assets/x.svg';
4+
5+ export let onDone: () => void = null;
6+ export let onClear: () => void = null;
7+ export let hideDoneButton = false;
8+ export let hideClearButton = false;
9+
10+ $: showDoneButton = (onDone && !hideDoneButton);
11+ $: showClearButton = (onClear && !hideClearButton);
12+
13+ let buttonClasses = 'cursor-pointer border border-gray-500 rounded-md aspect-square w-[48px]';
14+</script>
15+
16+<style lang="scss">
17+ .grid-cols-custom {
18+ grid-template-columns: repeat(auto-fill, 48px);
19+ }
20+</style>
21+
22+<div class="grid gap-1 self-start justify-center mr-1 xs:mr-0 xs:mb-1 xs:self-stretch {(showDoneButton && showClearButton) ? 'grid-cols-custom' : ''}">
23+ {#if showDoneButton}
24+ <img src={upArrow} alt="done" class={buttonClasses} on:click={onDone}/>
25+ {/if}
26+ {#if showClearButton}
27+ <img src={xIcon} alt="clear" class={buttonClasses} on:click={onClear}/>
28+ {/if}
29+ </div>
30+
ui/src/components/ship_view/AzimuthDetails.sveltenew file+90
@@ -0,0 +1,90 @@
1+<script lang="ts">
2+ import { clan } from 'urbit-ob';
3+ import TooltipAndDocLink from '@/common/TooltipAndDocLink.svelte';
4+ import EthAddressLink from '@/common/EthAddressLink.svelte';
5+ import ShipChain from './ShipChain.svelte';
6+
7+ export let patp: string;
8+ export let pointInfo: any = {};
9+
10+
11+ $: shipClass = clan(patp);
12+ $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
13+</script>
14+
15+{#if azPoint && pointInfo.life !== undefined}
16+ {#if shipClass !== 'galaxy'}
17+ <p>
18+ {#if pointInfo.sponsorChain && pointInfo.sponsorChain.length > 0}
19+ <p>
20+ {pointInfo.sponsorChain.length === 1 ? 'Sponsor:' : 'Sponsor Chain:'}
21+ <ShipChain shipChain={pointInfo.sponsorChain}></ShipChain>
22+ </p>
23+ {:else}
24+ Unsponsored
25+ {/if}
26+ </p>
27+ {/if}
28+ <p>
29+ <TooltipAndDocLink
30+ text="Number of times this ship's keys have been set. Must be at least 1 to use the network."
31+ doc="keys"
32+ class="inline"
33+ >
34+ Key Revision:
35+ {pointInfo.life}
36+ </TooltipAndDocLink>
37+ </p>
38+ <p>
39+ <TooltipAndDocLink
40+ text="Number of times this ship has breached."
41+ doc="keys"
42+ class="inline"
43+ >
44+ Factory Resets:
45+ {pointInfo.rift}
46+ </TooltipAndDocLink>
47+ </p>
48+ {#if pointInfo.proxies}
49+ <p>
50+ <span>
51+ Owner:
52+ <EthAddressLink address={pointInfo.proxies.owner.address} />
53+ </span>
54+ </p>
55+ <p>
56+ <span>
57+ Management Proxy:
58+ <EthAddressLink address={pointInfo.proxies['management-proxy'].address}>
59+ <span class="text-gray-700">not set</span>
60+ </EthAddressLink>
61+ </span>
62+ </p>
63+ <p>
64+ <span>
65+ Spawn Proxy:
66+ <EthAddressLink address={pointInfo.proxies['spawn-proxy'].address}>
67+ <span class="text-gray-700">not set</span>
68+ </EthAddressLink>
69+ </span>
70+ </p>
71+ <p>
72+ <span>
73+ Transfer Proxy:
74+ <EthAddressLink address={pointInfo.proxies['transfer-proxy'].address}>
75+ <span class="text-gray-700">not set</span>
76+ </EthAddressLink>
77+ </span>
78+ </p>
79+ {#if shipClass === 'galaxy'}
80+ <p>
81+ <span>
82+ Voting Proxy:
83+ <EthAddressLink address={pointInfo.proxies['voting-proxy'].address}>
84+ [not set]
85+ </EthAddressLink>
86+ </span>
87+ </p>
88+ {/if}
89+ {/if}
90+{/if}
ui/src/components/ship_view/ShipChain.sveltenew file+11
@@ -0,0 +1,11 @@
1+<script lang="ts">
2+ import ShipLink from '@/common/ShipLink.svelte';
3+
4+ export let shipChain: string[];
5+</script>
6+
7+{#each shipChain as ship, index}
8+ {#if index > 0}{' > '}{/if}
9+ <ShipLink patp={ship}></ShipLink>
10+{/each}
11+
ui/src/components/ship_view/ShipChildrenTabs.sveltenew file+136
@@ -0,0 +1,136 @@
1+<script lang="ts">
2+ import { clan } from 'urbit-ob';
3+
4+ import { normalizeId, filterIdsForChildMoons } from 'lib/id';
5+ import { getSpawnedPoints, getUnlockedSpawnedPoints, getLockedSpawnedPoints } from 'lib/api';
6+ import store from 'stores/store';
7+ import ShipListings from '@/ShipListings.svelte';
8+
9+ export let patp: string;
10+ export let spawnedCount: number = undefined;
11+
12+ let rawSpawnedPointsPromise: any;
13+ let rawSpawnedPoints: any = null;
14+ let rawLockedPointsPromise: any;
15+ let rawLockedPoints: any = null;
16+ let spawnedPoints: string[] = [];
17+ let lockedPoints: string[] = [];
18+
19+ $: shipClass = clan(patp);
20+ $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
21+ $: canSpawnPoints = ['galaxy', 'star'].includes(shipClass);
22+ $: spawnableClassPlural = !canSpawnPoints ? '' : (shipClass === 'galaxy' ? 'Star' : 'Planet');
23+ $: {
24+ rawSpawnedPoints = null;
25+ rawLockedPoints = null;
26+ if (canSpawnPoints) {
27+ let galaxy = shipClass === 'galaxy';
28+ let getSpawnedPointsFunc = galaxy ? getUnlockedSpawnedPoints : getSpawnedPoints;
29+ if (galaxy) {
30+ rawLockedPointsPromise = getLockedSpawnedPoints(patp);
31+ rawLockedPointsPromise.then((info) => {
32+ rawLockedPoints = info;
33+ }).catch((error) => {
34+ rawLockedPoints = { error };
35+ });
36+ }
37+ rawSpawnedPointsPromise = getSpawnedPointsFunc(patp);
38+ rawSpawnedPointsPromise.then((info) => {
39+ rawSpawnedPoints = info;
40+ }).catch((error) => {
41+ rawSpawnedPoints = { error };
42+ });
43+ }
44+ }
45+ $: {
46+ spawnedPoints = [];
47+ if (rawSpawnedPoints?.points) {
48+ spawnedPoints = rawSpawnedPoints?.points.map(patpToShip);
49+ }
50+ }
51+ $: {
52+ lockedPoints = [];
53+ if (rawLockedPoints?.points) {
54+ lockedPoints = rawLockedPoints?.points.map(patpToShip);
55+ }
56+ }
57+ $: trueSpawnedCount = spawnedCount || spawnedPoints.length;
58+ $: spawnedTab = {
59+ name: 'Spawned',
60+ plural: 'Spawned ' + spawnableClassPlural + (trueSpawnedCount == 1 ? '' : 's'),
61+ promise: rawSpawnedPointsPromise,
62+ points: spawnedPoints,
63+ count: trueSpawnedCount,
64+ };
65+ $: lockedTab = {
66+ name: 'Locked',
67+ plural: 'Locked ' + spawnableClassPlural + (lockedPoints.length == 1 ? '' : 's'),
68+ promise: rawLockedPointsPromise,
69+ points: lockedPoints,
70+ count: lockedPoints.length,
71+ };
72+ $: moons = filterIdsForChildMoons($store.peers, patp).map(patpToShip);
73+ $: moonsTab = {
74+ name: 'Moons',
75+ plural: 'Known Moon' + (moons.length == 1 ? '' : 's'),
76+ promise: true,
77+ points: moons,
78+ count: moons.length,
79+ };
80+ let tabIndex = 0;
81+ let tabs = [];
82+ $: {
83+ tabs = [];
84+ if (spawnedCount !== undefined || spawnedPoints.length > 0) {
85+ tabs.push(spawnedTab);
86+ }
87+ if (lockedPoints.length > 0) {
88+ tabs.push(lockedTab);
89+ }
90+ if (azPoint) {
91+ tabs.push(moonsTab);
92+ }
93+ }
94+ $: tab = tabs[tabIndex];
95+
96+ function patpToShip(id: string) {
97+ return { patp: normalizeId(id) };
98+ }
99+</script>
100+
101+<div class="p-3">
102+ {#if tabs.length > 1}
103+ <div class="flex gap-x-1">
104+ {#each tabs as tab, i}
105+ <h3 on:click={() => tabIndex = i}
106+ class:bg-gray-200={tabIndex !== i}
107+ class="px-2 border border-gray-300 border-b-0 rounded-t-md cursor-pointer"
108+ >
109+ {tab.name}
110+ </h3>
111+ {/each}
112+ </div>
113+ {/if}
114+ {#if tab}
115+ <div>
116+ <h3 class="text-lg m-2">
117+ {#if tab.count}
118+ {tab.count} {tab.plural}:
119+ {:else}
120+ No {tab.plural}
121+ {/if}
122+ </h3>
123+ {#await tab.promise}
124+ Loading {tab.plural}...
125+ {:then}
126+ <!-- TODO: pagination -->
127+ <ShipListings
128+ ships={tab.points}
129+ linkToShips
130+ differentiateContacts
131+ />
132+ {/await}
133+ </div>
134+ {/if}
135+</div>
136+
ui/src/components/ship_view/ShipView.sveltenew file+130
@@ -0,0 +1,130 @@
1+<script lang="ts">
2+ import { clan, patp2dec } from 'urbit-ob';
3+
4+ import { cite, normalizeId } from 'lib/id';
5+ import { getPoint, getSpawnedPoints } from 'lib/api';
6+ import { generateParentChain, processPointInfo } from 'lib/ship';
7+ import Sigil from '@/common/Sigil.svelte';
8+ import GoldBadge from '@/common/GoldBadge.svelte';
9+ import TooltipAndDocLink from '@/common/TooltipAndDocLink.svelte';
10+ import ShipLink from '@/common/ShipLink.svelte';
11+ import ChartLink from '@/common/ChartLink.svelte';
12+ import EthAddressLink from '@/common/EthAddressLink.svelte';
13+ import LoadingSpinner from '@/common/LoadingSpinner.svelte';
14+ import CollapsibleContent from '@/CollapsibleContent.svelte';
15+ import ShipChain from './ShipChain.svelte';
16+ import AzimuthDetails from './AzimuthDetails.svelte';
17+ import ShipChildrenTabs from './ShipChildrenTabs.svelte';
18+ import ProfileShipViewExtension from '~/components/extensions/ProfileShipViewExtension.svelte';
19+ import PalsShipViewExtension from '@/pals/PalsShipViewExtension.svelte';
20+ import AppsShipViewExtension from '@/extensions/AppsShipViewExtension.svelte';
21+ // import GroupsShipViewExtension from '~/components/extensions/GroupsShipViewExtension.svelte';
22+
23+ export let patp: string;
24+
25+ let parentChain: any[];
26+ let rawPointInfoPromise: any;
27+ let rawPointInfo: any = null;
28+ let pointInfo: any = {};
29+
30+ $: shipClass = clan(patp);
31+ $: azPoint = ['galaxy', 'star', 'planet'].includes(shipClass);
32+ $: {
33+ parentChain = generateParentChain(patp);
34+ }
35+ $: {
36+ rawPointInfo = null;
37+ if (azPoint) {
38+ rawPointInfoPromise = getPoint(patp);
39+ rawPointInfoPromise.then((info) => {
40+ rawPointInfo = info;
41+ }).catch((error) => {
42+ rawPointInfo = { error };
43+ });
44+ }
45+ }
46+ $: {
47+ pointInfo = {};
48+ if (azPoint) {
49+ pointInfo = processPointInfo(rawPointInfo);
50+ }
51+ }
52+</script>
53+
54+<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
55+ <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
56+ <div class="w-full max-w-[256px] md:min-w-[256px] flex-shrink-0">
57+ <Sigil patp={patp} size={256} useNew />
58+ </div>
59+ <div class="grow w-full min-w-0">
60+ <h2 class="text-2xl text-center">
61+ <TooltipAndDocLink
62+ text="The ship's @p (click to learn more)"
63+ doc="patpee" class="inline"
64+ >
65+ {cite(patp)}
66+ </TooltipAndDocLink>
67+ </h2>
68+ {#if (!azPoint)}
69+ <h3 class="text-lg text-center text-gray-700">{patp}</h3>
70+ {/if}
71+ <h4 class="text-center text-gray-700 break-words">
72+ {patp2dec(patp)}
73+ </h4>
74+ <div class="flex flex-wrap justify-center gap-1.5 my-1.5">
75+ <GoldBadge title={"it's a " + shipClass} doc={shipClass}>{shipClass}</GoldBadge>
76+ {#if azPoint}
77+ {#if pointInfo.spawnStatus}
78+ <GoldBadge title={pointInfo.spawnStatus.tooltip}>
79+ {pointInfo.spawnStatus.text}
80+ </GoldBadge>
81+ {/if}
82+ {#if pointInfo.layer}
83+ <GoldBadge title={pointInfo.layer.tooltip} doc="l2">{pointInfo.layer.text}</GoldBadge>
84+ {/if}
85+ {:else if (shipClass === 'moon')}
86+ <span>of</span>
87+ <ShipLink patp={parentChain.at(-1)}></ShipLink>
88+ {:else if (shipClass === 'comet')}
89+ <span>under</span>
90+ <ShipLink patp={parentChain.at(-1)}></ShipLink>
91+ {/if}
92+ </div>
93+ {#if pointInfo?.spawnedUnlocked}
94+ <div class="w-full text-center my-3">
95+ <ChartLink {patp}>View in Star Chart ↗</ChartLink>
96+ </div>
97+ {/if}
98+ <div class="mt-4 w-full break-words">
99+ {#if parentChain.length > 0}
100+ <p>
101+ {parentChain.length === 1 ? 'Parent:' : 'Parent Chain:'}
102+ <ShipChain shipChain={parentChain}></ShipChain>
103+ </p>
104+ {/if}
105+ {#if azPoint}
106+ {#await rawPointInfoPromise}
107+ <LoadingSpinner spinnerClass="text-black" />
108+ {:then}
109+ <CollapsibleContent>
110+ <b slot="title">Azimuth Details</b>
111+ <div slot="content">
112+ <AzimuthDetails {patp} {pointInfo}></AzimuthDetails>
113+ </div>
114+ </CollapsibleContent>
115+ {/await}
116+ {/if}
117+ </div>
118+ </div>
119+ </div>
120+ <div class="flex flex-wrap shrink min-h-0 h-fit -m-1 mt-2 justify-evenly justify-items-center">
121+ <ProfileShipViewExtension {patp} />
122+ <PalsShipViewExtension {patp} />
123+ <AppsShipViewExtension {patp} />
124+ </div>
125+ {#if azPoint}
126+ <div class="mt-5 border-t">
127+ <ShipChildrenTabs {patp} />
128+ </div>
129+ {/if}
130+</div>
ui/src/fake_data/fake-contacts.ts+6−1
@@ -1,4 +1,7 @@
1−export default {
1+
2+import type { Contacts } from '@urbit/api';
3+
4+const fakeContacts: Contacts = {
25 "~bus": {
36 "status": "busbusbus",
47 "last-updated": 1614902970000,
@@ -90,3 +93,5 @@ export default {
9093 ]
9194 },
9295};
96+
97+export default fakeContacts;
ui/src/lib/api.tsnew file+102
@@ -0,0 +1,102 @@
1+import 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+
8+console.log(`Initializing Urbit API at ${Date()}`);
9+const api: UrbitApi = new UrbitApi('', '', window.desk);
10+api.ship = window.ship;
11+// api.connect();
12+
13+export { api };
14+
15+export function scry<T = any>(path: string): Promise<T> {
16+ return api.scry<T>({ app: 'astrolabe', path });
17+}
18+
19+export function getPoint(patp: string): Promise<any> {
20+ return scry<any>(`/point/${patp}`);
21+}
22+
23+export function getSpawnedPoints(patp: string): Promise<any> {
24+ return scry<any>(`/point/${patp}/spawned`);
25+}
26+
27+export function getLockedSpawnedPoints(patp: string): Promise<any> {
28+ return scry<any>(`/point/${patp}/spawned/locked`);
29+}
30+
31+export function getUnlockedSpawnedPoints(patp: string): Promise<any> {
32+ return scry<any>(`/point/${patp}/spawned/unlocked`);
33+}
34+
35+export function getPeers(): Promise<any> {
36+ return scry<any>(`/peers`);
37+}
38+
39+export async function searchPoints(search: string, mode: ('patp' | 'sigil')): Promise<Patp[]> {
40+ const { points } = await scry<any>(`/search/${mode}/${search}`);
41+ return points.map(normalizeId);
42+}
43+
44+export function getChartData(old = false): Promise<any> {
45+ return scry<any>('/chart-data' + (old ? '-old' : ''));
46+}
47+
48+window['gcd'] = getChartData;
49+
50+export function getDoc(path: string): Promise<string> {
51+ return scry<string>(`/doc/${path}`);
52+}
53+
54+export function initialContacts(json: ContactUpdate, contacts: Rolodex): Rolodex {
55+ const data = _get(json, 'initial', false);
56+ if (data) {
57+ contacts = data.rolodex;
58+ }
59+ return contacts;
60+};
61+
62+export function addContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
63+ const data = _get(json, 'add', false);
64+ if (data) {
65+ contacts[data.ship] = data.contact;
66+ }
67+ return contacts;
68+};
69+
70+export function removeContact(json: ContactUpdate, contacts: Rolodex): Rolodex {
71+ const data = _get(json, 'remove', false);
72+ if (
73+ data &&
74+ (data.ship in contacts)
75+ ) {
76+ delete contacts[data.ship];
77+ }
78+ return contacts;
79+};
80+
81+export function handleContactUpdateEvent(updateContacts) {
82+ return ({ 'contact-update': contactUpdate }) => {
83+ console.log(`received contact-update: ${Object.keys(contactUpdate)}`)
84+ const reducers = [initialContacts, addContact, removeContact];
85+ const reducer = compose(reducers.map(r => sta => r(contactUpdate, sta)));
86+ updateContacts(reducer);
87+ };
88+}
89+
90+export function subscribeToContacts(e: (data: any) => void): Promise<any> {
91+ return api.subscribe({
92+ app: 'contact-store',
93+ path: '/all',
94+ event: handleContactUpdateEvent(e),
95+ err: () => {
96+ console.error(`Subscription to contact-store/all just get "err"`);
97+ },
98+ quit: () => {
99+ console.error(`Subscription to contact-store/all just get "quit"`);
100+ }
101+ })
102+}
ui/src/lib/apps.tsnew file+177
@@ -0,0 +1,177 @@
1+import _get from 'lodash/get';
2+import { compose } from 'lodash/fp';
3+import type { Apps } from 'types/store';
4+import store from 'stores/store';
5+import { sleep } from 'lib/utils';
6+import { api } from 'lib/api';
7+import { normalizeId } from 'lib/id';
8+
9+export function getProvidedApps(patp: string): Promise<any> {
10+ return api.scry<any>({ app: 'treaty', path: `/treaties/${patp}` });
11+}
12+
13+// Unused, use subscription
14+export function getKnownApps(): Promise<any> {
15+ return api.scry<any>({ app: 'treaty', path: `/allies` }).then(processAllies);
16+}
17+
18+export function getApp(ship: string, desk: string): Promise<any> {
19+ return getAppByPath(`${normalizeId(ship)}/${desk}`);
20+}
21+
22+// @param path - ship/desk e.g. ~mister-dister-dozzod-dozzod/webterm
23+export async function getAppByPath(path: string, retry: number = 2): Promise<any> {
24+ try {
25+ return await api.scry<any>({ app: 'treaty', path: `/treaty/${path}` });
26+ } catch (e) {
27+ if (retry > 0) {
28+ console.log(`Got error when fetching app: ${path}.\nTrying at most ${retry} more time(s)`);
29+ await sleep(750);
30+ return getAppByPath(path, retry - 1);
31+ } else {
32+ throw e;
33+ }
34+ }
35+}
36+
37+export function getInstalledApps(): Promise<any> {
38+ return api.scry<any>({ app: 'docket', path: `/charges` });
39+}
40+
41+export async function sendAllyUpdate(cmd: string, ship: string) {
42+ let result = await api.poke({
43+ app: 'treaty',
44+ mark: 'ally-update-0',
45+ json: { [cmd]: normalizeId(ship) },
46+ });
47+ return result;
48+}
49+
50+export function addAlly(patp: string) {
51+ return sendAllyUpdate('add', patp);
52+}
53+
54+export function delAlly(patp: string) {
55+ return sendAllyUpdate('del', patp);
56+}
57+
58+export async function addAllies(ships: string[]) {
59+ let results = await Promise.allSettled(ships.map((ship: string) => {
60+ return addAlly(normalizeId(ship));
61+ }));
62+ results = results.filter(r => r.status === 'fulfilled').map((r) => {
63+ return (r as PromiseFulfilledResult<any>).value;
64+ });
65+ return results;
66+}
67+
68+export async function delAllies(ships: string[], retry: number = 0) {
69+ let results = await Promise.allSettled(ships.map((ship: string) => {
70+ return delAlly(normalizeId(ship));
71+ }));
72+ results = results.filter(r => r.status === 'fulfilled').map((r) => {
73+ return (r as PromiseFulfilledResult<any>).value;
74+ });
75+ return results;
76+}
77+
78+export function processAllies(rawPals: any): Apps {
79+ let allies = rawPals.ini;
80+ let paths = Object.values(allies).flat() as string[];
81+ let set = new Set(paths);
82+ return {
83+ allies,
84+ set,
85+ };
86+}
87+
88+export type AllyUpdate = AllyUpdateIni | AllyUpdateAdd | AllyUpdateDel | AllyUpdateNew;
89+
90+export interface AllyUpdateIni {
91+ ini: {
92+ [ship: string]: string[];
93+ }
94+}
95+
96+export interface AllyUpdateNew {
97+ new: {
98+ ship: string;
99+ alliance: string[];
100+ }
101+}
102+
103+export interface AllyUpdateAdd {
104+ add: string;
105+}
106+
107+export interface AllyUpdateDel {
108+ del: string;
109+}
110+
111+export function handleAllyUpdateIni(json: AllyUpdate, apps: Apps): Apps {
112+ const data = _get(json, 'ini', false);
113+ if (data) {
114+ apps = processAllies(json)
115+ }
116+ return apps;
117+};
118+
119+export function handleAllyUpdateNew(json: AllyUpdate, apps: Apps): Apps {
120+ const data = _get(json, 'new', false);
121+ if (data) {
122+ apps = processAllies({
123+ ini: {
124+ ...apps.allies,
125+ [data.ship]: data.alliance,
126+ }
127+ });
128+ }
129+ return apps;
130+};
131+
132+export function handleAllyUpdateAdd(json: AllyUpdate, apps: Apps): Apps {
133+ const data = _get(json, 'add', false);
134+ if (data) {
135+ apps = processAllies({
136+ ini: {
137+ ...apps.allies,
138+ [data]: [],
139+ }
140+ });
141+ }
142+ return apps;
143+};
144+
145+export function handleAllyUpdateDel(json: AllyUpdate, apps: Apps): Apps {
146+ const data = _get(json, 'del', false);
147+ if (data && (data in apps.allies)) {
148+ delete apps.allies[data];
149+ apps = processAllies({
150+ ini: apps.allies,
151+ });
152+ }
153+ return apps;
154+};
155+
156+export function handleAllyUpdateEvent(updateApps) {
157+ return (allyUpdate: AllyUpdate) => {
158+ console.log(`received ally-update: ${Object.keys(allyUpdate)}`);
159+ const reducers = [handleAllyUpdateIni, handleAllyUpdateNew, handleAllyUpdateAdd, handleAllyUpdateDel];
160+ const reducer = compose(reducers.map(r => sta => r(allyUpdate, sta)));
161+ updateApps(reducer);
162+ };
163+}
164+
165+export function subscribeToApps(e: (data: any) => void): Promise<any> {
166+ return api.subscribe({
167+ app: 'treaty',
168+ path: '/allies',
169+ event: handleAllyUpdateEvent(e),
170+ err: () => {
171+ console.error(`Subscription to treaty/all just got "err"`);
172+ },
173+ quit: () => {
174+ console.error(`Subscription to treaty/all just got "quit"`);
175+ }
176+ })
177+}
ui/src/lib/colors.tsnew file+17
@@ -0,0 +1,17 @@
1+export const ORANGE_GOLD = '#f7d392';
2+export const GOLD = '#fcd34d';
3+export const GOLDT2 = '#fddc71';
4+export const GOLDS1 = '#e3be45';
5+export const GOLDS2 = '#caa93e';
6+export const WHITE = '#FFF';
7+export const LGREY = '#BBB';
8+export const DGREY = '#777';
9+export const DDGREY = '#555';
10+export const BLUE = '#1792c7';
11+export const GREEN = '#17c76a';
12+export const YOU_ARE_HERE = '#802';
13+export const YOU = '#b01';
14+export const YOUR_STAR = '#d71';
15+export const YOUR_GAL = '#fb1';
16+export const SUB_PICKED = LGREY;
17+export const NOT_PICKED = DDGREY;
ui/src/lib/graph.tsnew file+422
@@ -0,0 +1,422 @@
1+import Graph from 'graphology';
2+import * as colors from './colors';
3+
4+const example = '[0[3216291:t;9219219;4608:s[156546;1612196;219621:~;321965149:4608;]1280[4289594624;4278191360;426672256;4260168960;4260824320;4246668544;4246144256;4253746432;4235396352;4230415616;4225697024;4227007744;4219667712;4222616832;4224648448;4214490368;4215997696;4219143424;4237296896;]]]'
5+
6+export interface ChartPoint {
7+ patp: string,
8+ dominion: ('l1' | 'l2' | 'spawn'),
9+ picked: boolean,
10+ pickedChildren?: boolean,
11+ pickedSponsees?: boolean,
12+ sponsor?: string,
13+ parent?: string,
14+ children?: ChartPoint[],
15+ sponsees?: ChartPoint[],
16+}
17+
18+interface ChartSubs {
19+ children: ChartPoint[],
20+ sponsees: ChartPoint[],
21+}
22+
23+class ChartDataParser {
24+ filter: Set<string>;
25+
26+ constructor(filter: Set<string>) {
27+ this.filter = filter;
28+ }
29+
30+ parseMetaString(metaString: string): ChartPoint {
31+ let idString, subMetaString = '';
32+ const colIndex = metaString.indexOf(':');
33+ if (colIndex === -1) {
34+ idString = metaString;
35+ } else {
36+ idString = metaString.slice(0, colIndex);
37+ subMetaString = metaString.slice(colIndex + 1);
38+ }
39+ const patp = '~' + idString;
40+ const meta: ChartPoint = {
41+ patp,
42+ picked: !this.filter ? true : (this.filter.has(patp)),
43+ dominion: 'l1',
44+ }
45+ if (subMetaString) {
46+ const firstChar = subMetaString[0];
47+ if (firstChar === 't') {
48+ meta.dominion = 'l2';
49+ subMetaString = subMetaString.slice(1);
50+ } else if (firstChar === 's') {
51+ meta.dominion = 'spawn';
52+ subMetaString = subMetaString.slice(1);
53+ }
54+ if (subMetaString) {
55+ if (subMetaString[0] === '~') {
56+ meta.sponsor = null;
57+ } else {
58+ let [sponsor, parent] = subMetaString.slice(1).split('!');
59+ meta.sponsor = '~' + sponsor;
60+ meta.parent = '~' + parent;
61+ }
62+ }
63+ }
64+ return meta;
65+ }
66+
67+ extractMeta(data: string): [ChartPoint, string] {
68+ const index = data.search(/[[;]/);
69+ if (index === -1) {
70+ throw new Error(`No point meta present in ${data}`);
71+ }
72+ const metaString = data.slice(0, index);
73+ data = data.slice(index);
74+ return [this.parseMetaString(metaString), data];
75+ }
76+
77+ extractChildren(data: string, sup: string = undefined): [ChartSubs, string] {
78+ if (data[0] === ';')
79+ return [{
80+ children: [],
81+ sponsees: [],
82+ }, data.slice(1)];
83+ if (data[0] !== '[') throw new Error('Invalid children in data: ' + data.slice(0, 20));
84+
85+ let endFound = false;
86+ let counter = 0;
87+ data = data.slice(1);
88+ const children = [];
89+ const sponsees = [];
90+ while (!endFound && data.length && counter < 20000) {
91+ if (data[0] === ']') {
92+ endFound = true;
93+ data = data.slice(1);
94+ } else {
95+ let sub;
96+ [sub, data] = this.extractChartPoint(data);
97+ if (!sub.parent || sub.parent === sup)
98+ children.push(sub);
99+ if (sub.sponsor === undefined || sub.sponsor === sup)
100+ sponsees.push(sub);
101+ }
102+ counter++;
103+ }
104+ return [{ children, sponsees }, data];
105+ }
106+
107+ extractChartPoint(data: string): [ChartPoint, string] {
108+ let point, subs;
109+ [point, data] = this.extractMeta(data);
110+ [subs, data] = this.extractChildren(data, point.patp);
111+ point = {
112+ ...point,
113+ ...subs,
114+ };
115+ return [point, data];
116+ }
117+
118+ linkChartData(points: ChartPoint[], store: any): ChartPoint[] {
119+ if (!points) return points;
120+ return points.map(point => {
121+ if (!store[point.patp]) return point;
122+ if (!point.children?.length) {
123+ point.children = this.linkChartData(store[point.patp].children, store);
124+ let picked = point.children.filter(child => child.picked || child.pickedChildren);
125+ point.pickedChildren = picked.length > 0;
126+ }
127+ if (!point.sponsees?.length) {
128+ point.sponsees = this.linkChartData(store[point.patp].sponsees, store);
129+ let picked = point.sponsees.filter(child => child.picked || child.pickedSponsees);
130+ point.pickedSponsees = picked.length > 0;
131+ }
132+ return point;
133+ });
134+ }
135+
136+ parse(str: string): ChartSubs {
137+ if (!str) return { children: [], sponsees: [] };
138+ let [{ children: baseChildren }, _] = this.extractChildren(str, undefined);
139+ let store = {};
140+ baseChildren.forEach(child => {
141+ store[child.patp] = {
142+ children: child.children,
143+ sponsees: child.sponsees,
144+ };
145+ });
146+ let base: ChartPoint = {
147+ patp: '~base',
148+ dominion: 'l1',
149+ picked: true,
150+ }
151+
152+ let points = this.linkChartData([base], store);
153+ return {
154+ children: points[0].children || [],
155+ sponsees: points[0].sponsees || [],
156+ }
157+ }
158+}
159+
160+export function parseChartData(data: string, filter: Set<string>): ChartSubs {
161+ let start = Date.now();
162+ let parser = new ChartDataParser(filter);
163+ let parsed = parser.parse(data);
164+ // console.log(`finished parsing chart data to js object in ${Date.now() - start} ms`);
165+ return parsed;
166+}
167+
168+const circle = 2 * Math.PI;
169+function chordLength(radius, radians) {
170+ return 2 * radius * Math.sin(radians / 2);
171+}
172+function distancePolar(r1, th1, r2, th2) {
173+ return Math.sqrt((r1 * r1) + (r2 * r2) - (2 * r1 * r2 * Math.cos(th2 - th1)));
174+}
175+export function arrangePointsInSpirals(
176+ numPoints,
177+ {
178+ maxDistance, minDistance,
179+ startX = 0, startY = 0,
180+ maxArms = 6, maxPerArm = 45,
181+ makeSingleArmCircle = false,
182+ }): { coords: any[], numArms: number, minDistanceBetween: number } {
183+ const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
184+ let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
185+ const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
186+ const singleArmCircle = makeSingleArmCircle && numArms === 1;
187+ const numPerArm = numPoints / numArms;
188+ let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
189+ if (singleArmCircle) {
190+ minDistance = maxDistance;
191+ armThetaRange = circle;
192+ }
193+ const distanceRange = maxDistance - minDistance;
194+ const pointWeight = 1 / numPerArm;
195+ let minDistanceBetween = distancePolar(minDistance, 0,
196+ minDistance + distanceRange * pointWeight, armThetaRange * pointWeight);
197+ if (numPoints <= 3) minDistanceBetween = maxDistance * 0.75;
198+ const coords = [];
199+ let lastArm = null;
200+ let indexInArm = null;
201+ for (let i = 0; i < numPoints; i++) {
202+ const overallProgress = i / numPoints;
203+ const arm = Math.floor(numArms * overallProgress);
204+ if (lastArm === arm) {
205+ indexInArm++;
206+ } else {
207+ indexInArm = 0;
208+ }
209+ const interArmProgress = arm / numArms;
210+ const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
211+ const numInThisArm = beginningOfNextArm - (i - indexInArm);
212+ const intraArmProgress = indexInArm / numInThisArm;
213+ const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
214+ let line = (minDistance + distanceRange * intraArmProgress);
215+ let x = 0 + startX + line * Math.cos(theta);
216+ let y = 0 + startY + line * Math.sin(theta);
217+ coords.push({
218+ coords: [x, y],
219+ arm: singleArmCircle ? i : arm,
220+ });
221+ lastArm = arm;
222+ };
223+ return {
224+ coords,
225+ numArms,
226+ minDistanceBetween,
227+ };
228+}
229+type childKey = ('sponsees' | 'children');
230+export function populateGraph(g: Graph, chartSubs: ChartSubs, subsKey: childKey, callback?): Graph {
231+ let pickedSubsKey = subsKey == 'children' ? 'pickedChildren' : 'pickedSponsees';
232+ function pickedColor(point: ChartPoint, normal: string) {
233+ return point.picked ? normal : (point[pickedSubsKey] ? colors.SUB_PICKED : colors.NOT_PICKED);
234+ }
235+ function pickedArmColor(point: ChartPoint, normal: string) {
236+ return point[pickedSubsKey] ? normal : colors.NOT_PICKED;
237+ }
238+ let points = chartSubs[subsKey];
239+ if (!(points instanceof Array)) return;
240+ if (!g) {
241+ g = new Graph();
242+ } else {
243+ g.clear();
244+ }
245+
246+ let sponsorEdges = [];
247+
248+ let offsetX = 0;
249+ let offsetY = 0;
250+
251+ let a = 120000000;
252+ let b = 9000000;
253+ let theta = 0;
254+
255+ points.forEach((galaxy, gi) => {
256+ offsetX = 0 + (a + b * theta) * Math.cos(theta);
257+ offsetY = 0 + (a + b * theta) * Math.sin(theta);
258+
259+ theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
260+ const gPatp = galaxy.patp;
261+ const itsYou = gPatp === '~' + window.ship;
262+ g.addNode(gPatp, {
263+ label: gPatp,
264+ size: 3,
265+ color: itsYou ? colors.YOU : pickedColor(galaxy, colors.WHITE),
266+ x: offsetX,
267+ y: offsetY,
268+ zIndex: 10,
269+ });
270+
271+ const stars = galaxy[subsKey] || [];
272+ const numStars = stars.length;
273+ const maxStarDistance = 60000 * 128;
274+ const minStarDistance = maxStarDistance / 4;
275+ const starSize = 1.2 / Math.pow(numStars, 1 / 7);
276+ const {
277+ coords: starCoords,
278+ numArms: numStarArms,
279+ minDistanceBetween: minDistanceBetweenStars,
280+ } = arrangePointsInSpirals(numStars,
281+ {
282+ maxDistance: maxStarDistance,
283+ minDistance: minStarDistance,
284+ startX: offsetX,
285+ startY: offsetY,
286+ makeSingleArmCircle: true,
287+ });
288+ let lastStarArm = null;
289+ let lastStarPatp = null;
290+ let GSarmColor = pickedArmColor(galaxy, colors.LGREY)
291+ stars.forEach((star, si) => {
292+ const sPatp = star.patp;
293+
294+ const [starX, starY] = starCoords[si].coords;
295+ const arm = starCoords[si].arm;
296+
297+ const itsYou = sPatp === '~' + window.ship;
298+ if (itsYou) {
299+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
300+ }
301+ try {
302+ g.addNode(sPatp, {
303+ label: sPatp,
304+ size: starSize,
305+ color: itsYou ? colors.YOU : pickedColor(star, colors.GOLDT2),
306+ x: starX,
307+ y: starY,
308+ zIndex: 8,
309+ });
310+ if (lastStarArm == arm) {
311+ g.addEdge(sPatp, lastStarPatp, {
312+ color: GSarmColor,
313+ size: 0.1,
314+ zIndex: 7,
315+ });
316+ } else {
317+ g.addEdge(sPatp, gPatp, {
318+ color: GSarmColor,
319+ size: 0.2,
320+ zIndex: 9,
321+ });
322+ }
323+ lastStarArm = arm;
324+ lastStarPatp = sPatp;
325+ if (star.sponsor) {
326+ sponsorEdges.push([sPatp, star.sponsor, {
327+ color: colors.GREEN,
328+ size: 0.2,
329+ zIndex: 11,
330+ }]);
331+ }
332+
333+ const planets = star[subsKey] || [];
334+ const numPlanets = planets.length;
335+ // const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
336+ const maxPlanetDistance = minDistanceBetweenStars * 0.45;
337+ const minPlanetDistance = maxPlanetDistance / 4;
338+ const {
339+ coords: planetCoords,
340+ numArms: numPlanetArms,
341+ minDistanceBetween: minDistanceBetweenPlanets,
342+ } = arrangePointsInSpirals(numPlanets,
343+ {
344+ maxDistance: maxPlanetDistance,
345+ minDistance: minPlanetDistance,
346+ startX: starX,
347+ startY: starY,
348+ maxArms: 30,
349+ makeSingleArmCircle: true,
350+ });
351+ let lastPlanetArm = null;
352+ let lastPlanetPatp = null;
353+ let SParmColor = pickedArmColor(star, colors.DGREY)
354+ planets.forEach((planet, pi) => {
355+ const pPatp = planet.patp;
356+ const [planetX, planetY] = planetCoords[pi].coords;
357+ const arm = planetCoords[pi].arm;
358+ const planetSize = Math.sqrt(Math.min(minPlanetDistance, minDistanceBetweenPlanets)) / 1300;
359+ const planetEdgeSize = planetSize / 3;
360+ const itsYou = pPatp === '~' + window.ship;
361+ if (itsYou) {
362+ g.setNodeAttribute(sPatp, 'color', colors.YOUR_STAR);
363+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
364+ }
365+ try {
366+ g.addNode(pPatp, {
367+ label: pPatp,
368+ size: planetSize,
369+ color: itsYou ? colors.YOU : pickedColor(planet, planet.dominion == 'l2' ? colors.GREEN : colors.BLUE),
370+ x: planetX,
371+ y: planetY,
372+ zIndex: 6,
373+ });
374+ if (lastPlanetArm == arm) {
375+ g.addEdge(pPatp, lastPlanetPatp, {
376+ color: SParmColor,
377+ size: planetEdgeSize,
378+ zIndex: 5,
379+ });
380+ } else {
381+ if (numPlanetArms == 1) {
382+ g.addEdge(pPatp, sPatp, {
383+ color: SParmColor,
384+ size: planetEdgeSize,
385+ zIndex: 5,
386+ });
387+ }
388+ }
389+ if (planet.sponsor) {
390+ sponsorEdges.push([pPatp, planet.sponsor, {
391+ color: colors.GREEN,
392+ size: planetEdgeSize,
393+ zIndex: 11,
394+ }]);
395+ }
396+ lastPlanetArm = arm;
397+ lastPlanetPatp = pPatp;
398+ } catch (e) {
399+ let msg = `Couldn't add ${pPatp} to the graph`;
400+ console.error(msg, e);
401+ alertMe(msg + '\n' + e.message);
402+ }
403+ });
404+ } catch (e) {
405+ let msg = `Couldn't add ${sPatp} to the graph`;
406+ console.error(msg, e);
407+ alertMe(msg + '\n' + e.message);
408+ }
409+ });
410+ });
411+ sponsorEdges.forEach(([point, sponsor, attrs]) => {
412+ // g.addEdge(point, sponsor, attrs);
413+ });
414+ if (callback) {
415+ setTimeout(callback, 0);
416+ }
417+ return g;
418+}
419+
420+function alertMe(msg: string) {
421+ alert('Encountered an error, please tell ~midlev-mindyr: ' + msg);
422+}
ui/src/lib/id.ts+101−14
@@ -1,5 +1,10 @@
1−import type { Patp } from '@urbit/api';
2−import { listStrings } from './utils';
1+import BN from 'bn.js';
2+import type { Patp } from '@urbit/api'
3+import { hex2patp, patp2hex, patp as any2patp, sein } from 'urbit-ob';
4+import { isStringNaN, listStrings } from './utils';
5+
6+// 340282366920938463463374607431768211455
7+const largestValidAzNumber = (new BN(256)).pow(new BN(16)).sub(new BN(1));
38
49type Problem = string;
510
@@ -7,9 +12,11 @@ class SearchAnalysis {
712 queryProblems: Problem[] = [];
813 private _search: string = '';
914 patpProblems: Problem[] = [];
15+ isNumber: boolean = false;
16+ azNumber: BN;
1017
11− constructor(search: string) {
12− this.search = search;
18+ constructor(search?: string) {
19+ this.search = search || '';
1320 }
1421
1522 get searchIsSigged() {
@@ -23,8 +30,12 @@ class SearchAnalysis {
2330 return convertSearchTextToRegex(search);
2431 }
2532
33+ get azHex(): string {
34+ return hex2patp(this.azNumber.toString('hex'));
35+ }
36+
2637 get patp(): Patp {
27− return normalizeId(this._search);
38+ return this.isNumber ? this.azHex : normalizeId(this._search);
2839 }
2940
3041 get queryIsValid(): boolean {
@@ -85,6 +96,7 @@ class SearchAnalysis {
8596 resetAnalysis() {
8697 this.queryProblems = [];
8798 this.patpProblems = [];
99+ this.isNumber = false;
88100 }
89101
90102 // mergeAnalysis(analysis: SearchAnalysis): void {
@@ -96,6 +108,18 @@ class SearchAnalysis {
96108
97109 analyze(): void {
98110 this.resetAnalysis();
111+ if (!isStringNaN(this._search)) {
112+ let num = new BN(this._search);
113+ if (num.gt(largestValidAzNumber)) {
114+ this.pushPatpProblem(azNumberTooBig);
115+ } else if (num.negative === 1) {
116+ this.pushPatpProblem(azNumberNegative);
117+ } else {
118+ this.isNumber = true;
119+ this.azNumber = num;
120+ }
121+ return;
122+ }
99123 let search = normalizeIdAndDesig(this._search);
100124
101125 // Validate patp is not empty
@@ -299,6 +323,8 @@ const moonFiller = `${wildWord}-${wildWord}`;
299323const cometFiller = `-${wildWord}-${wildWord}-${wildWord}--${wildWord}-${wildWord}-${wildWord}-`;
300324
301325// Error Messages
326+const azNumberNegative = `The number you entered is negative. Urbit IDs must be non-negative integers.`;
327+const azNumberTooBig = `The number you entered is too large. The largest possible Urbit ID is 256^16 - 1 (${largestValidAzNumber.toString()}).`;
302328const emptyPatpMessage = "ID cannot be empty.";
303329const improperSigMessage = "There should only be one ~ in an ID, at the beginning.";
304330const cannotInferMoonPatpMessage = "Please enter the full moon ID, rather than using the '^' abbreviation.";
@@ -350,6 +376,7 @@ function invalidFragmentsMessage(fragments: string[]) {
350376 return `${wordListString} cannot match any prefix/suffix combinations.`;
351377}
352378
379+
353380function sig(str: string): string {
354381 if (str[0] !== '~') {
355382 return '~' + str;
@@ -369,33 +396,74 @@ function sanitizeSearch(search: string): string {
369396}
370397
371398function normalizeId(patp: string): string {
372− return sig(sanitizeSearch(patp));
399+ return unpadPatp(sig(sanitizeSearch(patp)));
373400}
374401
375402function normalizeIdAndDesig(patp: string): string {
376− return desig(sanitizeSearch(patp));
403+ return desig(normalizeId(patp));
377404}
378405
379−function splitIdIntoWords(str: string): string[] {
380− return str.split(/(?:--|[-_^])/);
406+function unpadPatp(patp: string): string {
407+ try {
408+ return hex2patp(patp2hex(patp));
409+ } catch {
410+ return patp;
411+ }
412+}
413+
414+// Adapted from cite() in urbit/pkg/npm/api/lib/lib.ts
415+// to handle those rare comets which start with dozzod
416+function cite(ship: string) {
417+ let patp = ship,
418+ shortened = '';
419+ if (patp === null || patp === '') {
420+ return null;
421+ }
422+ if (patp.startsWith('~')) {
423+ patp = patp.substr(1);
424+ }
425+ // comet
426+ if (patp.length >= 35) {
427+ shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(-6);
428+ return shortened;
429+ }
430+ // moon
431+ if (patp.length >= 20) {
432+ shortened = '~' + patp.slice(-13, -7) + '^' + patp.slice(-6);
433+ return shortened;
434+ }
435+ return `~${patp}`;
436+}
437+
438+export function splitIdIntoWords(str: string): string[] {
439+ return desig(str).split(/(?:--|[-_^])/);
440+}
441+
442+export function splitIdIntoSyls(str: string): string[] {
443+ return splitIdIntoWords(str).map(word => [word.slice(0,3), word.slice(3,6)]).flat();
381444}
382445
383446function padWithWild(search: string) {
384447 let words = search.split(/[_^]/);
385− words[0] = '*'.repeat(6 - words[0].length) + words[0];
386− words[1] += '*'.repeat(6 - words[1].length);
448+ words[0] = '*'.repeat(Math.max(0, 6 - words[0].length)) + words[0];
449+ words[1] += '*'.repeat(Math.max(0, 6 - words[1].length));
387450 return words.join(search.match(/([_^])/)[1]);
388451}
389452
390453function convertSearchTextToRegex(str: string, { checkStart = false, checkEnd = false } = {}): RegExp {
391454 str = str.replace(/\*/g, '.');
455+ if (str[0] === '~') checkStart = true;
392456 if (checkStart) {
393457 str = '^' + str;
394458 }
395459 if (checkEnd) {
396460 str = str + '$';
397461 }
398− return new RegExp(str);
462+ try {
463+ return new RegExp(str);
464+ } catch {
465+ return /a^/; // matches nothing
466+ }
399467}
400468
401469function expandAbbreviatedId(patp: string): string {
@@ -429,16 +497,35 @@ function suffixSearchValid(search: string, options = {}) {
429497 return syllableSearchValid(search, suffixes, options);
430498}
431499
432−
433−
434500function analyzeSearch(search: string): SearchAnalysis {
435501 return new SearchAnalysis(search);
436502}
437503
504+function isMoonLength(patp: string): boolean {
505+ return patp.length > 14 && patp.length <= 28;
506+}
507+
508+function isCometLength(patp: string): boolean {
509+ return patp.length > 28;
510+}
511+
512+function filterIdsForChildMoons(ids: string[], parentId: string): string[] {
513+ return ids.filter((peerId) => {
514+ if (!isMoonLength(peerId)) return false;
515+ return sein(peerId) === normalizeId(parentId);
516+ })
517+}
518+
438519export {
520+ prefixes,
521+ suffixes,
439522 SearchAnalysis,
440523 normalizeId,
441524 normalizeIdAndDesig,
525+ cite,
442526 convertSearchTextToRegex,
443527 analyzeSearch,
528+ isMoonLength,
529+ isCometLength,
530+ filterIdsForChildMoons,
444531};
ui/src/lib/link.ts+15−2
@@ -1,3 +1,16 @@
1−export function linkToShip(patp: string): string {
2− return `/ship/${patp}`;
1+import { buildQuerystring } from './utils';
2+
3+export function linkToShip(patp: string, queryParams = {}): string {
4+ return `/ship/${encodeURIComponent(patp)}` + buildQuerystring(queryParams);
5+}
6+
7+export function linkToSearch(search: string, queryParams = {}): string {
8+ return `/search/${encodeURIComponent(search)}` + buildQuerystring(queryParams);
9+}
10+
11+export function linkToChart(patp: string): string {
12+ if (patp) {
13+ return `/chart/${encodeURIComponent(patp)}`;
14+ }
15+ return `/chart`;
316}
ui/src/lib/pals.tsnew file+91
@@ -0,0 +1,91 @@
1+import type { Patp } from '@urbit/api';
2+// import uniq from 'lodash/without';
3+// import without from 'lodash/without';
4+import type { Pals, PalList, PalNames, Pal, PalInfo, PalStatus } from 'types/store';
5+import store from 'stores/store';
6+import { api } from 'lib/api';
7+import { normalizeId, normalizeIdAndDesig } from 'lib/id';
8+
9+export function getPals() {
10+ return api.scry<any>({ app: 'pals', path: '/json' }).then(processPals);
11+}
12+
13+function palCommand(cmd: string, ship: string, tags: string[] = []) {
14+ return {
15+ [cmd]: {
16+ ship: normalizeIdAndDesig(ship),
17+ in: tags,
18+ },
19+ };
20+}
21+
22+export async function sendPalCommand(cmd: string, ship: string, tags: string[] = []) {
23+ let result = await api.poke({
24+ app: 'pals',
25+ mark: 'pals-command',
26+ json: palCommand(cmd, ship, tags),
27+ });
28+ store.refreshPals();
29+ return result;
30+}
31+
32+export function addPal(patp: string, tags: string[] = []) {
33+ return sendPalCommand('meet', patp, tags);
34+}
35+
36+export function removePal(patp: string) {
37+ return sendPalCommand('part', patp, []);
38+}
39+
40+export function tagPal(patp: string, tag: (string | string[])) {
41+ if (typeof tag == 'string') tag = [tag];
42+ return addPal(patp, tag);
43+}
44+
45+export function untagPal(patp: string, tag: (string | string[])) {
46+ if (typeof tag == 'string') tag = [tag];
47+ return sendPalCommand('part', patp, tag);
48+}
49+
50+export function processPals(rawPals: any): Pals {
51+ let incoming: PalNames = {};
52+ let outgoing: PalList = {};
53+ let mutuals: PalList = {};
54+
55+ Object.entries(rawPals.incoming).forEach(([patp, bool]) => {
56+ incoming[normalizeId(patp)] = bool as boolean;
57+ });
58+ (Object.entries(rawPals.outgoing) as [string, any][]).forEach(([patp, { lists, ack }]) => {
59+ patp = normalizeId(patp);
60+ outgoing[patp] = {
61+ tags: lists as string[],
62+ ack: ack as boolean,
63+ };
64+ if (incoming[patp]) {
65+ mutuals[patp] = outgoing[patp];
66+ }
67+ });
68+
69+ return {
70+ incoming,
71+ outgoing,
72+ mutuals,
73+ };
74+}
75+
76+export function getPalStatus(pals: Pals, patp: Patp): PalStatus {
77+ if (pals.mutuals[patp]) return 'mutual';
78+ if (pals.outgoing[patp]) return 'outgoing';
79+ if (pals.incoming[patp]) return 'incoming';
80+ return 'unconnected';
81+}
82+
83+export function getPal(pals: Pals, patp: Patp): PalInfo {
84+ return {
85+ patp,
86+ status: getPalStatus(pals, patp),
87+ tags: [],
88+ ack: null,
89+ ...pals.outgoing[patp],
90+ };
91+}
ui/src/lib/ship.tsnew file+114
@@ -0,0 +1,114 @@
1+import { clan, sein } from 'urbit-ob';
2+
3+export const spawnStatusOptions = {
4+ unspawned: {
5+ text: 'unspawned',
6+ tooltip: 'This point has not yet been spawned by its parent',
7+ },
8+ locked: {
9+ text: 'locked',
10+ tooltip: 'This point is currently locked in a Linear Release Contract',
11+ },
12+ spawned: {
13+ text: 'spawned',
14+ tooltip: `This point has been spawned`,
15+ },
16+ spawnedNoKeys: {
17+ text: 'spawned (no keys set)',
18+ tooltip: 'This point has been spawned, but no keys have been set',
19+ },
20+};
21+export const layerOptions = {
22+ l1: {
23+ text: 'on layer 1',
24+ tooltip: 'This point is secured as an NFT on the Ethereum blockchain',
25+ },
26+ l2: {
27+ text: 'on layer 2',
28+ tooltip: `This point is secured by Urbit's "Layer 2" solution`,
29+ },
30+ spawn: {
31+ text: 'spawns on layer 2',
32+ tooltip: 'This point lives on layer 1, but it spawns new points on layer 2',
33+ },
34+};
35+
36+export function generateParentChain(patp: string): any[] {
37+ let parents = [patp];
38+ let galaxyFound = false;
39+ let count = 10;
40+ while (!galaxyFound && count > 0) {
41+ count--;
42+ if (count == 0) {
43+ throw new Error("You've got an infinite loop in building the parent chain");
44+ }
45+ let firstItem = parents[0];
46+ if (clan(firstItem) == 'galaxy') {
47+ galaxyFound = true;
48+ } else {
49+ parents.unshift(sein(firstItem))
50+ }
51+ }
52+ parents.pop();
53+ return parents;
54+}
55+
56+export function processPointInfo(rawPointInfo: any): any {
57+ let pointInfo: any = {};
58+ let spawnStatus: any,
59+ spawnedUnlocked: boolean = false,
60+ layer: any,
61+ life: number,
62+ rift: number,
63+ spawnedCount: number,
64+ sponsorChain: any[];
65+ let keysSet = false;
66+ if (!rawPointInfo) {
67+ // request pending
68+ } else {
69+ // defaults in case no info found
70+ spawnStatus = spawnStatusOptions.unspawned;
71+ life = 0;
72+ rift = 0;
73+
74+ if (rawPointInfo.point) {
75+ const { point } = rawPointInfo;
76+ const {
77+ 'probable-dominion': dominion,
78+ npoint
79+ } = point;
80+ layer = layerOptions[dominion];
81+ spawnedCount = point['spa-count'];
82+ sponsorChain = point['sponsor-chain'].reverse();
83+
84+ if (npoint) {
85+ life = npoint.net.keys.life;
86+ rift = npoint.net.rift;
87+ if (life > 0) {
88+ keysSet = true;
89+ }
90+ pointInfo.proxies = npoint.own;
91+ if (pointInfo.proxies.owner.address === '0x86cd9cd0992f04231751e3761de45cecea5d1801') {
92+ spawnStatus = spawnStatusOptions.locked;
93+ } else if (keysSet) {
94+ spawnStatus = spawnStatusOptions.spawned;
95+ spawnedUnlocked = true;
96+ } else {
97+ spawnStatus = spawnStatusOptions.spawnedNoKeys;
98+ spawnedUnlocked = true;
99+ }
100+ }
101+ }
102+ }
103+ return {
104+ ...pointInfo,
105+ layer,
106+ spawnStatus,
107+ spawnedUnlocked,
108+ keysSet,
109+ life,
110+ rift,
111+ spawnedCount,
112+ sponsorChain,
113+ };
114+}
ui/src/lib/sigil.tsnew file+387
@@ -0,0 +1,387 @@
1+import intersection from 'lodash/intersection';
2+import without from 'lodash/without';
3+import uniq from 'lodash/uniq';
4+import { partParents, symbolDefs } from '@johnhyde/sigil-js';
5+
6+import type { Clan, Geon, ModeDef, PartType, SymbolType } from 'types/sigil';
7+import { PART_TYPES } from 'types/sigil';
8+import { prefixes, suffixes } from 'lib/id';
9+
10+export function sigilScalingFunction(size: number): number {
11+ return 3.5 * Math.sqrt(51 / size);
12+}
13+
14+export function crowdedSigilScalingFunction(size: number): number {
15+ return 2.8 * Math.sqrt(51 / size);
16+}
17+
18+export class SigilQuery {
19+ clan: Clan;
20+ symbols: SymbolQuery[];
21+ allowFictional: boolean = false;
22+
23+ constructor(clan: Clan = 'planet', symbols = []) {
24+ this.clan = clan;
25+ this.symbols = symbols;
26+ this.padSymbols();
27+ }
28+
29+ padSymbols() {
30+ if (this.symbols.length === 1) {
31+ this.symbols.unshift(new SymbolQuery(this));
32+ }
33+ while (this.symbols.length < 4) {
34+ this.symbols.push(new SymbolQuery(this));
35+ }
36+ this.symbols = this.symbols.slice(0, 4);
37+ this.symbols.forEach((symbol, i) => {
38+ if (i % 2 === 0) {
39+ symbol.symbolType = 'prefix';
40+ // symbol.components = ['gci', 'lhf'];
41+ } else {
42+ symbol.symbolType = 'suffix';
43+ // symbol.components = ['gsq', 'atrf4'];
44+ }
45+ });
46+ this.symbols = this.symbols;
47+ }
48+
49+ clearSymbols() {
50+ this.symbols = [];
51+ this.padSymbols();
52+ return this;
53+ }
54+
55+ get activeSymbols() {
56+ if (this.clan === 'galaxy')
57+ return [this.symbols[1]];
58+ if (this.clan === 'star')
59+ return this.symbols.slice(0, 2);
60+ return this.symbols;
61+ }
62+
63+ get isPlausible() {
64+ return this.activeSymbols.every(symbol => symbol.isPlausible);
65+ }
66+
67+ get queryParts(): string[][] {
68+ return this.activeSymbols.map(symbol => symbol.components);
69+ }
70+
71+ get querySyls(): string[][] {
72+ return this.activeSymbols.map(symbol => symbol.plausibleSyllables);
73+ }
74+
75+ get queryInts(): number[][] {
76+ let ints = this.activeSymbols.map(symbol => symbol.plausibleInts);
77+ if (ints.length > 1) {
78+ ints[0] = without(ints[0], 0);
79+ }
80+ return ints;
81+ }
82+
83+ get isDefinitive() {
84+ return this.querySyls.every(syls => syls.length === 1);
85+ }
86+
87+ get isNotEmpty() {
88+ return this.activeSymbols.some(symbol => symbol.components.length > 0);
89+ }
90+
91+ get isWorthSearching() {
92+ if (!this.isNotEmpty) return false; // 🙃
93+ if (!this.isPlausible) return false;
94+ return true;
95+ }
96+
97+ get string(): string {
98+ return this.queryInts.map((int) => int.length == 256 ? 'any' : int.join('.')).join('_');
99+ }
100+
101+ get urlString(): string {
102+ return this.queryParts.map(parts => parts.join('.')).join('_');
103+ }
104+
105+ set urlString(str: string) {
106+ if (!str) return;
107+ const queryParts = str.split('_').map(sym => sym.split('.').filter(part => part !== ''));
108+ switch (queryParts.length) {
109+ case 1:
110+ this.clan = 'galaxy';
111+ break;
112+ case 2:
113+ this.clan = 'star';
114+ break;
115+ case 4:
116+ this.clan = 'planet';
117+ break;
118+ default:
119+ return;
120+ }
121+ let symbols;
122+ try {
123+ symbols = queryParts.map((parts) => {
124+ let symbol = new SymbolQuery(this, parts);
125+ if (symbol.components.length !== parts.length) {
126+ throw new Error(`Invalid component ids: ${without(parts, ...symbol.components)}`);
127+ }
128+ });
129+ } catch (e) {
130+ console.log("Couldn't load in sigil query text:", e.message);
131+ this.clan = 'planet';
132+ symbols = [];
133+ }
134+ this.padSymbols();
135+ }
136+
137+ get patp(): string {
138+ if (!this.isDefinitive) return '';
139+ const querySyls = this.querySyls.flat();
140+ let patp = '~' + querySyls[0];
141+ if (querySyls.length > 1) {
142+ patp += querySyls[1];
143+ if (querySyls.length > 2) {
144+ patp += '-' + querySyls[2] + querySyls[3];
145+ }
146+ }
147+ return patp;
148+ }
149+
150+ setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
151+ if (activeSymbolIndex >= this.activeSymbols.length)
152+ throw new Error(`Symbol index ${activeSymbolIndex} out of range for clan: ${this.clan}`);
153+ this.activeSymbols[activeSymbolIndex] = symbol;
154+ }
155+}
156+
157+const equivalentParts = {
158+ lfhb: 'lfhbm',
159+ lfhbm: 'lfhb',
160+ atlf2: 'atlf2l',
161+ atlf2l: 'atlf2',
162+ atlf4: 'atlf4l',
163+ atlf4l: 'atlf4',
164+ atlf6: 'atlf6l',
165+ atlf6l: 'atlf6',
166+}
167+
168+function addEquivalentParts(parts: string[]): string[] {
169+ const moreParts = parts.map(partId => equivalentParts[partId]).filter(part => part);
170+ return [...parts, ...moreParts];
171+}
172+
173+function getSyllablesBySymbolType(symbolType: SymbolType): string[] {
174+ if (symbolType === 'prefix') {
175+ return prefixes;
176+ } else if (symbolType === 'suffix') {
177+ return suffixes;
178+ } else {
179+ return [...prefixes, ...suffixes];
180+ }
181+}
182+
183+export function getPlausibleSyllables(parts: string[], symbolType: SymbolType): string[] {
184+ let plausibleSyllables = getSyllablesBySymbolType(symbolType);
185+ const possibilitiesByPart = parts.map((partId) => {
186+ let parents = partParents[partId];
187+ const otherPart = equivalentParts[partId];
188+ if (otherPart) {
189+ parents = [...parents, ...partParents[otherPart]];
190+ }
191+ return parents;
192+ });
193+ plausibleSyllables = intersection(plausibleSyllables, ...possibilitiesByPart);
194+ return plausibleSyllables;
195+}
196+
197+function convertSyllableToInt(syl: string, symbolType: SymbolType): number {
198+ let syls = getSyllablesBySymbolType(symbolType);
199+ return syls.findIndex(s => s === syl) % 256;
200+}
201+
202+export function getPlausibleNewParts(parts: string[], symbolType: SymbolType): string[] {
203+ const syllables: string[] = getPlausibleSyllables(parts, symbolType);
204+ let plausibleParts = uniq(syllables.map((syllable) => symbolDefs[syllable]).flat());
205+ plausibleParts = addEquivalentParts(plausibleParts);
206+ return without(plausibleParts, ...parts);
207+}
208+
209+export function filterPlausibleParts(parts: string[], symbolType: SymbolType, newParts: string[]): string[] {
210+ const plausibleParts = getPlausibleNewParts(parts, symbolType);
211+ return intersection(newParts, plausibleParts);
212+}
213+
214+export function arePartsPlausible(parts: string[], symbolType: SymbolType, newParts: string[]): boolean {
215+ return filterPlausibleParts(parts, symbolType, newParts).length > 0;
216+}
217+
218+function hasComplement(partId: string): boolean {
219+ const secondToLastChar = partId.slice(-2)[0];
220+ return partId[0] === 'c' && (secondToLastChar === 'm' || secondToLastChar === 'l');
221+}
222+
223+function getComplementaryCircleId(circleId: string): string {
224+ let newId = circleId.replace(/m(?=.$)/, 'l');
225+ if (newId === circleId) {
226+ newId = circleId.replace(/l(?=.$)/, 'm');
227+ }
228+ circleId = newId;
229+ circleId = circleId.replace(/f$/, 'b');
230+ if (newId === circleId) {
231+ circleId = circleId.replace(/b$/, 'f');
232+ }
233+ return circleId;
234+}
235+
236+function getPartTypeIndex(partId: string): number {
237+ return PART_TYPES.findIndex(p => p[0] === partId[0]);
238+}
239+
240+export function filterPartsByGeon(parts: string[], geon: Geon): string[] {
241+ if (['gq1', 'gbs'].includes(geon)) {
242+ parts = without(parts, 'ctr2mb', 'ctr2lf', 'atrf2', 'atra2', 'atrb2');
243+ } else {
244+ parts = without(parts, 'ctr3mf', 'ctr3lb');
245+ }
246+ if (['gq2', 'gfs'].includes(geon)) {
247+ parts = without(parts, 'ctl3mb', 'ctl3lf', 'ctl2mb', 'ctl2lf', 'atlf2', 'atla2', 'atlb2');
248+ } else {
249+ parts = without(parts, 'ctl3mf', 'ctl3lb');
250+ }
251+ if (['gq3', 'gbs'].includes(geon)) {
252+ parts = without(parts, 'cbl3mb', 'cbl3lf', 'cbl2mb', 'cbl2lf', 'ablf2', 'ablb2');
253+ } else {
254+ parts = without(parts, 'cbl3mf', 'cbl3lb');
255+ }
256+ if (['gq4', 'gfs'].includes(geon)) {
257+ parts = without(parts, 'cbr2mb', 'cbr2lf', 'abrf2', 'abrb2', 'arb2');
258+ }
259+ return parts;
260+}
261+
262+export function filterFakeParts(parts: string[]): string[] {
263+ return parts.filter(part => partParents[part]);
264+}
265+
266+export class SymbolQuery {
267+ sigilQuery?: SigilQuery;
268+ symbolType: SymbolType;
269+ components: string[];
270+
271+ constructor(sigilQuery: SigilQuery = undefined, components = [], symbolType: SymbolType = 'suffix') {
272+ this.sigilQuery = sigilQuery;
273+ this.components = components;
274+ this.symbolType = symbolType;
275+ this.cleanupParts();
276+ }
277+
278+ set geon(geon: Geon) {
279+ this.components = this.components.filter(c => c[0] !== 'g');
280+ if (geon)
281+ this.components = [geon, ...this.components];
282+ this.cleanupParts();
283+ }
284+
285+ get geon(): Geon {
286+ return this.components.find(c => c[0] === 'g');
287+ }
288+
289+ get allowFictional() {
290+ if (this.sigilQuery) return this.sigilQuery.allowFictional;
291+ return true;
292+ }
293+
294+ get plausibleSyllables() {
295+ return getPlausibleSyllables(this.components, this.symbolType);
296+ }
297+
298+ get plausibleInts() {
299+ return this.plausibleSyllables.map(syl => convertSyllableToInt(syl, this.symbolType));
300+ }
301+
302+ get isPlausible() {
303+ return this.plausibleSyllables.length > 0;
304+ }
305+
306+ get plausibleParts() {
307+ return getPlausibleNewParts(this.components, this.symbolType);
308+ }
309+
310+ get isPerfectMatch() {
311+ const syls = this.plausibleSyllables;
312+ if (syls.length === 1) return true;
313+ const compLength = this.components.length;
314+ return syls.map(syl => symbolDefs[syl].length).some(length => length === compLength);
315+ }
316+
317+ filterPlausibleParts(parts: string[]): string[] {
318+ return filterPlausibleParts(this.components, this.symbolType, parts);
319+ }
320+
321+ arePartsPlausible(parts: string[]): boolean {
322+ return arePartsPlausible(this.components, this.symbolType, parts);
323+ }
324+
325+ cleanupParts() {
326+ this.components = uniq(this.components);
327+ this.components = filterPartsByGeon(this.components, this.geon);
328+ this.components = filterFakeParts(this.components);
329+ // if (!this.allowFictional) {
330+ // this.components = this.plausibleParts;
331+ // }
332+ this.components = this.components.sort((c1, c2) => {
333+ let i1 = getPartTypeIndex(c1);
334+ let i2 = getPartTypeIndex(c2);
335+ let diff = i1 - i2;
336+ if (diff !== 0) return diff;
337+ if (c1[0] === 'c') {
338+ return c1.localeCompare(c2);
339+ } else {
340+ return c2.localeCompare(c1);
341+ }
342+ });
343+ }
344+
345+ clearPartType(partType: PartType) {
346+ this.components = this.components.filter(c => c[0] !== partType[0]);
347+ return this;
348+ }
349+
350+ clearExceptPartType(partType: PartType) {
351+ this.components = this.components.filter(c => c[0] === partType[0]);
352+ return this;
353+ }
354+
355+ clear() {
356+ this.components = [];
357+ return this;
358+ }
359+
360+ addPart(partId: string) {
361+ this.components = [...this.components, partId];
362+ if (hasComplement(partId)) {
363+ const otherPartId = getComplementaryCircleId(partId);
364+ this.components = [...this.components, otherPartId];
365+ }
366+ this.cleanupParts();
367+ return this;
368+ }
369+
370+ removePart(partId: string) {
371+ this.components = without(this.components, partId);
372+ if (hasComplement(partId)) {
373+ const otherPartId = getComplementaryCircleId(partId);
374+ this.components = without(this.components, otherPartId);
375+ }
376+ this.cleanupParts();
377+ return this;
378+ }
379+}
380+
381+export function getDeepParts(def: ModeDef): string[] {
382+ if (def.modes) {
383+ return uniq(Object.values(def.modes).map(getDeepParts).flat());
384+ } else {
385+ return def.parts || [];
386+ }
387+}
ui/src/lib/utils.ts+52−6
@@ -1,15 +1,61 @@
1−function listStrings(list: string[], includeAnd: boolean = false): string {
1+export function isStringNaN(value: string): boolean {
2+ return window.isNaN(value as any) || Number.isNaN(parseInt(value)) || !(/^-?\d+$/.test(value));
3+}
4+
5+export function listStrings(list: string[], includeAnd: boolean = false): string {
26 if (includeAnd && list.length > 1) {
37 return listStrings(list.slice(0, -1)) + ' and ' + listStrings(list.slice(-1));
48 }
59 return "'" + list.join("', '") + "'";
610}
711
8−function filterObject(obj, predicate) {
12+export function filterObject(obj, predicate) {
913 return Object.fromEntries(Object.entries(obj).filter(([key, value]) => predicate(key, value)));
1014}
1115
12−export {
13− listStrings,
14− filterObject,
15−}
16+export function buildQuerystring(data) {
17+ if (typeof (data) === 'string') return data;
18+
19+ var query = [];
20+ for (let key in data) {
21+ if (data.hasOwnProperty(key)) {
22+ // Encode each key and value, concatenate them into a string, and push them to the array
23+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
24+ }
25+ }
26+
27+ // Join each item in the array with a `&` and return the resulting string
28+ return query.length ? '?' + query.join('&') : '';
29+}
30+
31+export function setStoreKey(store, key, value) {
32+ store.update((store) => ({
33+ ...store,
34+ [key]: value,
35+ }));
36+}
37+
38+export function toggleStoreKey(store, key) {
39+ store.update((store) => ({
40+ ...store,
41+ [key]: !store[key],
42+ }));
43+}
44+
45+export function capitalize(string: string): string {
46+ return string.charAt(0).toUpperCase() + string.slice(1);
47+}
48+
49+export function uxToHex(ux: string) {
50+ let value = ux;
51+ if (ux.length > 2 && ux.substring(0, 2) === '0x') {
52+ value = ux.substring(2)
53+ }
54+
55+ value = value.replace('.', '').padStart(6, '0');
56+ return '#' + value;
57+}
58+
59+export function sleep(ms) {
60+ return new Promise(resolve => setTimeout(resolve, ms));
61+}
ui/src/main.ts+5−2
@@ -1,7 +1,10 @@
11import App from './App.svelte'
22
3+window.desk = window.desk || 'astrolabe';
4+window.ship = window.ship || 'zod';
5+
36const app = new App({
4− target: document.getElementById('app')
7+ target: document.getElementById('app'),
58})
69
7−export default app
10+export default app;
ui/src/routes/HelpPage.sveltenew file+26
@@ -0,0 +1,26 @@
1+<script lang="ts">
2+ import { onMount } from 'svelte';
3+ import { push } from 'svelte-spa-router';
4+ import docs from 'stores/docs';
5+ import Doc from '@/docs/Doc.svelte';
6+ export let params: any = {};
7+ $: path = params.path || '';
8+
9+ onMount(() => {
10+ docs.hide();
11+ });
12+
13+ // $: title = path ? capitalizeWords(path.replace(/-/g, ' ')) : 'Help';
14+
15+ function closeDoc() {
16+ push('#/');
17+ }
18+
19+ function navigate(event) {
20+ push(`#/help/${event.detail}`)
21+ }
22+</script>
23+
24+<div class="m-4 2xs:m-8 md:max-w-xl md:mx-auto">
25+ <Doc {path} on:close={closeDoc} on:navigate={navigate} />
26+</div>
ui/src/routes/Home.svelte+191−7
@@ -1,11 +1,195 @@
11<script lang="ts">
2− import SearchBar from "../components/SearchBar.svelte";
3− import SearchResults from "../components/SearchResults.svelte";
4− let patp: string;
5− let searchQuery: RegExp;
2+ import { push, replace, location, querystring } from 'svelte-spa-router'
3+ import pageName from 'stores/pageName';
4+ import { SearchAnalysis } from 'lib/id';
5+ import { SigilQuery } from 'lib/sigil';
6+ import { buildQuerystring } from 'lib/utils';
7+ import { linkToShip, linkToChart, linkToSearch } from 'lib/link';
8+ import SearchBar from "@/search/SearchBar.svelte";
9+ import SearchResults from "@/search/SearchResults.svelte";
10+ import ShipView from '@/ship_view/ShipView.svelte';
11+ import StarChart from '@/StarChart.svelte';
12+ import ValidationProblems from '@/ValidationProblems.svelte';
13+ import HomeScreenTiles from '@/HomeScreenTiles.svelte';
14+ export let params: any = {};
15+
16+ let analysis: SearchAnalysis = new SearchAnalysis();
17+ let sigilQuery: SigilQuery = new SigilQuery('planet');
18+ let navigating = false;
19+
20+ let searchParams = new URLSearchParams($querystring);
21+ function getUrlSearchMode(searchParams) {
22+ return (searchParams.get('mode') == 'sigil') ? 'sigil' : 'patp';
23+ }
24+ let searchMode: ('patp' | 'sigil') = getUrlSearchMode(searchParams);
25+ let search: string = searchParams.get('search') || params.arg || '';
26+ let patp: string = undefined;
27+ let urlSearch = search;
28+ let urlSearchMode = searchMode;
29+ let urlChangedSinceShipQuery = false;
30+ $: {
31+ const searchParams = new URLSearchParams($querystring);
32+ const potentialUrlSearch = searchParams.get('search') || params.arg || '';
33+ if (($location + $querystring).includes(potentialUrlSearch)) {
34+ urlSearch = potentialUrlSearch;
35+ urlSearchMode = getUrlSearchMode(searchParams);
36+ urlChangedSinceShipQuery = true;
37+ navigating = false;
38+ // console.log('url changed');
39+ }
40+ }
41+ $: {
42+ urlChangedSinceShipQuery = (search || true) && searchMode && false;
43+ // console.log('search changed');
44+ }
45+ function autoNav() {
46+ // console.log('autonav');
47+ if ((search !== urlSearch || searchMode !== urlSearchMode)) {
48+ if (urlChangedSinceShipQuery) {
49+ search = urlSearch;
50+ searchMode = urlSearchMode;
51+ // console.log('url wins');
52+ } else {
53+ navigating = true;
54+ // console.log('search wins');
55+ // create a temp one because the real one doesn't always update in time!
56+ let tempAnalysis = new SearchAnalysis(search);
57+ if (params.mode === 'chart') {
58+ // console.log('to chart');
59+ navToChart(tempAnalysis.patpIsValid);
60+ } else {
61+ if (tempAnalysis.patpIsValid) {
62+ // console.log('to ship view');
63+ navToShipView(tempAnalysis.patp, true);
64+ } else {
65+ const fromShipMode = params.mode === 'ship';
66+ // console.log('to search');
67+ navToSearch(fromShipMode);
68+ }
69+ }
70+ }
71+ }
72+ }
73+ $: urlSearch, urlSearchMode, search, searchMode, autoNav();
74+ function nav(path, addToHistory = false) {
75+ (addToHistory ? push : replace)(path);
76+ }
77+ function navHome(addToHistory = false) {
78+ params.mode = null;
79+ params.arg = null;
80+ if (searchMode == 'patp') {
81+ nav('/', addToHistory);
82+ } else {
83+ nav('/' + buildQuerystring({ mode: searchMode }), addToHistory);
84+ }
85+ }
86+ function navToShipView(patp, addToHistory = false) {
87+ params.mode = 'ship';
88+ params.arg = patp;
89+ if (patp === search) {
90+ nav(linkToShip(patp), addToHistory);
91+ } else {
92+ nav(linkToShip(patp, { search, mode: searchMode }), addToHistory);
93+ }
94+ }
95+ function navToSearch(addToHistory = false) {
96+ if (!search) {
97+ navHome(addToHistory);
98+ } else {
99+ params.mode = 'search';
100+ params.arg = search;
101+ nav(linkToSearch(search, { mode: searchMode }), addToHistory);
102+ }
103+ }
104+ function navToChart(addToHistory = true) {
105+ params.mode = 'chart';
106+ params.arg = search;
107+ nav(linkToChart(search), addToHistory);
108+ }
109+
110+ function navToMode(mode, addToHistory = false) {
111+ switch (mode) {
112+ case 'ship':
113+ navToShipView(addToHistory);
114+ break;
115+ case 'search':
116+ navToSearch(addToHistory);
117+ break;
118+ default:
119+ navHome(addToHistory);
120+ }
121+ }
122+ $: {
123+ let properMode = params.mode;
124+ if (properMode === 'chart') {
125+ // nice
126+ } else if (analysis.search === search) {
127+ if (analysis.patpIsValid) {
128+ properMode = 'ship';
129+ } else if (analysis.search.length > 0) {
130+ properMode = 'search';
131+ }
132+ }
133+ if (params.mode !== properMode) {
134+ navToMode(properMode, false);
135+ }
136+ }
137+
138+ let placeholderText;
139+ $: {
140+ if (analysis.search === urlSearch) {
141+ placeholderText = undefined;
142+ switch (params.mode) {
143+ case 'ship':
144+ $pageName = params.arg;
145+ break;
146+ case 'search':
147+ $pageName = 'Search';
148+ break;
149+ case 'chart':
150+ $pageName = 'Star Chart';
151+ placeholderText = 'Search by @p or #.';
152+ break;
153+ default:
154+ $pageName = null;
155+ }
156+ }
157+ }
158+
159+
6160</script>
7161
8−<div class="m-8 flex flex-col space-y-8">
9− <SearchBar bind:patp bind:searchQuery />
10− <SearchResults {patp} {searchQuery}/>
162+<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
163+ <SearchBar bind:analysis bind:sigilQuery bind:search bind:searchMode bind:patp {placeholderText}
164+ allowSigilSearch={params.mode !== 'chart'}
165+ />
166+ <!-- <div class="bg-white">
167+ <p>
168+ Mode: {params.mode}
169+ </p>
170+ <p>
171+ Search: {search}
172+ </p>
173+ <p>
174+ URL Search: {urlSearch}
175+ </p>
176+ <p>
177+ Search mode: {searchMode}
178+ </p>
179+ <p>
180+ Patp: {patp}
181+ </p>
182+ </div> -->
183+ {#if params.mode == 'chart'}
184+ <StarChart patp={patp || null} />
185+ {:else if patp}
186+ <ShipView {patp} />
187+ {:else if (searchMode === 'patp' && analysis.queryIsValid) || (searchMode === 'sigil' && sigilQuery.isWorthSearching)}
188+ <SearchResults regexQuery={analysis.query} sigilQuery={sigilQuery}
189+ patpSearch={analysis.search} {searchMode} />
190+ {:else if (analysis.search.length > 0 && analysis.queryProblems.length > 0)}
191+ <ValidationProblems problems={analysis.queryProblems} />
192+ {:else}
193+ <HomeScreenTiles />
194+ {/if}
11195</div>
ui/src/routes/ShipViewPage.sveltedeleted−25
@@ -1,25 +0,0 @@
1−<script lang="ts">
2− import { link, replace } from 'svelte-spa-router'
3− import { analyzeSearch, normalizeId } from '../lib/id';
4− import { linkToShip } from '../lib/link';
5− import ShipView from '../components/ShipView.svelte';
6− import ValidationProblems from '../components/ValidationProblems.svelte';
7− export let params: any = {};
8−
9− $: patp = normalizeId(params.patp);
10− $: analysis = analyzeSearch(params.patp);
11− $: {
12− if (analysis.patpIsValid && patp !== params.patp) {
13− replace(linkToShip(patp));
14− }
15− }
16−</script>
17−
18−<div class="m-8 flex flex-col space-y-8">
19− <a use:link={"/"} class="p-4 bg-white rounded-lg text-center">Go Home</a>
20− {#if analysis.patpIsValid}
21− <ShipView patp={patp} />
22− {:else}
23− <ValidationProblems problems={analysis.patpProblems} />
24− {/if}
25−</div>
ui/src/routes/SigilPage.sveltenew file+61
@@ -0,0 +1,61 @@
1+<script lang="ts">
2+ import { prefixes, suffixes } from 'lib/id';
3+ import Sigil from "@/common/Sigil.svelte";
4+ // import Legil from "@/common/Legil.svelte";
5+
6+ let index: number = undefined;
7+ let altBgColor = 'red';
8+
9+ let phonemes = prefixes.map(p => p + 'zod').concat(suffixes).sort().map(p => '~' + p);
10+
11+ function into(i) {
12+ index = i;
13+ }
14+
15+ function out() {
16+ index = undefined;
17+ }
18+
19+ function forward() {
20+ index = index + 1;
21+ index = Math.min(511, index);
22+ }
23+
24+ function back() {
25+ index = index - 1;
26+ index = Math.max(0, index);
27+ }
28+</script>
29+
30+<div class="bg-white m-4">
31+ {#if index == undefined}
32+ <div class="flex flex-wrap">
33+ {#each phonemes as phoneme, i}
34+ <div class="w-[128px] m-2" on:click={() => into(i)}>
35+ <h2>
36+ {phoneme}
37+ </h2>
38+ <div class="flex">
39+ <Sigil patp={phoneme} size={64} {altBgColor} />
40+ <Sigil patp={phoneme} size={64} {altBgColor} useNew />
41+ <!-- <Legil patp={phoneme} size={64} {altBgColor} /> -->
42+ </div>
43+ </div>
44+ {/each}
45+ </div>
46+ {:else}
47+ <div class="flex gap-2">
48+ <button class="p-4 border" on:click={back}>&lt;</button>
49+ <button class="p-4 border" on:click={out}>^</button>
50+ <button class="p-4 border" on:click={forward}>&gt;</button>
51+ </div>
52+ <h2>
53+ {phonemes[index]}
54+ </h2>
55+ <div class="flex w-full">
56+ <Sigil patp={phonemes[index]} size={400} {altBgColor} />
57+ <Sigil patp={phonemes[index]} size={400} {altBgColor} useNew />
58+ <!-- <Legil patp={phonemes[index]} size={400} {altBgColor} /> -->
59+ </div>
60+ {/if}
61+</div>
ui/src/stores/docs.tsnew file+44
@@ -0,0 +1,44 @@
1+import { writable } from 'svelte/store';
2+
3+import { getDoc } from 'lib/api';
4+const docs = {};
5+
6+const initStore: any = {
7+ show: false,
8+ path: '',
9+};
10+
11+const store = writable(initStore);
12+const { subscribe, update, set } = store;
13+
14+async function getCachedDoc(path): Promise<string> {
15+ if (docs[path]) {
16+ return docs[path];
17+ } else {
18+ const doc = await getDoc(path);
19+ docs[path] = doc;
20+ return doc;
21+ }
22+}
23+
24+function show(path) {
25+ update($store => ({
26+ ...$store,
27+ path,
28+ show: true,
29+ }));
30+}
31+
32+function hide() {
33+ update($store => ({
34+ ...$store,
35+ show: false,
36+ }));
37+}
38+
39+export default {
40+ subscribe,
41+ getDoc: getCachedDoc,
42+ show,
43+ hide
44+};
ui/src/stores/keys.tsnew file+68
@@ -0,0 +1,68 @@
1+import { writable, get } from 'svelte/store';
2+
3+const docs = {};
4+
5+class MapWithDefault<K, V> extends Map<K, V> {
6+ default: Function;
7+
8+ get(key: K) {
9+ if (!this.has(key)) {
10+ this.set(key, this.default());
11+ }
12+ return super.get(key);
13+ }
14+
15+ constructor(defaultFunction: () => V) {
16+ super();
17+ this.default = defaultFunction;
18+ }
19+}
20+
21+type Bindings = MapWithDefault<string, MapWithDefault<string, Function>>;
22+interface KeysStore {
23+ keydown: Bindings;
24+ keyup: Bindings;
25+ globalkeydown: Bindings;
26+ globalkeyup: Bindings;
27+};
28+function newEmptyBindings(): Bindings {
29+ return new MapWithDefault(() => new MapWithDefault(() => Function));
30+}
31+const initStore: KeysStore = {
32+ keydown: newEmptyBindings(),
33+ keyup: newEmptyBindings(),
34+ globalkeydown: newEmptyBindings(),
35+ globalkeyup: newEmptyBindings(),
36+};
37+
38+const store = writable(initStore);
39+const { subscribe, update, set } = store;
40+
41+function runCallbacks(bindings: Bindings, event: KeyboardEvent) {
42+ let els = [];
43+ if (event.ctrlKey) els.push('ctrl');
44+ if (event.metaKey) els.push('cmd');
45+ if (event.altKey) els.push('alt');
46+ if (event.shiftKey) els.push('shift');
47+ els.push(event.key == ' ' ? 'Space' : event.key.toLowerCase());
48+ let combo = els.join('+');
49+ // console.log(event);
50+ let callbacks = bindings.get(combo);
51+ if (callbacks) {
52+ callbacks.forEach((callback) => {
53+ try {
54+ if (callback)
55+ callback(event);
56+ } catch (e) {
57+ console.error(e);
58+ }
59+ });
60+ }
61+}
62+
63+export default {
64+ subscribe,
65+ update,
66+ set,
67+ runCallbacks,
68+};
ui/src/stores/pageName.tsnew file+12
@@ -0,0 +1,12 @@
1+import { writable } from 'svelte/store';
2+
3+const initial = null;
4+
5+const store = writable(initial);
6+const { subscribe, update, set } = store;
7+
8+function reset() {
9+ set(initial);
10+}
11+
12+export default { subscribe, update, set, reset };
ui/src/stores/searchStores.tsnew file+66
@@ -0,0 +1,66 @@
1+import { writable, derived } from 'svelte/store';
2+
3+import type { Contact, Rolodex } from '@urbit/api';
4+import store from './store';
5+import type { StoreState, SearchedContactsState } from 'types/store';
6+import { createEmptyContact } from 'types/store';
7+import { SearchSettings } from 'types/store';
8+import { splitIdIntoSyls, isMoonLength, isCometLength } from 'lib/id';
9+import { filterObject } from 'lib/utils';
10+
11+const searchSettings = writable(new SearchSettings());
12+
13+const searchedContacts = derived([store, searchSettings], deriveSearchedContacts);
14+searchedContacts.searchPatp = store.searchPatp;
15+searchedContacts.searchSigil = store.searchSigil;
16+
17+function deriveSearchedContacts([$store, $searchSettings]): SearchedContactsState {
18+ let idMatches;
19+ if ($store.searchMode === 'patp') {
20+ if (!$store.patpQuery) {
21+ return {};
22+ }
23+ idMatches = idMatchesRegex;
24+ } else {
25+ if (!$store.sigilQuery) {
26+ return {};
27+ }
28+ idMatches = idMatchesSyls;
29+ }
30+ function isIdAllowed(id) {
31+ if (!$searchSettings.includeMoons && isMoonLength(id)) {
32+ return false;
33+ }
34+ if (!$searchSettings.includeComets && isCometLength(id)) {
35+ return false;
36+ }
37+ return true;
38+ }
39+ function idMatchesSyls(id) {
40+ if (!isIdAllowed(id)) return false;
41+ const idSyls = splitIdIntoSyls(id);
42+ if (idSyls.length !== $store.sigilQuery.length) return false;
43+ return idSyls.every((syl, index) => {
44+ const symbolSyls = $store.sigilQuery[index];
45+ if (symbolSyls.length == 256) return true;
46+ return symbolSyls.includes(syl);
47+ });
48+ }
49+ function idMatchesRegex(id) {
50+ if (!$store.patpQuery.test(id)) return false;
51+ return isIdAllowed(id);
52+ }
53+ const filteredContacts = filterObject($store.contacts, idMatches) as Rolodex;
54+ $store.peers.forEach((id) => {
55+ if (idMatches(id) && !filteredContacts[id]) {
56+ filteredContacts[id] = createEmptyContact();
57+ }
58+ })
59+ return filteredContacts;
60+}
61+
62+export {
63+ // searchedContacts: { ...searchedContacts, search: store.search },
64+ searchedContacts,
65+ searchSettings,
66+};
ui/src/stores/store.ts+66−24
@@ -1,40 +1,82 @@
11import { writable } from 'svelte/store';
2−import type { StoreState } from '../types/store';
3−import { filterObject } from '../lib/utils';
4−import fakeContacts from '../fake_data/fake-contacts';
2+
3+import type { Rolodex } from '@urbit/api';
4+import { subscribeToContacts, getPeers } from 'lib/api';
5+import type { StoreState, Apps } from 'types/store';
6+import { normalizeId } from 'lib/id';
7+import { setStoreKey } from 'lib/utils';
8+import { getPals } from 'lib/pals';
9+import { subscribeToApps } from 'lib/apps';
510
611const initStore: StoreState = {
7− // chats: [],
8− // groups: {},
912 contacts: {},
13+ peers: [],
14+ pals: {
15+ incoming: {},
16+ outgoing: {},
17+ mutuals: {},
18+ },
19+ palsInstalled: false,
20+ apps: {
21+ allies: {},
22+ set: new Set(),
23+ },
24+ patpQuery: null,
25+ sigilQuery: null,
26+ searchMode: 'patp',
1027 connection: 'disconnected',
1128 ship: window.ship,
1229};
1330
14−const { subscribe, update, set } = writable(initStore);
15−
16−function search(idQuery: RegExp) {
17− update(($store: StoreState): StoreState => {
18− if (!idQuery) {
19− return {
20− ...$store,
21− contacts: fakeContacts,
22− };
23− }
24− // const filteredContacts = filterObject($store.contacts, (id, contact) => {
25− const filteredContacts = filterObject(fakeContacts, (id) => {
26− return idQuery.test(id);
27− });
31+const store = writable(initStore);
32+const { subscribe, update, set } = store;
33+
34+function searchPatp(patpQuery: RegExp) {
35+ setStoreKey(store, 'patpQuery', patpQuery);
36+ setStoreKey(store, 'searchMode', 'patp');
37+}
38+
39+function searchSigil(syls: string[][]) {
40+ setStoreKey(store, 'sigilQuery', syls);
41+ setStoreKey(store, 'searchMode', 'sigil');
42+}
43+
44+function reset() {
45+ set(initStore);
46+}
47+
48+function updateContacts(callback: (contacts: Rolodex) => Rolodex): void {
49+ update($store => {
2850 return {
2951 ...$store,
30− contacts: filteredContacts,
31− }
52+ contacts: callback($store.contacts),
53+ };
3254 });
3355}
3456
35−function reset() {
36− set(initStore);
57+subscribeToContacts(updateContacts);
58+
59+getPeers().then(({ points }) => {
60+ setStoreKey(store, 'peers', points.map(normalizeId));
61+});
62+
63+function refreshPals() {
64+ return getPals().then((pals) => {
65+ setStoreKey(store, 'pals', pals);
66+ setStoreKey(store, 'palsInstalled', true);
67+ });
68+}
69+refreshPals();
70+
71+function updateApps(callback: (apps) => Apps): void {
72+ update($store => {
73+ return {
74+ ...$store,
75+ apps: callback($store.apps),
76+ };
77+ });
3778}
3879
80+subscribeToApps(updateApps);
3981
40−export default { subscribe, search, reset };
82+export default { subscribe, set, searchPatp, searchSigil, refreshPals, reset };
ui/src/styles/docs.scssnew file+49
@@ -0,0 +1,49 @@
1+.doc {
2+ h1, h2, h3, h4 {
3+ @apply font-bold mt-2 py-2;
4+ }
5+
6+ h1 {
7+ @apply text-2xl
8+ }
9+
10+ h2 {
11+ @apply text-xl
12+ }
13+
14+ h3 {
15+ @apply text-lg
16+ }
17+
18+ p {
19+ @apply py-2;
20+ }
21+
22+ ul, ol {
23+ @apply pl-5;
24+
25+ li {
26+ h1, h2, h3, h4 {
27+ @apply py-0;
28+ }
29+
30+ // & * {
31+ // @apply inline;
32+ // }
33+ }
34+ }
35+
36+ ul {
37+ @apply list-disc;
38+ }
39+
40+ ol {
41+ @apply list-decimal;
42+ }
43+
44+ code {
45+ @apply bg-orange-gold px-0.5 rounded;
46+ // @apply bg-gray-300 px-0.5 rounded;
47+ font-size: 0.9375em;
48+ }
49+}
ui/src/styles/styles.scssnew file+13
@@ -0,0 +1,13 @@
1+@import 'tailwind-output.css';
2+@import 'tippy.css';
3+@import 'docs.scss';
4+
5+body {
6+ @apply bg-navy w-[100vw] h-[100vh] overflow-auto;
7+}
8+
9+a {
10+ @apply underline underline-offset-2 decoration-gold-s2;
11+ // @apply underline underline-offset-2 decoration-gold;
12+ // @apply underline underline-offset-2 decoration-orange-gold;
13+}
ui/src/styles/tailwind-output.css+1553−79
@@ -424,41 +424,363 @@ Ensure the default browser behavior of the `hidden` attribute.
424424 --tw-backdrop-saturate: ;
425425 --tw-backdrop-sepia: ;
426426}
427+.container {
428+ width: 100%;
429+}
430+@media (min-width: 360px) {
431+
432+ .container {
433+ max-width: 360px;
434+ }
435+}
436+@media (min-width: 475px) {
437+
438+ .container {
439+ max-width: 475px;
440+ }
441+}
442+@media (min-width: 640px) {
443+
444+ .container {
445+ max-width: 640px;
446+ }
447+}
448+@media (min-width: 768px) {
449+
450+ .container {
451+ max-width: 768px;
452+ }
453+}
454+@media (min-width: 1024px) {
455+
456+ .container {
457+ max-width: 1024px;
458+ }
459+}
460+@media (min-width: 1280px) {
461+
462+ .container {
463+ max-width: 1280px;
464+ }
465+}
466+@media (min-width: 1536px) {
467+
468+ .container {
469+ max-width: 1536px;
470+ }
471+}
472+.pointer-events-none {
473+ pointer-events: none;
474+}
475+.pointer-events-auto {
476+ pointer-events: auto;
477+}
478+.visible {
479+ visibility: visible;
480+}
481+.invisible {
482+ visibility: hidden;
483+}
427484.absolute {
428485 position: absolute;
429486}
430487.relative {
431488 position: relative;
432489}
490+.inset-0 {
491+ top: 0px;
492+ right: 0px;
493+ bottom: 0px;
494+ left: 0px;
495+}
496+.inset-x-0 {
497+ left: 0px;
498+ right: 0px;
499+}
500+.inset-x-auto {
501+ left: auto;
502+ right: auto;
503+}
504+.inset-x-full {
505+ left: 100%;
506+ right: 100%;
507+}
433508.right-0 {
434509 right: 0px;
435510}
511+.-bottom-2 {
512+ bottom: -0.5rem;
513+}
514+.top-0 {
515+ top: 0px;
516+}
517+.bottom-0 {
518+ bottom: 0px;
519+}
520+.-bottom-12 {
521+ bottom: -3rem;
522+}
523+.-bottom-8 {
524+ bottom: -2rem;
525+}
526+.-bottom-4 {
527+ bottom: -1rem;
528+}
529+.-bottom-5 {
530+ bottom: -1.25rem;
531+}
532+.-bottom-3 {
533+ bottom: -0.75rem;
534+}
535+.top-1 {
536+ top: 0.25rem;
537+}
538+.right-1 {
539+ right: 0.25rem;
540+}
541+.top-2 {
542+ top: 0.5rem;
543+}
544+.right-2 {
545+ right: 0.5rem;
546+}
547+.right-4 {
548+ right: 1rem;
549+}
550+.right-3 {
551+ right: 0.75rem;
552+}
553+.left-1 {
554+ left: 0.25rem;
555+}
556+.left-2 {
557+ left: 0.5rem;
558+}
559+.z-20 {
560+ z-index: 20;
561+}
562+.z-10 {
563+ z-index: 10;
564+}
565+.z-50 {
566+ z-index: 50;
567+}
568+.col-span-3 {
569+ grid-column: span 3 / span 3;
570+}
571+.col-span-2 {
572+ grid-column: span 2 / span 2;
573+}
574+.m-4 {
575+ margin: 1rem;
576+}
577+.m-2 {
578+ margin: 0.5rem;
579+}
580+.-m-1 {
581+ margin: -0.25rem;
582+}
436583.m-8 {
437584 margin: 2rem;
438585}
439−.mx-8 {
440− margin-left: 2rem;
441− margin-right: 2rem;
586+.-m-2 {
587+ margin: -0.5rem;
588+}
589+.m-auto {
590+ margin: auto;
591+}
592+.m-1 {
593+ margin: 0.25rem;
442594}
443595.mx-auto {
444596 margin-left: auto;
445597 margin-right: auto;
446598}
447−.-mt-2 {
448− margin-top: -0.5rem;
599+.my-1 {
600+ margin-top: 0.25rem;
601+ margin-bottom: 0.25rem;
602+}
603+.my-3 {
604+ margin-top: 0.75rem;
605+ margin-bottom: 0.75rem;
606+}
607+.mx-4 {
608+ margin-left: 1rem;
609+ margin-right: 1rem;
610+}
611+.mx-8 {
612+ margin-left: 2rem;
613+ margin-right: 2rem;
614+}
615+.my-4 {
616+ margin-top: 1rem;
617+ margin-bottom: 1rem;
618+}
619+.my-2 {
620+ margin-top: 0.5rem;
621+ margin-bottom: 0.5rem;
622+}
623+.my-5 {
624+ margin-top: 1.25rem;
625+ margin-bottom: 1.25rem;
626+}
627+.my-0 {
628+ margin-top: 0px;
629+ margin-bottom: 0px;
630+}
631+.my-auto {
632+ margin-top: auto;
633+ margin-bottom: auto;
634+}
635+.-mx-1 {
636+ margin-left: -0.25rem;
637+ margin-right: -0.25rem;
638+}
639+.mx-1 {
640+ margin-left: 0.25rem;
641+ margin-right: 0.25rem;
642+}
643+.my-1\.5 {
644+ margin-top: 0.375rem;
645+ margin-bottom: 0.375rem;
646+}
647+.mt-2 {
648+ margin-top: 0.5rem;
649+}
650+.ml-\[22px\] {
651+ margin-left: 22px;
652+}
653+.mr-1 {
654+ margin-right: 0.25rem;
655+}
656+.ml-8 {
657+ margin-left: 2rem;
449658}
450659.mr-4 {
451660 margin-right: 1rem;
452661}
662+.ml-1 {
663+ margin-left: 0.25rem;
664+}
665+.mt-4 {
666+ margin-top: 1rem;
667+}
668+.mt-5 {
669+ margin-top: 1.25rem;
670+}
453671.ml-4 {
454672 margin-left: 1rem;
455673}
674+.-mt-2 {
675+ margin-top: -0.5rem;
676+}
677+.-mt-1 {
678+ margin-top: -0.25rem;
679+}
680+.ml-3 {
681+ margin-left: 0.75rem;
682+}
683+.ml-6 {
684+ margin-left: 1.5rem;
685+}
686+.ml-5 {
687+ margin-left: 1.25rem;
688+}
689+.ml-2 {
690+ margin-left: 0.5rem;
691+}
692+.-mt-0 {
693+ margin-top: -0px;
694+}
695+.mt-3 {
696+ margin-top: 0.75rem;
697+}
698+.mt-10 {
699+ margin-top: 2.5rem;
700+}
701+.mt-20 {
702+ margin-top: 5rem;
703+}
704+.-ml-1 {
705+ margin-left: -0.25rem;
706+}
707+.mr-3 {
708+ margin-right: 0.75rem;
709+}
710+.ml-auto {
711+ margin-left: auto;
712+}
713+.mb-1 {
714+ margin-bottom: 0.25rem;
715+}
716+.mr-2 {
717+ margin-right: 0.5rem;
718+}
719+.mr-0 {
720+ margin-right: 0px;
721+}
722+.-mb-24 {
723+ margin-bottom: -6rem;
724+}
725+.-mb-20 {
726+ margin-bottom: -5rem;
727+}
728+.-mb-16 {
729+ margin-bottom: -4rem;
730+}
731+.mb-2 {
732+ margin-bottom: 0.5rem;
733+}
734+.-mt-0\.5 {
735+ margin-top: -0.125rem;
736+}
737+.block {
738+ display: block;
739+}
740+.inline-block {
741+ display: inline-block;
742+}
743+.inline {
744+ display: inline;
745+}
456746.flex {
457747 display: flex;
458748}
749+.table {
750+ display: table;
751+}
459752.grid {
460753 display: grid;
461754}
755+.contents {
756+ display: contents;
757+}
758+.hidden {
759+ display: none;
760+}
761+.aspect-square {
762+ aspect-ratio: 1 / 1;
763+}
764+.h-full {
765+ height: 100%;
766+}
767+.h-3 {
768+ height: 0.75rem;
769+}
770+.h-\[100vh\] {
771+ height: 100vh;
772+}
773+.h-12 {
774+ height: 3rem;
775+}
776+.h-5 {
777+ height: 1.25rem;
778+}
779+.h-fit {
780+ height: -webkit-fit-content;
781+ height: -moz-fit-content;
782+ height: fit-content;
783+}
462784.h-10 {
463785 height: 2.5rem;
464786}
@@ -468,124 +790,1276 @@ Ensure the default browser behavior of the `hidden` attribute.
468790.h-8 {
469791 height: 2rem;
470792}
471−.h-12 {
472− height: 3rem;
793+.h-2 {
794+ height: 0.5rem;
795+}
796+.h-1 {
797+ height: 0.25rem;
798+}
799+.h-7 {
800+ height: 1.75rem;
801+}
802+.h-4 {
803+ height: 1rem;
804+}
805+.h-24 {
806+ height: 6rem;
807+}
808+.h-20 {
809+ height: 5rem;
810+}
811+.h-48 {
812+ height: 12rem;
813+}
814+.h-64 {
815+ height: 16rem;
816+}
817+.h-72 {
818+ height: 18rem;
819+}
820+.h-16 {
821+ height: 4rem;
822+}
823+.h-auto {
824+ height: auto;
825+}
826+.h-0 {
827+ height: 0px;
828+}
829+.h-min {
830+ height: -webkit-min-content;
831+ height: -moz-min-content;
832+ height: min-content;
833+}
834+.h-40 {
835+ height: 10rem;
836+}
837+.h-60 {
838+ height: 15rem;
839+}
840+.min-h-\[20px\] {
841+ min-height: 20px;
842+}
843+.min-h-full {
844+ min-height: 100%;
845+}
846+.min-h-0 {
847+ min-height: 0px;
848+}
849+.min-h-\[14rem\] {
850+ min-height: 14rem;
473851}
474852.w-full {
475853 width: 100%;
476854}
477−.w-10 {
478− width: 2.5rem;
855+.w-min {
856+ width: -webkit-min-content;
857+ width: -moz-min-content;
858+ width: min-content;
859+}
860+.w-3 {
861+ width: 0.75rem;
862+}
863+.w-\[128px\] {
864+ width: 128px;
865+}
866+.w-\[100vw\] {
867+ width: 100vw;
479868}
480869.w-12 {
481870 width: 3rem;
482871}
872+.w-5 {
873+ width: 1.25rem;
874+}
875+.w-10 {
876+ width: 2.5rem;
877+}
878+.w-7 {
879+ width: 1.75rem;
880+}
881+.w-auto {
882+ width: auto;
883+}
884+.w-4 {
885+ width: 1rem;
886+}
887+.w-6 {
888+ width: 1.5rem;
889+}
890+.w-48 {
891+ width: 12rem;
892+}
893+.w-24 {
894+ width: 6rem;
895+}
896+.w-20 {
897+ width: 5rem;
898+}
899+.w-8 {
900+ width: 2rem;
901+}
902+.w-64 {
903+ width: 16rem;
904+}
905+.w-72 {
906+ width: 18rem;
907+}
908+.w-16 {
909+ width: 4rem;
910+}
911+.w-40 {
912+ width: 10rem;
913+}
914+.w-fit {
915+ width: -webkit-fit-content;
916+ width: -moz-fit-content;
917+ width: fit-content;
918+}
919+.w-max {
920+ width: -webkit-max-content;
921+ width: -moz-max-content;
922+ width: max-content;
923+}
924+.w-\[48px\] {
925+ width: 48px;
926+}
927+.min-w-0 {
928+ min-width: 0px;
929+}
930+.min-w-\[128px\] {
931+ min-width: 128px;
932+}
483933.max-w-md {
484934 max-width: 28rem;
485935}
936+.max-w-xs {
937+ max-width: 20rem;
938+}
939+.max-w-sm {
940+ max-width: 24rem;
941+}
942+.max-w-3xl {
943+ max-width: 48rem;
944+}
945+.max-w-2xl {
946+ max-width: 42rem;
947+}
948+.max-w-xl {
949+ max-width: 36rem;
950+}
951+.max-w-lg {
952+ max-width: 32rem;
953+}
954+.max-w-\[128px\] {
955+ max-width: 128px;
956+}
957+.max-w-\[256px\] {
958+ max-width: 256px;
959+}
960+.max-w-\[10rem\] {
961+ max-width: 10rem;
962+}
963+.max-w-\[18rem\] {
964+ max-width: 18rem;
965+}
966+.max-w-\[15rem\] {
967+ max-width: 15rem;
968+}
969+.flex-shrink-0 {
970+ flex-shrink: 0;
971+}
972+.flex-shrink {
973+ flex-shrink: 1;
974+}
975+.shrink {
976+ flex-shrink: 1;
977+}
978+.shrink-0 {
979+ flex-shrink: 0;
980+}
981+.flex-grow {
982+ flex-grow: 1;
983+}
486984.grow {
487985 flex-grow: 1;
488986}
489−.grid-cols-3 {
490− grid-template-columns: repeat(3, minmax(0, 1fr));
987+.grow-\[2\] {
988+ flex-grow: 2;
491989}
492−.flex-col {
493− flex-direction: column;
990+.border-collapse {
991+ border-collapse: collapse;
494992}
495−.flex-wrap {
496− flex-wrap: wrap;
993+.origin-bottom-right {
994+ transform-origin: bottom right;
497995}
498−.items-center {
499− align-items: center;
996+.origin-left {
997+ transform-origin: left;
500998}
501−.justify-center {
502− justify-content: center;
999+.origin-right {
1000+ transform-origin: right;
5031001}
504−.space-y-8 > :not([hidden]) ~ :not([hidden]) {
505− --tw-space-y-reverse: 0;
506− margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
507− margin-bottom: calc(2rem * var(--tw-space-y-reverse));
1002+.origin-top-left {
1003+ transform-origin: top left;
5081004}
509−.justify-self-start {
510− justify-self: start;
1005+.origin-top-right {
1006+ transform-origin: top right;
5111007}
512−.justify-self-end {
513− justify-self: end;
1008+.origin-bottom-left {
1009+ transform-origin: bottom left;
5141010}
515−.break-words {
516− overflow-wrap: break-word;
1011+.translate-x-0 {
1012+ --tw-translate-x: 0px;
1013+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5171014}
518−.break-all {
519− word-break: break-all;
1015+.translate-y-0 {
1016+ --tw-translate-y: 0px;
1017+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5201018}
521−.rounded-full {
522− border-radius: 9999px;
1019+.translate-x-1\/4 {
1020+ --tw-translate-x: 25%;
1021+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5231022}
524−.rounded-lg {
525− border-radius: 0.5rem;
1023+.translate-y-1\/4 {
1024+ --tw-translate-y: 25%;
1025+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5261026}
527−.rounded-sm {
528− border-radius: 0.125rem;
1027+.-translate-x-1\/4 {
1028+ --tw-translate-x: -25%;
1029+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5291030}
530−.border-b {
531− border-bottom-width: 1px;
1031+.-translate-y-1\/4 {
1032+ --tw-translate-y: -25%;
1033+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5321034}
533−.bg-black {
534− --tw-bg-opacity: 1;
535− background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1035+.rotate-90 {
1036+ --tw-rotate: 90deg;
1037+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5361038}
537−.bg-white {
538− --tw-bg-opacity: 1;
539− background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1039+.rotate-45 {
1040+ --tw-rotate: 45deg;
1041+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5401042}
541−.bg-gray-200 {
542− --tw-bg-opacity: 1;
543− background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1043+.-rotate-45 {
1044+ --tw-rotate: -45deg;
1045+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5441046}
545−.bg-gray-300 {
546− --tw-bg-opacity: 1;
547− background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1047+.scale-150 {
1048+ --tw-scale-x: 1.5;
1049+ --tw-scale-y: 1.5;
1050+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5481051}
549−.p-4 {
550− padding: 1rem;
1052+.scale-50 {
1053+ --tw-scale-x: .5;
1054+ --tw-scale-y: .5;
1055+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5511056}
552−.p-6 {
553− padding: 1.5rem;
1057+.transform {
1058+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
5541059}
555−.p-8 {
556− padding: 2rem;
1060+@-webkit-keyframes spin {
1061+
1062+ to {
1063+ transform: rotate(360deg);
1064+ }
5571065}
558−.p-1 {
559− padding: 0.25rem;
1066+@keyframes spin {
1067+
1068+ to {
1069+ transform: rotate(360deg);
1070+ }
5601071}
561−.p-2 {
562− padding: 0.5rem;
1072+.animate-spin {
1073+ -webkit-animation: spin 1s linear infinite;
1074+ animation: spin 1s linear infinite;
5631075}
564−.py-2 {
565− padding-top: 0.5rem;
566− padding-bottom: 0.5rem;
1076+.cursor-pointer {
1077+ cursor: pointer;
5671078}
568−.text-center {
569− text-align: center;
1079+.resize {
1080+ resize: both;
5701081}
571−.text-2xl {
572− font-size: 1.5rem;
573− line-height: 2rem;
1082+.auto-cols-min {
1083+ grid-auto-columns: -webkit-min-content;
1084+ grid-auto-columns: min-content;
5741085}
575−.opacity-10 {
576− opacity: 0.1;
1086+.auto-cols-fr {
1087+ grid-auto-columns: minmax(0, 1fr);
5771088}
578−.filter {
579− filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1089+.auto-cols-max {
1090+ grid-auto-columns: -webkit-max-content;
1091+ grid-auto-columns: max-content;
5801092}
581−.transition-opacity {
582− transition-property: opacity;
583− transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
584− transition-duration: 150ms;
1093+.grid-flow-row {
1094+ grid-auto-flow: row;
5851095}
586−.last\:border-none:last-child {
1096+.grid-flow-col {
1097+ grid-auto-flow: column;
1098+}
1099+.grid-flow-row-dense {
1100+ grid-auto-flow: row dense;
1101+}
1102+.grid-flow-col-dense {
1103+ grid-auto-flow: column dense;
1104+}
1105+.auto-rows-min {
1106+ grid-auto-rows: -webkit-min-content;
1107+ grid-auto-rows: min-content;
1108+}
1109+.auto-rows-auto {
1110+ grid-auto-rows: auto;
1111+}
1112+.auto-rows-max {
1113+ grid-auto-rows: -webkit-max-content;
1114+ grid-auto-rows: max-content;
1115+}
1116+.grid-cols-3 {
1117+ grid-template-columns: repeat(3, minmax(0, 1fr));
1118+}
1119+.grid-cols-2 {
1120+ grid-template-columns: repeat(2, minmax(0, 1fr));
1121+}
1122+.grid-cols-1 {
1123+ grid-template-columns: repeat(1, minmax(0, 1fr));
1124+}
1125+.grid-cols-4 {
1126+ grid-template-columns: repeat(4, minmax(0, 1fr));
1127+}
1128+.grid-cols-6 {
1129+ grid-template-columns: repeat(6, minmax(0, 1fr));
1130+}
1131+.grid-rows-2 {
1132+ grid-template-rows: repeat(2, minmax(0, 1fr));
1133+}
1134+.grid-rows-none {
1135+ grid-template-rows: none;
1136+}
1137+.grid-rows-3 {
1138+ grid-template-rows: repeat(3, minmax(0, 1fr));
1139+}
1140+.flex-col {
1141+ flex-direction: column;
1142+}
1143+.flex-wrap {
1144+ flex-wrap: wrap;
1145+}
1146+.place-content-center {
1147+ place-content: center;
1148+}
1149+.place-items-stretch {
1150+ place-items: stretch;
1151+}
1152+.content-center {
1153+ align-content: center;
1154+}
1155+.items-start {
1156+ align-items: flex-start;
1157+}
1158+.items-center {
1159+ align-items: center;
1160+}
1161+.justify-end {
1162+ justify-content: flex-end;
1163+}
1164+.justify-center {
1165+ justify-content: center;
1166+}
1167+.justify-between {
1168+ justify-content: space-between;
1169+}
1170+.justify-evenly {
1171+ justify-content: space-evenly;
1172+}
1173+.justify-items-center {
1174+ justify-items: center;
1175+}
1176+.justify-items-stretch {
1177+ justify-items: stretch;
1178+}
1179+.gap-5 {
1180+ gap: 1.25rem;
1181+}
1182+.gap-4 {
1183+ gap: 1rem;
1184+}
1185+.gap-2 {
1186+ gap: 0.5rem;
1187+}
1188+.gap-1 {
1189+ gap: 0.25rem;
1190+}
1191+.gap-3 {
1192+ gap: 0.75rem;
1193+}
1194+.gap-1\.5 {
1195+ gap: 0.375rem;
1196+}
1197+.gap-x-4 {
1198+ -moz-column-gap: 1rem;
1199+ column-gap: 1rem;
1200+}
1201+.gap-x-1 {
1202+ -moz-column-gap: 0.25rem;
1203+ column-gap: 0.25rem;
1204+}
1205+.gap-x-2 {
1206+ -moz-column-gap: 0.5rem;
1207+ column-gap: 0.5rem;
1208+}
1209+.gap-x-3 {
1210+ -moz-column-gap: 0.75rem;
1211+ column-gap: 0.75rem;
1212+}
1213+.space-y-4 > :not([hidden]) ~ :not([hidden]) {
1214+ --tw-space-y-reverse: 0;
1215+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
1216+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
1217+}
1218+.space-y-8 > :not([hidden]) ~ :not([hidden]) {
1219+ --tw-space-y-reverse: 0;
1220+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
1221+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
1222+}
1223+.space-y-1 > :not([hidden]) ~ :not([hidden]) {
1224+ --tw-space-y-reverse: 0;
1225+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
1226+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
1227+}
1228+.space-x-3 > :not([hidden]) ~ :not([hidden]) {
1229+ --tw-space-x-reverse: 0;
1230+ margin-right: calc(0.75rem * var(--tw-space-x-reverse));
1231+ margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
1232+}
1233+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
1234+ --tw-space-y-reverse: 0;
1235+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
1236+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
1237+}
1238+.divide-y > :not([hidden]) ~ :not([hidden]) {
1239+ --tw-divide-y-reverse: 0;
1240+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
1241+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
1242+}
1243+.divide-x > :not([hidden]) ~ :not([hidden]) {
1244+ --tw-divide-x-reverse: 0;
1245+ border-right-width: calc(1px * var(--tw-divide-x-reverse));
1246+ border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
1247+}
1248+.divide-gold > :not([hidden]) ~ :not([hidden]) {
1249+ --tw-divide-opacity: 1;
1250+ border-color: rgb(252 211 77 / var(--tw-divide-opacity));
1251+}
1252+.divide-black > :not([hidden]) ~ :not([hidden]) {
1253+ --tw-divide-opacity: 1;
1254+ border-color: rgb(0 0 0 / var(--tw-divide-opacity));
1255+}
1256+.divide-gray-800 > :not([hidden]) ~ :not([hidden]) {
1257+ --tw-divide-opacity: 1;
1258+ border-color: rgb(31 41 55 / var(--tw-divide-opacity));
1259+}
1260+.divide-gray-600 > :not([hidden]) ~ :not([hidden]) {
1261+ --tw-divide-opacity: 1;
1262+ border-color: rgb(75 85 99 / var(--tw-divide-opacity));
1263+}
1264+.divide-red-600 > :not([hidden]) ~ :not([hidden]) {
1265+ --tw-divide-opacity: 1;
1266+ border-color: rgb(220 38 38 / var(--tw-divide-opacity));
1267+}
1268+.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
1269+ --tw-divide-opacity: 1;
1270+ border-color: rgb(229 231 235 / var(--tw-divide-opacity));
1271+}
1272+.divide-gray-400 > :not([hidden]) ~ :not([hidden]) {
1273+ --tw-divide-opacity: 1;
1274+ border-color: rgb(156 163 175 / var(--tw-divide-opacity));
1275+}
1276+.divide-gray-300 > :not([hidden]) ~ :not([hidden]) {
1277+ --tw-divide-opacity: 1;
1278+ border-color: rgb(209 213 219 / var(--tw-divide-opacity));
1279+}
1280+.divide-gray-500 > :not([hidden]) ~ :not([hidden]) {
1281+ --tw-divide-opacity: 1;
1282+ border-color: rgb(107 114 128 / var(--tw-divide-opacity));
1283+}
1284+.place-self-center {
1285+ place-self: center;
1286+}
1287+.self-start {
1288+ align-self: flex-start;
1289+}
1290+.self-center {
1291+ align-self: center;
1292+}
1293+.justify-self-start {
1294+ justify-self: start;
1295+}
1296+.justify-self-end {
1297+ justify-self: end;
1298+}
1299+.overflow-auto {
1300+ overflow: auto;
1301+}
1302+.overflow-hidden {
1303+ overflow: hidden;
1304+}
1305+.overflow-scroll {
1306+ overflow: scroll;
1307+}
1308+.overflow-y-auto {
1309+ overflow-y: auto;
1310+}
1311+.whitespace-pre-line {
1312+ white-space: pre-line;
1313+}
1314+.break-normal {
1315+ overflow-wrap: normal;
1316+ word-break: normal;
1317+}
1318+.break-words {
1319+ overflow-wrap: break-word;
1320+}
1321+.break-all {
1322+ word-break: break-all;
1323+}
1324+.rounded-md {
1325+ border-radius: 0.375rem;
1326+}
1327+.rounded-lg {
1328+ border-radius: 0.5rem;
1329+}
1330+.rounded-full {
1331+ border-radius: 9999px;
1332+}
1333+.rounded {
1334+ border-radius: 0.25rem;
1335+}
1336+.rounded-xl {
1337+ border-radius: 0.75rem;
1338+}
1339+.rounded-sm {
1340+ border-radius: 0.125rem;
1341+}
1342+.rounded-2xl {
1343+ border-radius: 1rem;
1344+}
1345+.rounded-3xl {
1346+ border-radius: 1.5rem;
1347+}
1348+.rounded-b-2xl {
1349+ border-bottom-right-radius: 1rem;
1350+ border-bottom-left-radius: 1rem;
1351+}
1352+.rounded-t-2xl {
1353+ border-top-left-radius: 1rem;
1354+ border-top-right-radius: 1rem;
1355+}
1356+.rounded-t-md {
1357+ border-top-left-radius: 0.375rem;
1358+ border-top-right-radius: 0.375rem;
1359+}
1360+.rounded-br-2xl {
1361+ border-bottom-right-radius: 1rem;
1362+}
1363+.rounded-bl-none {
1364+ border-bottom-left-radius: 0px;
1365+}
1366+.rounded-tl-lg {
1367+ border-top-left-radius: 0.5rem;
1368+}
1369+.rounded-br-lg {
1370+ border-bottom-right-radius: 0.5rem;
1371+}
1372+.border {
1373+ border-width: 1px;
1374+}
1375+.border-4 {
1376+ border-width: 4px;
1377+}
1378+.border-8 {
1379+ border-width: 8px;
1380+}
1381+.border-2 {
1382+ border-width: 2px;
1383+}
1384+.border-0 {
1385+ border-width: 0px;
1386+}
1387+.border-\[1\.5px\] {
1388+ border-width: 1.5px;
1389+}
1390+.border-l-8 {
1391+ border-left-width: 8px;
1392+}
1393+.border-r {
1394+ border-right-width: 1px;
1395+}
1396+.border-b {
1397+ border-bottom-width: 1px;
1398+}
1399+.border-l {
1400+ border-left-width: 1px;
1401+}
1402+.border-t {
1403+ border-top-width: 1px;
1404+}
1405+.border-b-0 {
1406+ border-bottom-width: 0px;
1407+}
1408+.border-none {
5871409 border-style: none;
5881410}
589−.hover\:opacity-40:hover {
1411+.border-gray-500 {
1412+ --tw-border-opacity: 1;
1413+ border-color: rgb(107 114 128 / var(--tw-border-opacity));
1414+}
1415+.border-gray-400 {
1416+ --tw-border-opacity: 1;
1417+ border-color: rgb(156 163 175 / var(--tw-border-opacity));
1418+}
1419+.border-gold {
1420+ --tw-border-opacity: 1;
1421+ border-color: rgb(252 211 77 / var(--tw-border-opacity));
1422+}
1423+.border-gray-300 {
1424+ --tw-border-opacity: 1;
1425+ border-color: rgb(209 213 219 / var(--tw-border-opacity));
1426+}
1427+.border-navy {
1428+ --tw-border-opacity: 1;
1429+ border-color: rgb(31 36 60 / var(--tw-border-opacity));
1430+}
1431+.border-white {
1432+ --tw-border-opacity: 1;
1433+ border-color: rgb(255 255 255 / var(--tw-border-opacity));
1434+}
1435+.border-black {
1436+ --tw-border-opacity: 1;
1437+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
1438+}
1439+.border-red-500 {
1440+ --tw-border-opacity: 1;
1441+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
1442+}
1443+.border-gold-s5 {
1444+ --tw-border-opacity: 1;
1445+ border-color: rgb(126 106 39 / var(--tw-border-opacity));
1446+}
1447+.border-gold-s2 {
1448+ --tw-border-opacity: 1;
1449+ border-color: rgb(202 169 62 / var(--tw-border-opacity));
1450+}
1451+.border-gold-s1 {
1452+ --tw-border-opacity: 1;
1453+ border-color: rgb(227 190 69 / var(--tw-border-opacity));
1454+}
1455+.border-gray-800 {
1456+ --tw-border-opacity: 1;
1457+ border-color: rgb(31 41 55 / var(--tw-border-opacity));
1458+}
1459+.border-gray-600 {
1460+ --tw-border-opacity: 1;
1461+ border-color: rgb(75 85 99 / var(--tw-border-opacity));
1462+}
1463+.border-gray-200 {
1464+ --tw-border-opacity: 1;
1465+ border-color: rgb(229 231 235 / var(--tw-border-opacity));
1466+}
1467+.bg-gray-400 {
1468+ --tw-bg-opacity: 1;
1469+ background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1470+}
1471+.bg-white {
1472+ --tw-bg-opacity: 1;
1473+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1474+}
1475+.bg-navy {
1476+ --tw-bg-opacity: 1;
1477+ background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1478+}
1479+.bg-gold {
1480+ --tw-bg-opacity: 1;
1481+ background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1482+}
1483+.bg-l1-blue {
1484+ --tw-bg-opacity: 1;
1485+ background-color: rgb(23 146 199 / var(--tw-bg-opacity));
1486+}
1487+.bg-l2-green {
1488+ --tw-bg-opacity: 1;
1489+ background-color: rgb(23 199 106 / var(--tw-bg-opacity));
1490+}
1491+.bg-you {
1492+ --tw-bg-opacity: 1;
1493+ background-color: rgb(187 0 17 / var(--tw-bg-opacity));
1494+}
1495+.bg-orange-gold {
1496+ --tw-bg-opacity: 1;
1497+ background-color: rgb(247 211 146 / var(--tw-bg-opacity));
1498+}
1499+.bg-gray-300 {
1500+ --tw-bg-opacity: 1;
1501+ background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1502+}
1503+.bg-gray-200 {
1504+ --tw-bg-opacity: 1;
1505+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1506+}
1507+.bg-black {
1508+ --tw-bg-opacity: 1;
1509+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
1510+}
1511+.bg-amber-300 {
1512+ --tw-bg-opacity: 1;
1513+ background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1514+}
1515+.bg-yellow-300 {
1516+ --tw-bg-opacity: 1;
1517+ background-color: rgb(253 224 71 / var(--tw-bg-opacity));
1518+}
1519+.bg-orange-300 {
1520+ --tw-bg-opacity: 1;
1521+ background-color: rgb(253 186 116 / var(--tw-bg-opacity));
1522+}
1523+.bg-yellow-200 {
1524+ --tw-bg-opacity: 1;
1525+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
1526+}
1527+.bg-yellow-400 {
1528+ --tw-bg-opacity: 1;
1529+ background-color: rgb(250 204 21 / var(--tw-bg-opacity));
1530+}
1531+.bg-yellow-500 {
1532+ --tw-bg-opacity: 1;
1533+ background-color: rgb(234 179 8 / var(--tw-bg-opacity));
1534+}
1535+.bg-yellow-800 {
1536+ --tw-bg-opacity: 1;
1537+ background-color: rgb(133 77 14 / var(--tw-bg-opacity));
1538+}
1539+.bg-amber-200 {
1540+ --tw-bg-opacity: 1;
1541+ background-color: rgb(253 230 138 / var(--tw-bg-opacity));
1542+}
1543+.bg-gold-t8 {
1544+ --tw-bg-opacity: 1;
1545+ background-color: rgb(254 246 219 / var(--tw-bg-opacity));
1546+}
1547+.bg-gold-s8 {
1548+ --tw-bg-opacity: 1;
1549+ background-color: rgb(50 42 15 / var(--tw-bg-opacity));
1550+}
1551+.bg-gold-s4 {
1552+ --tw-bg-opacity: 1;
1553+ background-color: rgb(151 127 46 / var(--tw-bg-opacity));
1554+}
1555+.bg-gold-t1 {
1556+ --tw-bg-opacity: 1;
1557+ background-color: rgb(252 215 95 / var(--tw-bg-opacity));
1558+}
1559+.bg-gold-t2 {
1560+ --tw-bg-opacity: 1;
1561+ background-color: rgb(253 220 113 / var(--tw-bg-opacity));
1562+}
1563+.bg-gold-s2 {
1564+ --tw-bg-opacity: 1;
1565+ background-color: rgb(202 169 62 / var(--tw-bg-opacity));
1566+}
1567+.bg-gold-s1 {
1568+ --tw-bg-opacity: 1;
1569+ background-color: rgb(227 190 69 / var(--tw-bg-opacity));
1570+}
1571+.bg-blue-800 {
1572+ --tw-bg-opacity: 1;
1573+ background-color: rgb(30 64 175 / var(--tw-bg-opacity));
1574+}
1575+.bg-green-900 {
1576+ --tw-bg-opacity: 1;
1577+ background-color: rgb(20 83 45 / var(--tw-bg-opacity));
1578+}
1579+.bg-green-100 {
1580+ --tw-bg-opacity: 1;
1581+ background-color: rgb(220 252 231 / var(--tw-bg-opacity));
1582+}
1583+.bg-blue-200 {
1584+ --tw-bg-opacity: 1;
1585+ background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1586+}
1587+.bg-gray-500 {
1588+ --tw-bg-opacity: 1;
1589+ background-color: rgb(107 114 128 / var(--tw-bg-opacity));
1590+}
1591+.bg-red-200 {
1592+ --tw-bg-opacity: 1;
1593+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
1594+}
1595+.bg-red-100 {
1596+ --tw-bg-opacity: 1;
1597+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
1598+}
1599+.bg-gray-600 {
1600+ --tw-bg-opacity: 1;
1601+ background-color: rgb(75 85 99 / var(--tw-bg-opacity));
1602+}
1603+.fill-transparent {
1604+ fill: transparent;
1605+}
1606+.object-cover {
1607+ -o-object-fit: cover;
1608+ object-fit: cover;
1609+}
1610+.p-1 {
1611+ padding: 0.25rem;
1612+}
1613+.p-5 {
1614+ padding: 1.25rem;
1615+}
1616+.p-4 {
1617+ padding: 1rem;
1618+}
1619+.p-8 {
1620+ padding: 2rem;
1621+}
1622+.p-3 {
1623+ padding: 0.75rem;
1624+}
1625+.p-2 {
1626+ padding: 0.5rem;
1627+}
1628+.p-6 {
1629+ padding: 1.5rem;
1630+}
1631+.p-0 {
1632+ padding: 0px;
1633+}
1634+.p-\[3px\] {
1635+ padding: 3px;
1636+}
1637+.p-\[4px\] {
1638+ padding: 4px;
1639+}
1640+.px-2 {
1641+ padding-left: 0.5rem;
1642+ padding-right: 0.5rem;
1643+}
1644+.py-1 {
1645+ padding-top: 0.25rem;
1646+ padding-bottom: 0.25rem;
1647+}
1648+.px-4 {
1649+ padding-left: 1rem;
1650+ padding-right: 1rem;
1651+}
1652+.px-0\.5 {
1653+ padding-left: 0.125rem;
1654+ padding-right: 0.125rem;
1655+}
1656+.px-0 {
1657+ padding-left: 0px;
1658+ padding-right: 0px;
1659+}
1660+.py-0 {
1661+ padding-top: 0px;
1662+ padding-bottom: 0px;
1663+}
1664+.py-4 {
1665+ padding-top: 1rem;
1666+ padding-bottom: 1rem;
1667+}
1668+.py-2 {
1669+ padding-top: 0.5rem;
1670+ padding-bottom: 0.5rem;
1671+}
1672+.px-6 {
1673+ padding-left: 1.5rem;
1674+ padding-right: 1.5rem;
1675+}
1676+.py-8 {
1677+ padding-top: 2rem;
1678+ padding-bottom: 2rem;
1679+}
1680+.px-8 {
1681+ padding-left: 2rem;
1682+ padding-right: 2rem;
1683+}
1684+.px-3 {
1685+ padding-left: 0.75rem;
1686+ padding-right: 0.75rem;
1687+}
1688+.px-1 {
1689+ padding-left: 0.25rem;
1690+ padding-right: 0.25rem;
1691+}
1692+.px-1\.5 {
1693+ padding-left: 0.375rem;
1694+ padding-right: 0.375rem;
1695+}
1696+.py-0\.5 {
1697+ padding-top: 0.125rem;
1698+ padding-bottom: 0.125rem;
1699+}
1700+.pt-10 {
1701+ padding-top: 2.5rem;
1702+}
1703+.pb-8 {
1704+ padding-bottom: 2rem;
1705+}
1706+.pb-4 {
1707+ padding-bottom: 1rem;
1708+}
1709+.pb-2 {
1710+ padding-bottom: 0.5rem;
1711+}
1712+.pb-0 {
1713+ padding-bottom: 0px;
1714+}
1715+.pt-0 {
1716+ padding-top: 0px;
1717+}
1718+.pl-5 {
1719+ padding-left: 1.25rem;
1720+}
1721+.pt-20 {
1722+ padding-top: 5rem;
1723+}
1724+.text-center {
1725+ text-align: center;
1726+}
1727+.align-baseline {
1728+ vertical-align: baseline;
1729+}
1730+.align-top {
1731+ vertical-align: top;
1732+}
1733+.align-middle {
1734+ vertical-align: middle;
1735+}
1736+.align-bottom {
1737+ vertical-align: bottom;
1738+}
1739+.align-text-bottom {
1740+ vertical-align: text-bottom;
1741+}
1742+.align-sub {
1743+ vertical-align: sub;
1744+}
1745+.font-mono {
1746+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
1747+}
1748+.text-sm {
1749+ font-size: 0.875rem;
1750+ line-height: 1.25rem;
1751+}
1752+.text-xl {
1753+ font-size: 1.25rem;
1754+ line-height: 1.75rem;
1755+}
1756+.text-2xl {
1757+ font-size: 1.5rem;
1758+ line-height: 2rem;
1759+}
1760+.text-lg {
1761+ font-size: 1.125rem;
1762+ line-height: 1.75rem;
1763+}
1764+.text-3xl {
1765+ font-size: 1.875rem;
1766+ line-height: 2.25rem;
1767+}
1768+.text-base {
1769+ font-size: 1rem;
1770+ line-height: 1.5rem;
1771+}
1772+.font-bold {
1773+ font-weight: 700;
1774+}
1775+.capitalize {
1776+ text-transform: capitalize;
1777+}
1778+.leading-4 {
1779+ line-height: 1rem;
1780+}
1781+.leading-none {
1782+ line-height: 1;
1783+}
1784+.leading-3 {
1785+ line-height: .75rem;
1786+}
1787+.leading-5 {
1788+ line-height: 1.25rem;
1789+}
1790+.leading-8 {
1791+ line-height: 2rem;
1792+}
1793+.text-gray-600 {
1794+ --tw-text-opacity: 1;
1795+ color: rgb(75 85 99 / var(--tw-text-opacity));
1796+}
1797+.text-white {
1798+ --tw-text-opacity: 1;
1799+ color: rgb(255 255 255 / var(--tw-text-opacity));
1800+}
1801+.text-gray-800 {
1802+ --tw-text-opacity: 1;
1803+ color: rgb(31 41 55 / var(--tw-text-opacity));
1804+}
1805+.text-gray-700 {
1806+ --tw-text-opacity: 1;
1807+ color: rgb(55 65 81 / var(--tw-text-opacity));
1808+}
1809+.text-black {
1810+ --tw-text-opacity: 1;
1811+ color: rgb(0 0 0 / var(--tw-text-opacity));
1812+}
1813+.text-green-600 {
1814+ --tw-text-opacity: 1;
1815+ color: rgb(22 163 74 / var(--tw-text-opacity));
1816+}
1817+.text-gray-500 {
1818+ --tw-text-opacity: 1;
1819+ color: rgb(107 114 128 / var(--tw-text-opacity));
1820+}
1821+.text-green-500 {
1822+ --tw-text-opacity: 1;
1823+ color: rgb(34 197 94 / var(--tw-text-opacity));
1824+}
1825+.text-green-700 {
1826+ --tw-text-opacity: 1;
1827+ color: rgb(21 128 61 / var(--tw-text-opacity));
1828+}
1829+.text-gray-400 {
1830+ --tw-text-opacity: 1;
1831+ color: rgb(156 163 175 / var(--tw-text-opacity));
1832+}
1833+.text-gold {
1834+ --tw-text-opacity: 1;
1835+ color: rgb(252 211 77 / var(--tw-text-opacity));
1836+}
1837+.text-gray-300 {
1838+ --tw-text-opacity: 1;
1839+ color: rgb(209 213 219 / var(--tw-text-opacity));
1840+}
1841+.underline {
1842+ -webkit-text-decoration-line: underline;
1843+ text-decoration-line: underline;
1844+}
1845+.no-underline {
1846+ -webkit-text-decoration-line: none;
1847+ text-decoration-line: none;
1848+}
1849+.underline-offset-2 {
1850+ text-underline-offset: 2px;
1851+}
1852+.opacity-25 {
1853+ opacity: 0.25;
1854+}
1855+.opacity-75 {
1856+ opacity: 0.75;
1857+}
1858+.opacity-50 {
1859+ opacity: 0.5;
1860+}
1861+.opacity-10 {
1862+ opacity: 0.1;
1863+}
1864+.opacity-20 {
1865+ opacity: 0.2;
1866+}
1867+.opacity-30 {
1868+ opacity: 0.3;
1869+}
1870+.opacity-40 {
5901871 opacity: 0.4;
5911872}
1873+.opacity-5 {
1874+ opacity: 0.05;
1875+}
1876+.outline {
1877+ outline-style: solid;
1878+}
1879+.ring {
1880+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1881+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1882+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1883+}
1884+.blur {
1885+ --tw-blur: blur(8px);
1886+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1887+}
1888+.drop-shadow {
1889+ --tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06));
1890+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1891+}
1892+.sepia {
1893+ --tw-sepia: sepia(100%);
1894+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1895+}
1896+.filter {
1897+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1898+}
1899+.transition {
1900+ transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1901+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
1902+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter;
1903+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1904+ transition-duration: 150ms;
1905+}
1906+.transition-opacity {
1907+ transition-property: opacity;
1908+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1909+ transition-duration: 150ms;
1910+}
1911+.transition-transform {
1912+ transition-property: transform;
1913+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1914+ transition-duration: 150ms;
1915+}
1916+.transition-all {
1917+ transition-property: all;
1918+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1919+ transition-duration: 150ms;
1920+}
1921+.duration-150 {
1922+ transition-duration: 150ms;
1923+}
1924+.hover\:bg-gray-200:hover {
1925+ --tw-bg-opacity: 1;
1926+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1927+}
1928+.hover\:opacity-80:hover {
1929+ opacity: 0.8;
1930+}
1931+@media (min-width: 360px) {
1932+
1933+ .\32xs\:-bottom-3\.5 {
1934+ bottom: -0.875rem;
1935+ }
1936+
1937+ .\32xs\:-bottom-3 {
1938+ bottom: -0.75rem;
1939+ }
1940+
1941+ .\32xs\:m-8 {
1942+ margin: 2rem;
1943+ }
1944+
1945+ .\32xs\:mx-8 {
1946+ margin-left: 2rem;
1947+ margin-right: 2rem;
1948+ }
1949+
1950+ .\32xs\:space-y-8 > :not([hidden]) ~ :not([hidden]) {
1951+ --tw-space-y-reverse: 0;
1952+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
1953+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
1954+ }
1955+
1956+ .\32xs\:p-8 {
1957+ padding: 2rem;
1958+ }
1959+
1960+ .\32xs\:py-8 {
1961+ padding-top: 2rem;
1962+ padding-bottom: 2rem;
1963+ }
1964+}
1965+@media (min-width: 475px) {
1966+
1967+ .xs\:m-0 {
1968+ margin: 0px;
1969+ }
1970+
1971+ .xs\:mr-0 {
1972+ margin-right: 0px;
1973+ }
1974+
1975+ .xs\:mb-1 {
1976+ margin-bottom: 0.25rem;
1977+ }
1978+
1979+ .xs\:max-w-\[18rem\] {
1980+ max-width: 18rem;
1981+ }
1982+
1983+ .xs\:max-w-\[8rem\] {
1984+ max-width: 8rem;
1985+ }
1986+
1987+ .xs\:flex-row {
1988+ flex-direction: row;
1989+ }
1990+
1991+ .xs\:flex-col {
1992+ flex-direction: column;
1993+ }
1994+
1995+ .xs\:items-center {
1996+ align-items: center;
1997+ }
1998+
1999+ .xs\:self-stretch {
2000+ align-self: stretch;
2001+ }
2002+
2003+ .xs\:rounded-bl-2xl {
2004+ border-bottom-left-radius: 1rem;
2005+ }
2006+
2007+ .xs\:rounded-bl-none {
2008+ border-bottom-left-radius: 0px;
2009+ }
2010+
2011+ .xs\:border-l {
2012+ border-left-width: 1px;
2013+ }
2014+
2015+ .xs\:border-t-0 {
2016+ border-top-width: 0px;
2017+ }
2018+}
2019+@media (min-width: 768px) {
2020+
2021+ .md\:mx-auto {
2022+ margin-left: auto;
2023+ margin-right: auto;
2024+ }
2025+
2026+ .md\:w-1\/2 {
2027+ width: 50%;
2028+ }
2029+
2030+ .md\:min-w-\[256px\] {
2031+ min-width: 256px;
2032+ }
2033+
2034+ .md\:max-w-xl {
2035+ max-width: 36rem;
2036+ }
2037+
2038+ .md\:max-w-2xl {
2039+ max-width: 42rem;
2040+ }
2041+
2042+ .md\:flex-row {
2043+ flex-direction: row;
2044+ }
2045+
2046+ .md\:flex-nowrap {
2047+ flex-wrap: nowrap;
2048+ }
2049+
2050+ .md\:items-start {
2051+ align-items: flex-start;
2052+ }
2053+
2054+ .md\:space-x-8 > :not([hidden]) ~ :not([hidden]) {
2055+ --tw-space-x-reverse: 0;
2056+ margin-right: calc(2rem * var(--tw-space-x-reverse));
2057+ margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
2058+ }
2059+
2060+ .md\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
2061+ --tw-space-y-reverse: 0;
2062+ margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
2063+ margin-bottom: calc(0px * var(--tw-space-y-reverse));
2064+ }
2065+}
ui/src/styles/tippy.cssnew file+5
@@ -0,0 +1,5 @@
1+@import 'tippy.js/dist/tippy.css';
2+
3+.tippy-box[data-theme~='astrolabe'] {
4+ @apply bg-navy rounded-md px-1 text-white text-base border border-white break-words;
5+}
ui/src/types/sigil.tsnew file+148
@@ -0,0 +1,148 @@
1+export const CLANS = ['galaxy', 'star', 'planet'] as const;
2+export type Clan = typeof CLANS[number];
3+
4+export const PART_TYPES = ['geon', 'line', 'arc', 'bezier', 'circle', 'donut'] as const;
5+export type PartType = typeof PART_TYPES[number];
6+
7+export const GEONS = ['gci', 'gsq', 'ghl', 'ghr', 'ght', 'ghb', 'gq1', 'gq2', 'gq3', 'gq4', 'gfs', 'gbs'];
8+export type Geon = typeof GEONS[number];
9+
10+export type SymbolType = ('prefix' | 'suffix');
11+
12+export interface ModeDef {
13+ modes?: ModeDefs;
14+ displayParts?: string[];
15+ parts?: string[];
16+}
17+
18+export interface ModeDefs {
19+ [mode: string]: ModeDef
20+}
21+
22+
23+export const MODE_DEFS: ModeDefs = {
24+ geon: {
25+ // modes: {},
26+ displayParts: ['ghb'],
27+ },
28+ line: {
29+ modes: {
30+ mainFulls: {
31+ parts: ['lvf', 'lhf', 'lff', 'lbf'],
32+ },
33+ halvesFulls: {
34+ parts: ['lvfl', 'lvfr', 'lhft', 'lhfb', 'lfht', 'lfhb', 'lbht', 'lbhb'],
35+ },
36+ forwards: {
37+ parts: ['lff', 'lfft', 'lffb'],
38+ },
39+ backwards: {
40+ parts: ['lbf', 'lbft'],
41+ },
42+ weirds: {
43+ parts: ['lbhtf', 'lbrf', 'lfhm'],
44+ }
45+ },
46+ parts: ['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf'],
47+ },
48+ arcCorner: {
49+ modes: {
50+ trFulls: {
51+ parts: ['atrf8', 'atrf6', 'atrf4', 'atrf2'],
52+ },
53+ trHalves: {
54+ parts: [/*'atra8',*/ 'atra6', 'atra4', 'atra2', 'atrb8', 'atrb6', 'atrb4', 'atrb2'],
55+ displayParts: ['atrb8', 'atrb6', 'atrb4', 'atrb2'],
56+ },
57+ tlFulls: {
58+ parts: ['atlf8', 'atlf6', 'atlf4', 'atlf2'],
59+ },
60+ tlHalves: {
61+ parts: ['atla8', 'atla6', 'atla4', 'atla2', 'atlb8', 'atlb6', 'atlb4', 'atlb2'],
62+ displayParts: ['atlb8', 'atlb6', 'atlb4', 'atlb2'],
63+ },
64+ blFulls: {
65+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
66+ },
67+ blHalves: {
68+ parts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
69+ displayParts: ['ablb8', 'ablb6', 'ablb4', 'ablb2'],
70+ },
71+ brFulls: {
72+ parts: ['abrf8', 'abrf6', 'abrf4', 'abrf2'],
73+ },
74+ brHalves: {
75+ parts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
76+ displayParts: ['abrb8', 'abrb6', 'abrb4', 'abrb2'],
77+ },
78+ },
79+ parts: ['ablf8', 'ablf6', 'ablf4', 'ablf2'],
80+ },
81+ arc: {
82+ modes: {
83+ rings: {
84+ parts: ['am081', 'am082', 'am083', 'am08s'],
85+ },
86+ diagonals: {
87+ parts: ['am741', 'am742', 'am743', 'am422', 'am423'],
88+ },
89+ topsBottoms: {
90+ parts: ['ab2', 'ab3', 'ab4', 'at1', 'at3', 'at4'],
91+ },
92+ leftsRights: {
93+ parts: ['al2', 'al3', 'al4', 'ar1', 'ar2', 'ar4'/*, 'arb2'*/],
94+ },
95+ rights: {
96+ parts: ['am642', 'am643'],
97+ },
98+ middles: {
99+ parts: ['am222', 'am223', 'am622', 'am623'],
100+ },
101+ middless: {
102+ parts: ['am142', 'am443'],
103+ },
104+ topRights: {
105+ parts: ['am021', 'am022', 'am023', 'am242', 'arb2'],
106+ },
107+ },
108+ parts: ['am222', 'am223', 'ar4', 'ar2', 'ar1'],
109+ },
110+ circle: {
111+ modes: {
112+ mediums: {
113+ parts: [
114+ 'cmmb',
115+ 'ctl3mb', 'cbl3mb',
116+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
117+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
118+ 'cbl3mf', 'ctr3mf', 'ctl3mf',
119+ ],
120+ displayParts: [
121+ 'cmmb',
122+ 'ctl3mb', 'cbl3mb',
123+ 'ct2mb', 'cb2mb', 'cl2mb', 'cr2mb', 'ctl2mb', 'ctr2mb', 'cbl2mb', 'cbr2mb',
124+ 'ctl1mb', 'ctr1mb', 'cbl1mb', 'cbr1mb',
125+ 'ctr3mf',
126+ ],
127+ },
128+ sides: {
129+ parts: ['cl1mb', 'cr1mb', 'cl3mb', 'cr3mb'],
130+ },
131+ smalls: {
132+ parts: ['cr1sb', 'cr2sb'],
133+ },
134+ },
135+ displayParts: [
136+ 'cbr3mb', 'ctl3mb',
137+ 'cmmb',
138+ 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
139+ ],
140+ },
141+ donut: {
142+ parts: ['dbl', 'dl', 'dm', 'dtl', 'dtr'],
143+ displayParts: ['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr'],
144+ },
145+ bezier: {
146+ parts: ['b1l1', 'b1l2', 'b1l3', 'b4t2', 'b4t3'],
147+ },
148+};
ui/src/types/store.ts+66−4
@@ -1,16 +1,78 @@
11import type { ConnectionStatus } from './connection';
2−import type { Patp, Group, Contact } from '@urbit/api';
2+import type { Patp, Group, Contact, Rolodex } from '@urbit/api';
3+
4+export function createEmptyContact(): Contact {
5+ return {
6+ nickname: null, bio: null, status: null, color: null, avatar: null,
7+ cover: null, groups: [], 'last-updated': 0,
8+ };
9+}
10+export interface Contacts {
11+ [contact: string]: Contact;
12+};
13+
14+export interface Docs {
15+ [doc: string]: string;
16+};
17+
18+export interface PalNames {
19+ [patp: Patp]: boolean;
20+}
21+
22+export interface Pal {
23+ tags: string[];
24+ ack: boolean;
25+}
26+
27+export interface PalList {
28+ [patp: Patp]: Pal;
29+}
30+
31+export interface Pals {
32+ incoming: PalNames;
33+ outgoing: PalList;
34+ mutuals: PalList;
35+}
36+
37+export const PAL_STATUSES = ['mutual', 'outgoing', 'incoming', 'unconnected'] as const;
38+export type PalStatus = typeof PAL_STATUSES[number];
39+export interface PalInfo extends Pal {
40+ patp: Patp;
41+ status: PalStatus;
42+}
43+
44+export interface Apps {
45+ allies: {
46+ [ally: string]: string[],
47+ },
48+ set: Set<string>,
49+}
350
451export interface StoreState {
552 // Urbit
653 // groups: {
754 // [group: string]: Group;
855 // };
9− contacts: {
10− [contact: string]: Contact;
11− };
56+ contacts: Rolodex;
57+ peers: Patp[];
58+ pals: Pals;
59+ palsInstalled: boolean;
60+ apps: Apps,
61+ patpQuery: RegExp;
62+ sigilQuery: string[][];
63+ searchMode: ('patp' | 'sigil');
64+ // contacts: Contacts;
65+ // contactSearchResults: Contacts;
1266 // chats: [];
1367 connection: ConnectionStatus;
1468 subscription?: unknown;
1569 ship: Patp;
1670}
71+export type SearchedContactsState = Rolodex;
72+
73+export class SearchSettings {
74+ includeMoons: boolean = true;
75+ includeComets: boolean = false;
76+
77+ allowFictionalSigils: boolean = false;
78+}
ui/svelte.config.js+2−2
@@ -1,4 +1,4 @@
1−import sveltePreprocess from 'svelte-preprocess'
1+import sveltePreprocess from 'svelte-preprocess';
22
33export default {
44 // Consult https://github.com/sveltejs/svelte-preprocess
@@ -10,4 +10,4 @@ export default {
1010
1111 handler(warning)
1212 },
13−}
13+};
ui/tailwind.config.cjs+38−3
@@ -1,12 +1,47 @@
1+const defaultTheme = require('tailwindcss/defaultTheme')
2+
13module.exports = {
24 mode: 'jit',
35 content: [
46 './index.html',
5− './src/**/*.{js,ts,jsx,tsx,svelte}',
7+ './src/**/*.{js,ts,jsx,tsx,svelte,css,scss}',
68 ],
79 theme: {
8− extend: {}
10+ extend: {
11+ colors: {
12+ navy: '#1f243c',
13+ gold: '#fcd34d',
14+ 'orange-gold': '#f7d392',
15+ 'l1-blue': '#1792c7',
16+ 'l2-green': '#17c76a',
17+ 'you': '#b01',
18+ 'gold-s1': '#e3be45',
19+ 'gold-s2': '#caa93e',
20+ 'gold-s3': '#b09436',
21+ 'gold-s4': '#977f2e',
22+ 'gold-s5': '#7e6a27',
23+ 'gold-s6': '#65541f',
24+ 'gold-s7': '#4c3f17',
25+ 'gold-s8': '#322a0f',
26+ 'gold-s9': '#191508',
27+ 'gold-t1': '#fcd75f',
28+ 'gold-t2': '#fddc71',
29+ // 'gold-t3': 'b09436',
30+ 'gold-t4': '#fde594',
31+ // 'gold-t5': '7e6a27',
32+ 'gold-t6': '#feedb8',
33+ // 'gold-t7': '4c3f17',
34+ 'gold-t8': '#fef6db',
35+ // 'gold-t9': '191508',
36+ }
37+ },
38+ screens: {
39+ '2xs': '360px',
40+ 'xs': '475px',
41+ ...defaultTheme.screens,
42+ },
43+ },
44+ screens: {
945 },
10− screens: {},
1146 plugins: []
1247};
ui/tsconfig.json+9
@@ -7,6 +7,15 @@
77 "module": "esnext",
88 "resolveJsonModule": true,
99 "baseUrl": ".",
10+ "paths": {
11+ "~/*": ["src/*"],
12+ "@/*": ["src/components/*"],
13+ "lib/*": ["src/lib/*"],
14+ "stores/*": ["src/stores/*"],
15+ "assets/*": ["src/assets/*"],
16+ "actions/*": ["src/actions/*"],
17+ "types/*": ["src/types/*"],
18+ },
1019 /**
1120 * Typecheck JS in `.svelte` and `.js` files by default.
1221 * Disable checkJs if you'd like to use dynamic types in JS.
ui/vite.config.ts+22−2
@@ -1,14 +1,34 @@
11import { loadEnv, defineConfig } from 'vite';
22import { svelte } from '@sveltejs/vite-plugin-svelte'
33import { urbitPlugin } from '@urbit/vite-plugin-urbit';
4+import path from 'path';
45
56// https://vitejs.dev/config/
67export default ({ mode }) => {
78 Object.assign(process.env, loadEnv(mode, process.cwd()));
89 const SHIP_URL = process.env.SHIP_URL || process.env.VITE_SHIP_URL || 'http://localhost:8080';
9− console.log(SHIP_URL);
10+ console.log('SHIP_URL', SHIP_URL);
1011
1112 return defineConfig({
12− plugins: [urbitPlugin({ base: 'astrolabe', target: SHIP_URL, secure: false }), svelte()]
13+ plugins: [
14+ urbitPlugin({ base: 'astrolabe', target: SHIP_URL, secure: false }),
15+ svelte(),
16+ ],
17+ resolve: {
18+ alias: {
19+ '~': path.resolve('./src'),
20+ '@': path.resolve('./src/components'),
21+ lib: path.resolve('./src/lib'),
22+ stores: path.resolve('./src/stores'),
23+ assets: path.resolve('./src/assets'),
24+ actions: path.resolve('./src/actions'),
25+ types: path.resolve('./src/types'),
26+ },
27+ },
28+ server: {
29+ fs: {
30+ strict: false,
31+ },
32+ },
1333 });
1434};