on master help sign in
Merge branch 'urbit:master' into update-dill-docs
commit21841bff713f2a15acc36db14d21bec0898ba0f2 authortinnus-napbus <77721746+tinnus-napbus@users.noreply.github.com> dateWed 15 Sep 2021, 16:25 (5 years ago) parent82944e573e5deb
63 files changed+1335−611
.npmrc+1 components/Contact.js+1−7 components/EventPreview.js+17−12 components/Footer.js+1−6 components/Markdown.js+3−1 components/Section.js+2−2 components/Snippets.js+44 components/TableOfContents.js+5−2 components/TwoUp.js+12 components/icons/Cross.js+11 content/blog/haleek-maul-interview.md+71 content/blog/urbithost-interview.md+2−2 content/docs/arvo/clay/examples.md+26−58 content/docs/arvo/eyre/tasks.md+15−15 content/docs/arvo/iris/tasks.md+3−3 content/docs/arvo/jael/scry.md+15−15 content/docs/arvo/jael/tasks.md+15−15 content/docs/hoon/reference/cheat-sheet.md+3−13 content/docs/hoon/reference/style.md+108−108 content/docs/userspace/graph-store/sample-application-overview.md+369 content/docs/userspace/overview.md+5 content/docs/userspace/threads/basics/summary.md+3−3 content/docs/userspace/threads/examples/child-thread.md+33−33 content/docs/userspace/threads/examples/get-json.md+5−4 content/docs/userspace/threads/examples/main-loop.md+8−8 content/docs/userspace/threads/examples/poke-agent.md+2−2 content/docs/userspace/threads/examples/scry.md+10−10 content/docs/userspace/threads/examples/take-fact.md+2−2 content/docs/userspace/threads/gall/poke-thread.md+8−9 content/docs/userspace/threads/gall/start-thread.md+9−9 content/docs/userspace/threads/gall/stop-thread.md+8−8 content/docs/userspace/threads/gall/take-facts.md+8−8 content/docs/userspace/threads/gall/take-result.md+9−9 content/docs/userspace/threads/http-api.md+1−1 content/docs/userspace/threads/reference.md+5−13 content/events/2021-09-16-developer-call-azimuth-scaled.md+46 content/events/2021-09-24-urbit-outside.md+23 content/events/2021-10-15-assembly.md+26 content/getting-started/planet.md+4−2 content/grants/a-collaborative-web-mapping-system-for-urbit.md → content/grants/a-network-enabled-spatial-database-and-demonstration-app.md+26−21 content/grants/ethereum-abi-parser.md+1 content/grants/machine-learning.md+1−1 content/grants/matrices-in-hoon.md+1−1 content/grants/planet-sales.md+1−1 content/grants/urbit-notes.md+1−1 content/grants/~home-urbit.md+120 content/using/id/guide-to-breaches.md+1−3 content/using/os/getting-started.md+3−3 content/using/os/ship-troubleshooting.md+2−2 lib/constants.js+55−1 lib/hooks.js+22 lib/lib.js+1−1 next.config.js+1 package-lock.json+5 package.json+2−1 pages/blog/[slug].js+9−18 pages/events.js+5−19 pages/events/[slug].js+37−90 pages/grants.js+10−3 pages/grants/[slug].js+2−1 pages/index.js+66−37 styles/globals.css+22−25 styles/markdown.css+2−2
.npmrcnew file+1
@@ -0,0 +1 @@
1+unsafe-perm = true
components/Contact.js+1−7
@@ -79,13 +79,7 @@ export default function Contact({ emphasize }) {
7979 Github
8080 </a>
8181 </h4>
82− <h4 className="mt-6 text-wall-500">
83− Ask questions in our{" "}
84− <a className={linkText} href={contact.discord}>
85− Discord
86− </a>
87− </h4>
88− <h4 className="mt-6 text-wall-500 flex items-baseline flex-wrap">
82+ <h4 className="mt-4 text-wall-500 flex items-baseline flex-wrap">
8983 Boot Urbit and join{" "}
9084 <div className="md:ml-2 rounded-lg flex items-stretch">
9185 <code
components/EventPreview.js+17−12
@@ -1,6 +1,11 @@
11import Link from "next/link";
22import { DateTime } from "luxon";
3−import { ShowOrHide, ReadableList, Person } from "../components/Snippets";
3+import {
4+ ShowOrHide,
5+ ReadableList,
6+ Person,
7+ DateRange,
8+} from "../components/Snippets";
49import {
510 generateDisplayDate,
611 generateRealtimeDate,
@@ -11,7 +16,7 @@ import {
1116
1217export default function EventPreview({ event, className, big }) {
1318 // Event tiles have a 'dark mode' used when their background images are dark and white text is needed for legibility.
14− const grayText = event?.dark ? "text-midWhite" : "text-wall-400";
19+ const grayText = event?.dark ? "text-washedWhite" : "text-wall-400";
1520 const blackText = event?.dark ? "text-white" : "text-wall-600";
1621
1722 const starts = generateDisplayDate(event.starts, event.timezone);
@@ -23,7 +28,9 @@ export default function EventPreview({ event, className, big }) {
2328 generateRealtimeDate(event.starts) > DateTime.now() && !inFuture;
2429
2530 return (
26− <div className={`cursor-pointer aspect-w-5 aspect-h-4 ${className}`}>
31+ <div
32+ className={`cursor-pointer aspect-w-4 aspect-h-5 md:aspect-w-5 md:aspect-h-4 ${className}`}
33+ >
2734 <div
2835 key={event.slug}
2936 className={`bg-wall-100 rounded-xl bg-cover bg-center bg-no-repeat `}
@@ -38,15 +45,13 @@ export default function EventPreview({ event, className, big }) {
3845 big ? "justify-center" : ""
3946 } flex-col h-full`}
4047 >
41− <h4 className={`${blackText}`}>{event.title}</h4>
42− <p className={grayText}>{formatDate(starts)}</p>
43− <p className={grayText}>
44− {formatTime(starts)}
45− {typeof event.ends !== "undefined"
46− ? ` to ${formatTime(ends)}`
47− : null}
48− {" " + formatTimeZone(starts)}
49− </p>
48+ <h3 className={`${blackText} mb-2`}>{event.title}</h3>
49+ <p className={`${blackText} mb-2`}>{event.location}</p>
50+ <DateRange
51+ starts={starts}
52+ ends={ends}
53+ className={`${grayText} type-sub`}
54+ />
5055 </div>
5156
5257 <div className="absolute p-6 left-0 bottom-0 w-full pr-32">
components/Footer.js+1−6
@@ -5,7 +5,7 @@ import { contact } from "../lib/constants";
55
66export default function Footer() {
77 return (
8− <footer className="bg-wall-100 w-full mt-48 flex justify-center">
8+ <footer className="bg-wall-100 mt-20 w-full flex justify-center">
99 <SingleColumn>
1010 <Section short className="flex flex-row flex-wrap">
1111 <div className="w-1/2 md:w-1/3 flex flex-col flex-shrink">
@@ -126,11 +126,6 @@ export default function Footer() {
126126 <a className="type-bold text-wall-500">Dev Mailing List</a>
127127 </div>
128128 </Link>
129− <Link href="https://discord.gg/CfSKKaB2ep">
130− <div>
131− <a className="mt-2 type-bold text-wall-500">Support Discord</a>
132− </div>
133− </Link>
134129 <Link href="https://github.com/urbit/azimuth">
135130 <div>
136131 <a className="mt-2 type-bold text-wall-500">Governance</a>
components/Markdown.js+3−1
@@ -38,7 +38,9 @@ export default async function Markdown({ post }) {
3838 const result = await remark()
3939 .use(html, options)
4040 .use(gfm)
41− .use(remarkprism)
41+ .use(remarkprism, {
42+ plugins: ["show-invisibles"],
43+ })
4244 .use(slug)
4345 .use(heading)
4446 .process(post.content);
components/Section.js+2−2
@@ -8,8 +8,8 @@ export default function Section({
88 narrow = false,
99}) {
1010 const spacing = classnames({
11− "py-4 md:py-6 lg:py-8": short,
12− "py-8 md:py-10 lg:py-20": !short,
11+ "py-8": short,
12+ "py-20": !short,
1313 "layout-narrow": narrow,
1414 layout: !narrow,
1515 });
components/Snippets.js+44
@@ -1,3 +1,12 @@
1+import {
2+ getAllEvents,
3+ generateDisplayDate,
4+ generateRealtimeDate,
5+ formatDate,
6+ formatTime,
7+ formatTimeZone,
8+} from "../lib/lib";
9+
110export function Name({ children, className }) {
211 return <b className={`font-normal ${className || ""}`}>{children}</b>;
312}
@@ -61,3 +70,38 @@ export function ShowOrHide({ children, condition }) {
6170 }
6271 return null;
6372}
73+
74+export function DateRange({ starts, ends, className, short }) {
75+ // For events which have no end datetime
76+ if (!ends.isValid) {
77+ return (
78+ <div>
79+ <p className={className}>
80+ {`${formatDate(starts)} • ${formatTime(starts)} ${formatTimeZone(
81+ starts
82+ )}`}
83+ </p>
84+ </div>
85+ );
86+ }
87+ // For events which start and end on the same day
88+ if (starts.hasSame(ends, "day")) {
89+ return (
90+ <div>
91+ <p className={className}>
92+ {`${formatDate(starts)} • ${formatTime(starts)} to ${formatTime(
93+ ends
94+ )} ${formatTimeZone(starts)}`}
95+ </p>
96+ </div>
97+ );
98+ }
99+ // For multi-day events
100+ return (
101+ <div>
102+ <p className={className}>{`${starts.toFormat(
103+ "cccc, LLLL d"
104+ )} to ${formatDate(ends)}`}</p>
105+ </div>
106+ );
107+}
components/TableOfContents.js+5−2
@@ -52,9 +52,12 @@ const useHeadingsData = (noh3s) => {
5252
5353const Headings = ({ headings, activeId }) => (
5454 <ul>
55− {headings.map((heading) => (
55+ {headings.map((heading, index) => (
5656 <li key={heading.id}>
57− <a className="font-semibold" href={`#${heading.id}`}>
57+ <a
58+ className={index === 0 ? "font-bold" : "font-medium"}
59+ href={`#${heading.id}`}
60+ >
5861 {heading.title}
5962 </a>
6063 {heading.items.length > 0 && (
components/TwoUp.jsnew file+12
@@ -0,0 +1,12 @@
1+export default function TwoUp({ children, className = "" }) {
2+ return (
3+ <div className={`flex flex-wrap w-full ${className}`}>
4+ <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
5+ {children[0] || null}
6+ </div>
7+ <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
8+ {children[1] || null}
9+ </div>
10+ </div>
11+ );
12+}
components/icons/Cross.jsnew file+11
@@ -0,0 +1,11 @@
1+export default function Cross(props) {
2+ return (
3+ <svg {...props} viewBox="0 0 12 12">
4+ <path
5+ fillRule="evenodd"
6+ clipRule="evenodd"
7+ d="M0.292893 10.2942C-0.0976311 10.6847 -0.0976311 11.3179 0.292893 11.7084C0.683417 12.0989 1.31658 12.0989 1.70711 11.7084L6.00063 7.41487L10.2948 11.7091C10.6853 12.0996 11.3185 12.0996 11.709 11.7091C12.0996 11.3185 12.0996 10.6854 11.709 10.2949L7.41484 6.00066L11.7084 1.70711C12.0989 1.31658 12.0989 0.683417 11.7084 0.292894C11.3179 -0.0976312 10.6847 -0.0976312 10.2942 0.292894L6.00063 4.58645L1.70775 0.293571C1.31723 -0.0969534 0.684061 -0.0969534 0.293536 0.293571C-0.0969879 0.684095 -0.0969879 1.31726 0.293536 1.70778L4.58641 6.00066L0.292893 10.2942Z"
8+ />
9+ </svg>
10+ );
11+}
content/blog/haleek-maul-interview.mdnew file+71
@@ -0,0 +1,71 @@
1++++
2+title = "NFTs, Urbit IDs, and Communities w/ Haleek Maul"
3+date = "2021-09-09"
4+description = "An interview with the founder of Holdersland"
5+[extra]
6+author = "Matt"
7+ship = "~tirwyd-sarmes"
8+image = "https://media.urbit.org/site/posts/essays/haleek_sigil.jpg"
9++++
10+
11+![](https://media.urbit.org/site/posts/essays/haleek_sigil.jpg)
12+
13+*Check out the Holdersland x Urbit collection: [holders.land/urbit](https://holders.land/urbit)*
14+
15+We sat down recently with Haleek Maul to discuss the current state of NFTs and the future of Urbit IDs and digital communities.
16+
17+Are Urbit IDs NFTs? Sure, they're ERC-721 non-fungible tokens which you can buy on OpenSea—but to be clear, we made them way back before the word "NFT" had entered mainstream consciousness. An Urbit ID is a number that is used to generate both a phonetic name and an avatar (what we call [sigils](https://urbit.org/blog/creating-sigils)), but it is so much more than your average NFT—it is a key to an entire digital world.
18+
19+We built Urbit IDs for digital communities and the people they’re made of. An Urbit ID is a decentralized and pseudonymous identity perfect for your collective, commune, squad, team, or DAO. Unlike most addresses or social identities, you own your Urbit ID like you own your crypto. Your Discord handle might be free for now, but as the saying goes, if it’s free then you’re the product. On Urbit your ID is actually yours and you pay for it up front. You can log into third party apps or software without ever relinquishing control of your data. What is yours stays with you. IDs themselves can be owned collectively—there are already DAOs on Urbit who jointly own stars and galaxies.
20+
21+Urbit IDs are evolving and generative. They’re generative because some IDs spawn additional NFTs which themselves can be bought and sold. For instance, each star can spawn 65,535 planets, and each galaxy can spawn 255 stars. The network itself is evolving, meaning that the network can be upgraded via a majority vote by galaxy holders. Urbit IDs and the Ubit network itself are inherently dynamic.
22+
23+At Urbit, we support communities of all kinds. We recently granted Holdersland ten Urbit IDs: four stars and six planets to be auctioned off. Haleek Maul (his Urbit ID is `~fosmex-hocmel`) will be creating custom artwork for each of the sigils. The proceeds of the auction will help support Holdersland in bringing blockchain technologies to the Caribbean. The auction will go live on September 16th, 2021 on [holders.land/urbit](https://holders.land/urbit).
24+
25+Without further ado, here’s Haleek Maul.
26+
27+---
28+
29+**Tell us a little bit about yourself and Holdersland.**
30+
31+Sure. My name is Haleek Maul and I’m a multi-disciplinary artist. I was born in America and came to Barbados when I was three years old. I’ve been making music since 2007. I put out my first debut project in 2012 and since then it has been a process of creating art from the stuff flowing through my mind.
32+
33+Holdersland is a digital extension of my artistic project—a way of creating change in the world around me. When I was younger, there weren’t many artists like me coming from Barbados. I was into a lot of niche electronic and club music and felt alienated from the larger conversation. Holdersland is a way to help creative people in the Caribbean connect with like-minded people around the world, especially using new technologies like blockchain. So I founded Holdersland in 2020 when I left my record label after four years. I felt like music institutions was archaic and didn’t feed into my other artistic practices. I wanted to create an institution that captured the full creative practice—all the way from creating to selling. Most importantly, I wanted to get artists from Barbados and the Caribbean involved in global conversations around art and technology.
34+
35+**What exactly is Holdersland?**
36+
37+It’s a company centered around Caribbean intellectual property. We’re trying to help people get connected with new web3 technologies, especially artists. Holdersland is an aggregator that helps connects those who are interested in new technology with those those who actually develop it.
38+
39+For example, there is a lot of math that underlies music. Part of our project is to extract these mathematical principles and see how they can help generate new forms of art that can be widely distributed. So we’re working to build our own projects and work with users to adopt some of these new technologies. It’s a community of people centered around these values. We plan to implement voting, governance, and incentive structures into the community soon.
40+
41+**What do you think is the link between your two main interests: music and technology?**
42+
43+I’ve always been interested in technology. Anyone who has made music in the last twenty years has had to get involved with tech. Especially if you’re a producer. There are many young kids who are downloading music software and messing around with it, and in the process becoming knowledgeable about music production and design. Science and math live right beside art.
44+
45+I was also into web development when I was a teenager and was very interested in how the internet worked. This led into the work I’m doing with Holdersland. Technology, more often than not, is concerned with collaboration and communication. But in order to develop new technologies we often need to talk about nitty-gritty details. This requires detailed knowledge of particular technologies.
46+
47+**What is the great hope or promise with new decentralized technologies like blockchain or Urbit?**
48+
49+They lower the barrier to communication and trade. Especially for countries like Barbados, we don’t have access to the world market. But now with these networks, and assets like NFTs, we can engage in commerce without borders. It allows new entrants to join the market.
50+
51+It also allows new financial relationships and funding structures to emerge. Rather than have a few people contribute $20k, you can have hundreds of people contribute $100. Once again, this lowers the entry for people to invest is early-stage projects. This changes the way that companies build and ship products.
52+
53+Brain drain is when skilled workers move abroad for more profitable employment. These new technologies allow native Carribeans to stay where they are while earning money. Using these new technologies, the Carribean can prosper while not losing its unique character and cultural identity. Crypto is permanently going to change the distribution of wealth and culture around the world.
54+
55+Discord is a fine community solution, but it’s not ideal. There’s an endless array of plugins and dependencies that makes Discord unstable. However, it is a good mixture of an office, a storefront, and a club: I can do my work, sell assets, and just have fun. Communities are important. People naturally want to be in communities, we just need a better solution than Discord. Especially one that’s crypto-native and allows for commerce. Urbit is a one-stop shop that could fill that gap.
56+
57+**What is the collaboration between Holdersland and Urbit that’s happening this week?**
58+
59+I’m producing some custom sigil artwork for a set of stars and planets. Both the Urbit assets and artwork will be auctioned off together. The proceeds from the event will go towards creating a community music studio here in Barbados and technology education around the island.
60+
61+A lot of people here in Barbados don’t even understand what crypto is. A lot of the work being done around this project is to raise awareness about the potentials of this new technology. I want this to be a general springboard for the Caribbean to gain greater technological literacy. That literacy is an important step for lower-income people to drastically change their situation.
62+
63+**What are you looking forward to talking about on the New World Energy panel at Assembly 2021?**
64+
65+I want to talk about how to migrate communities onto Urbit. How to use Urbit as a new platform for niche cultures found around the current internet. Also the great strength and power provided by Urbit’s digital identities.
66+
67+NFTs can be very powerful—there are a lot of possible applications of a unique digital asset. It’s already dissolved geographical boundaries which will help countries to export their intellectual products freely. Countries need first to gain knowledge around these technologies and their economies will follow.
68+
69+**Thanks for chatting!**
70+
71+See you at Assembly 2021!
content/blog/urbithost-interview.md+2−2
@@ -5,10 +5,10 @@ description = "Interview with the founder of UrbitHost ~lavlyn-litmeg"
55[extra]
66author = "Matt"
77ship = "~tirwyd-sarmes"
8−image = "https://media.urbit.org/site/posts/essays/urbithost.png"
8+image = "https://media.urbit.org/site/posts/essays/nasa-star-cluster.jpg"
99+++
1010
11−![](https://media.urbit.org/site/posts/essays/urbithost.png)
11+![](https://media.urbit.org/site/posts/essays/nasa-star-cluster.jpg)
1212
1313**An Interview with UrbitHost**
1414
content/docs/arvo/clay/examples.md+26−58
@@ -4,38 +4,6 @@ weight = 7
44template = "doc.html"
55+++
66
7−# Contents
8−
9−- [Introduction](#introduction)
10−- [%warp](#warp) - Read and Subscribe.
11− - [%sing](#sing) - Read a file or directory.
12− - [%next](#next) - Subscribe for the next change to a file or directory.
13− - [%mult](#mult) - Subscribe for the next change to a set of files and/or directories.
14− - [%many](#many) - Track changes to a `desk` for the specified range of revisions.
15− - [Cancel Subscription](#cancel-subscription)
16−- [%info](#info) - Write and Modify.
17− - [%ins](#ins) - Add a file.
18− - [%del](#del) - Delete a file.
19− - [%mut](#mut) - Change a file.
20− - [Multiple Changes](#multiple-changes) - Change multiple files.
21−- [Manage Mounts](#manage-mounts)
22− - [%boat](#boat) - List mounts.
23− - [%mont](#mont) - Mount something.
24− - [%ogre](#ogre) - Unmount something.
25− - [%dirk](#dirk) - Commit changes.
26−- [Merge Desks](#merge-desks)
27− - [%merg](#merg)
28−- [Permissions](#permissions)
29− - [%perm](#perm) - Set file permissions.
30− - [%cred](#cred) - Add permission group.
31− - [%crew](#crew) - Get permission groups.
32− - [%crow](#crow) - Get group usage.
33−- [Foreign Ships](#foreign-ships)
34− - [%warp](#warp-remote) - Read a file on a foreign ship.
35− - [%merg](#merg-remote) - Merge from a foreign `desk`.
36−
37−# Introduction
38−
397This document contains a number of examples of interacting with Clay using its various `task`s. Sections correspond to the general details in the [API Reference](/docs/arvo/clay/tasks) document.
408
419Most examples will either use `|pass` to just send a `task` or the following thread to send a `task` and take the resulting `gift`. You can save the following thread to the `ted` directory of your `%home` `desk`:
@@ -61,11 +29,11 @@ Most examples will either use `|pass` to just send a `task` or the following thr
6129(pure:m !>(~))
6230```
6331
64−# `%warp`
32+## `%warp`
6533
6634See the [Read and Subscribe](/docs/arvo/clay/tasks#warp---read-and-track) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
6735
68−## `%sing`
36+### `%sing`
6937
7038Here we'll look at reading files by passing Clay a `%warp` `task` with a `%sing` `rave` and receiving a `%writ` `gift` containing the data in response.
7139
@@ -115,7 +83,7 @@ Here's a breakdown of the `task` we sent:
11583
11684![%sing diagram](https://media.urbit.org/docs/arvo/clay/sing.png "%sing diagram")
11785
118−## `%next`
86+### `%next`
11987
12088Here we'll look at subscribing to the next version of a file by passing Clay a `%warp` `task` with a `%next` `rave` and receiving a `%writ` `gift` when the file changes.
12189
@@ -151,7 +119,7 @@ Here's a breakdown of the task we sent:
151119
152120![%next diagram](https://media.urbit.org/docs/arvo/clay/next.png "%next diagram")
153121
154−## `%mult`
122+### `%mult`
155123
156124Here we'll look at subscribing to the next version of multiple files by passing Clay a `%warp` `task` with a `%mult` `rave` and receiving a `%wris` `gift` when any of the files change.
157125
@@ -210,7 +178,7 @@ Here's a breakdown of the `task` we sent:
210178
211179![subscribe mult diagram](https://media.urbit.org/docs/arvo/clay/sub-mult.png "subscribe mult diagram")
212180
213−## `%many`
181+### `%many`
214182
215183Here we'll look at subscribing to a range of changes to a `desk` by passing Clay a `%warp` `task` with a `%many` `rave` and receiving `%writ` `gift`s when changes occur.
216184
@@ -304,7 +272,7 @@ Here's a breakdown of the `task` we sent:
304272
305273![subscribe many diagram](https://media.urbit.org/docs/arvo/clay/sub-many.png "subscribe many diagram")
306274
307−## Cancel Subscription
275+### Cancel Subscription
308276
309277Here we'll look at cancelling a subscription by sending Clay a `%warp` `task` with a null `(unit rave)` in the `riff`.
310278
@@ -348,11 +316,11 @@ Here's a breakdown of the `task` we sent:
348316
349317![cancel subscription diagram](https://media.urbit.org/docs/arvo/clay/stop-sub.png "cancel subscription diagram")
350318
351−# `%info`
319+## `%info`
352320
353321See the [Write and Modify](/docs/arvo/clay/tasks#write-and-modify) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
354322
355−## `%ins`
323+### `%ins`
356324
357325Here we'll look at adding a file by sending Clay a `%info` `task` containing a `%ins` `miso`.
358326
@@ -371,7 +339,7 @@ Here's a breakdown of the `task` we sent:
371339
372340![%ins diagram](https://media.urbit.org/docs/arvo/clay/ins.png)
373341
374−## `%del`
342+### `%del`
375343
376344Here we'll look at deleting a file by sending Clay a `%info` `task` containing a `%del` `miso`.
377345
@@ -388,11 +356,11 @@ Here's a breakdown of the `task` we sent:
388356
389357![%del diagram](https://media.urbit.org/docs/arvo/clay/del.png)
390358
391−## `%mut`
359+### `%mut`
392360
393361Identical to the [%ins](#ins) example, just replace `%ins` with `%mut`.
394362
395−## Multiple Changes
363+### Multiple Changes
396364
397365Here we'll look at changing multiple files in one request by sending Clay a `%info` `task` containing multiple `miso` in the `soba`.
398366
@@ -433,11 +401,11 @@ Save to `ted/multi-change.hoon`, `|commit %home`, and run:
433401- /~zod/home/38/baz/txt
434402```
435403
436−# Manage Mounts
404+## Manage Mounts
437405
438406See the [Manage Mounts](/docs/arvo/clay/tasks#manage-mounts) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
439407
440−## `%boat`
408+### `%boat`
441409
442410Here we'll look at requesting the list of existing mount points on a ship by sending Clay a `%boat` `task` and receiving a `%hill` `gift`.
443411
@@ -448,7 +416,7 @@ Using the `send-task-take-gift.hoon` thread, let's make such a request:
448416[%hill p=~[%home]]
449417```
450418
451−## `%mont`
419+### `%mont`
452420
453421Here we'll look at mounting `desk`s, directories and files to unix by sending Clay a `%mont` `task`.
454422
@@ -487,7 +455,7 @@ Let's try mounting a subdirectory and a single folder:
487455
488456If you look in your pier you'll now see a `gen` folder with the contents of `/gen` and a `hi.hoon` file by itself. Notice how the file extension has been automatically added.
489457
490−## `%ogre`
458+### `%ogre`
491459
492460Here we'll look at unmounting `desk`s, directories and files by sending Clay a `%ogre` `task`.
493461
@@ -518,7 +486,7 @@ If we specify a non-existent mount point it will fail with an error printed to t
518486
519487If we give it an unmounted `beam` it will not print an error but still won't work.
520488
521−## `%dirk`
489+### `%dirk`
522490
523491Here we'll look at committing changed files by sending Clay a `%dirk` `task`.
524492
@@ -535,11 +503,11 @@ Clay will print the changed files to the dojo with a leading `+`, `-` or `:` to
535503
536504If you have the same `desk` mounted to multiple points, a committed change in one mount will also update the others.
537505
538−# Merge Desks
506+## Merge Desks
539507
540508See the [Merge Desks](/docs/arvo/clay/tasks#merge-desks) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
541509
542−## `%merg`
510+### `%merg`
543511
544512Here we'll look at merging `desk`s by sending Clay a `%merg` `task` and receiving a `%mere` `gift` in response.
545513
@@ -610,11 +578,11 @@ Now change the text in the `foo.txt` in the `%home` `desk`, hit backspace to det
610578
611579You can also specify it by revision number or label.
612580
613−# Permissions
581+## Permissions
614582
615583See the [Permissions](/docs/arvo/clay/tasks#permissions) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
616584
617−## `%perm`
585+### `%perm`
618586
619587Here we'll look at setting permissions by sending Clay a `%perm` `task`.
620588
@@ -694,7 +662,7 @@ Here's a breakdown of a `%perm` task:
694662
695663![perm diagram](https://media.urbit.org/docs/arvo/clay/perm-diagram.png)
696664
697−## `%cred`
665+### `%cred`
698666
699667Here we'll look at creating a permission group by sending Clay a `%cred` `task`.
700668
@@ -706,7 +674,7 @@ Let's create a group called `'foo'` with a few ships:
706674
707675We'll check it with the next kind of `task`: [%crew](#crew).
708676
709−## `%crew`
677+### `%crew`
710678
711679Here we'll look at retrieving permission groups by sending Clay a `%crew` `task` and receiving a `%cruz` `gift` in response.
712680
@@ -717,7 +685,7 @@ Let's check, using the `send-task-take-gift.hoon` thread, for the permission gro
717685[%cruz cez={[p=~.foo q={~nec ~bud ~wes ~zod ~sev}]}]
718686```
719687
720−## `%crow`
688+### `%crow`
721689
722690Here we'll look at retrieving a list of all files and directories in all `desk`s which have permissions set for a group by sending Clay a `%crow` `task` and receiving a `%croz` `gift` in response.
723691
@@ -749,11 +717,11 @@ Now we'll use the `send-task-take-gift.hoon` thread to try `%crow`:
749717]
750718```
751719
752−# Foreign Ships
720+## Foreign Ships
753721
754722See the [Foreign Ships](/docs/arvo/clay/tasks#foreign-ships) section of the [API Reference](/docs/arvo/clay/tasks) document for general details.
755723
756−## `%warp` - Remote
724+### `%warp` - Remote
757725
758726Here we'll look at reading files on a foreign ship by sending Clay a `%warp` `task` with a foreign ship in the `wer` field and receiving a `%writ` `gift` in response.
759727
@@ -816,7 +784,7 @@ call: failed
816784/sys/vane/clay/hoon:<[1.365 48].[1.365 50]>
817785```
818786
819−## `%merg` - Remote
787+### `%merg` - Remote
820788
821789To merge a foreign `desk` into a local one, you just send Clay a `%merg` `task` (as you would for a local merge) and specify the foreign ship in the `her` field. For an example, see the [%merg](#merg) section.
822790
content/docs/arvo/eyre/tasks.md+15−15
@@ -20,7 +20,7 @@ This `task` notifies Eyre of the listening HTTPS and HTTP ports. It is automatic
2020
2121The `insecure` field is the HTTP port and `secure` is the optional HTTPS port.
2222
23−### Returns
23+#### Returns
2424
2525Eyre returns no `gift` in response to a `%live` `task`.
2626
@@ -34,7 +34,7 @@ This `task` either configures HTTPS with a certificate and keypair, or configure
3434
3535The [$http-rule](/docs/arvo/eyre/data-types#http-rule) is either tagged with `%cert` or `%turf`. A `%cert` `http-rule` sets an HTTPS certificate and keypair or removes it if null. A `%turf` `http-rule` either adds or removes a DNS binding depending on whether the `action` is `%put` or `%del`. Note that using `%turf` will automatically cause the system to try and obtain a certificate and keypair via Letsencrypt.
3636
37−### Returns
37+#### Returns
3838
3939Eyre returns no `gift` in response to a `%rule` `task`.
4040
@@ -48,7 +48,7 @@ This `task` is how Eyre receives an inbound HTTP request. It will ordinarily be
4848
4949The `secure` field says whether it's over HTTPS. The `address` is the IP address from which the request originated. The [$request:http](/docs/arvo/eyre/data-types#requesthttp) is the HTTP request itself containing the method, URL, headers, and body.
5050
51−### Returns
51+#### Returns
5252
5353Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors.
5454
@@ -60,7 +60,7 @@ Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the
6060
6161This `task` is how Eyre receives an inbound HTTP request over the local loopback port. It behaves the same and takes the same arguments as in the [%request](#request) example except it skips any normally required authentication. Just like for a [%request](#request) `task`, you'd not normally use this manually.
6262
63−### Returns
63+#### Returns
6464
6565Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the contents of the `%request`, state of the connection, and other factors.
6666
@@ -74,7 +74,7 @@ This `task` is sent to Eyre by the runtime when an HTTP client closes its previo
7474
7575This `task` takes no arguments.
7676
77−### Returns
77+#### Returns
7878
7979Eyre may `pass` a `%response` `gift` on the appropriate `duct` depending on the state of the connection and other factors.
8080
@@ -98,7 +98,7 @@ The `cage` of the second and subsequent `fact`s must have a `mark` of `%http-res
9898
9999Finally, once you've sent all the `fact`s you want, you can `kick` Eyre's subscription and it will complete the response and close the connection to the HTTP client.
100100
101−### Returns
101+#### Returns
102102
103103Eyre will respond with a `%bound` `gift` which says whether the binding was successful and looks like:
104104
@@ -108,7 +108,7 @@ Eyre will respond with a `%bound` `gift` which says whether the binding was succ
108108
109109The `accepted` field says whether the binding succeeded and the `binding` is the requested binding described above.
110110
111−### Example
111+#### Example
112112
113113See the [Direct HTTP Handling With Gall Agents](/docs/arvo/eyre/examples#direct-http-handling-with-gall-agents) section of the [Examples](/docs/arvo/eyre/examples) document for an example.
114114
@@ -140,11 +140,11 @@ The `?` says whether the HTTP request contained a valid session cookie and the [
140140
141141The `simple-payload:http` returned by the generator is similar to the response described in the [%connect](#connect) section except the HTTP headers and body are all contained in the one response rather than staggered across several.
142142
143−### Returns
143+#### Returns
144144
145145Eyre will return a `%bound` `gift` as described at the end of the [%connect](#connect) section.
146146
147−### Example
147+#### Example
148148
149149See the [Generators](/docs/arvo/eyre/examples#generators) section of the [Examples](/docs/arvo/eyre/examples) document for an example.
150150
@@ -158,7 +158,7 @@ This `task` deletes a URL binding previously set by a `%connect` or `%serve` `ta
158158
159159The [$binding](/docs/arvo/eyre/data-types#binding) is the URL path and domain of the binding you want to delete.
160160
161−### Returns
161+#### Returns
162162
163163Eyre returns no `gift` in response to a `%disconnect` `task`.
164164
@@ -172,7 +172,7 @@ This `task` tells Eyre that the web login code has changed, causing Eyre to thro
172172
173173This `task` takes no arguments.
174174
175−### Returns
175+#### Returns
176176
177177Eyre returns no `gift` in response to a `%code-changed` `task`.
178178
@@ -186,11 +186,11 @@ This `task` tells Eyre to start responding positively to CORS requests for the s
186186
187187The [$origin](/docs/arvo/eyre/data-types#origin) is a CORS origin like `http://foo.example` you want to approve.
188188
189−### Returns
189+#### Returns
190190
191191Eyre returns no `gift` in response to a `%approve-origin` `task`.
192192
193−### Example
193+#### Example
194194
195195See the [Managing CORS Origins](/docs/arvo/eyre/examples#managing-cors-origins) section of the [Examples](/docs/arvo/eyre/examples) document for an example.
196196
@@ -204,10 +204,10 @@ This `task` tells Eyre to start responding negatively to CORS requests for the s
204204
205205The [$origin](/docs/arvo/eyre/data-types#origin) is a CORS origin like `http://foo.example` you want want to reject.
206206
207−### Returns
207+#### Returns
208208
209209Eyre returns no `gift` in response to a `%reject-origin` `task`.
210210
211−### Example
211+#### Example
212212
213213See the [Managing CORS Origins](/docs/arvo/eyre/examples#managing-cors-origins) section of the [Examples](/docs/arvo/eyre/examples) document for an example.
content/docs/arvo/iris/tasks.md+3−3
@@ -18,7 +18,7 @@ The [$request:http](/docs/arvo/eyre/data-types#requesthttp) is the request itsel
1818
1919The [$outbound-config](/docs/arvo/iris/data-types#outbound-config) specifies the number of redirects to follow before failing and the number of retries to attempt before giving up. The default values are `5` and `3` respectively. As of writing, **retries and auto-following redirects are not implemented**, so what you specify here is irrelevant and you can just use the bunt value of `outbound-config`.
2020
21−### Returns
21+#### Returns
2222
2323Iris returns a `%http-response` `gift` in response to a `%request` task. A `%response` `gift` looks like:
2424
@@ -32,7 +32,7 @@ The `client-response` structure specifies three kinds of responses - `%progress`
3232
3333Note that neither `%progress` partial messages nor `%cancel` responses have been implemented in Vere at the time of writing, so **you will only ever receive a single `%http-response` `gift` with a `%finished` `client-response`**. If the request fails for some reason, you'll still get an empty `%finished` `client-response` with a `504` status code.
3434
35−### Example
35+#### Example
3636
3737See the [Example](/docs/arvo/iris/example) document.
3838
@@ -46,7 +46,7 @@ Cancel a previous request to fetch a remote HTTP resource.
4646
4747A `%cancel-request` `task` does not take any arguments, the [request](#request) to cancel is determined implicitly.
4848
49−### Returns
49+#### Returns
5050
5151Iris does not return any `gift` in response to a `%cancel-request` `task`. You will also not receive any `gift` back from the original `%request` `task` you've cancelled.
5252
content/docs/arvo/jael/scry.md+15−15
@@ -10,7 +10,7 @@ Jael's scry endpoints never take a `care`. The particular endpoints are specifie
1010
1111A `%step` scry gets the current web login code revision number. It takes your ship name as its `path` and the type returned is a `@ud`.
1212
13−### Example
13+#### Example
1414
1515```
1616> .^(@ud %j /=step=/(scot %p our))
@@ -21,7 +21,7 @@ A `%step` scry gets the current web login code revision number. It takes your sh
2121
2222A `%code` scry gets the current web login code. It takes your ship name as its `path` and the type returned is a `@p`.
2323
24−### Example
24+#### Example
2525
2626```
2727> .^(@p %j /=code=/(scot %p our))
@@ -32,7 +32,7 @@ A `%code` scry gets the current web login code. It takes your ship name as its `
3232
3333A `%life` scry gets the current `life` (key revision number) of a ship if known, otherwise it crashes. It takes a ship as its `path` and the type returned is a `@ud`.
3434
35−### Example
35+#### Example
3636
3737```
3838> .^(@ud %j /=life=/(scot %p our))
@@ -43,7 +43,7 @@ A `%life` scry gets the current `life` (key revision number) of a ship if known,
4343
4444A `%lyfe` scry gets the current unitized `life` (key revision number) of a ship if known, otherwise returns `~`. It takes a ship as its `path` and the type returned is a `(unit @ud)`.
4545
46−### Example
46+#### Example
4747
4848```
4949> .^((unit @ud) %j /=lyfe=/(scot %p our))
@@ -54,7 +54,7 @@ A `%lyfe` scry gets the current unitized `life` (key revision number) of a ship
5454
5555A `%rift` scry gets the current `rift` (continuity number) of a ship if known, otherwise it crashes. It takes a ship as its `path` and the type returned is a `@ud`.
5656
57−### Example
57+#### Example
5858
5959```
6060.^(@ud %j /=rift=/(scot %p our))
@@ -65,7 +65,7 @@ A `%rift` scry gets the current `rift` (continuity number) of a ship if known, o
6565
6666A `%ryft` scry gets the current unitized `rift` (continuity number) of a ship if known, otherwise it returns `~`. It takes a ship as its `path` and the type returned is a `(unit @ud)`.
6767
68−### Example
68+#### Example
6969
7070```
7171> .^((unit @ud) %j /=ryft=/(scot %p our))
@@ -76,7 +76,7 @@ A `%ryft` scry gets the current unitized `rift` (continuity number) of a ship if
7676
7777A `%vein` scry gets your ship's private key for the specified `life`. It takes a `life` `@ud` as its `path` and the type returned is a `ring`.
7878
79−### Example
79+#### Example
8080
8181```
8282> .^(@uw %j /=vein=/1)
@@ -87,7 +87,7 @@ A `%vein` scry gets your ship's private key for the specified `life`. It takes a
8787
8888A `%vile` scry gets your `jam`mmed private boot parameters at your ship's current `life`. It takes no additional arguments in its `path`. The type returned is a `@` which is a `jam`med [$seed](/docs/arvo/jael/data-types#seed).
8989
90−### Examples
90+#### Examples
9191
9292```
9393> .^(@ %j /=vile=)
@@ -108,7 +108,7 @@ A `%vile` scry gets your `jam`mmed private boot parameters at your ship's curren
108108
109109A `%deed` scry gets the `life`, pubkey and maybe a signature if the ship in question is a comet. It takes the target ship and life as its `path` like `/~sampel-palnet/1` and the type returned is a `[life pass (unit @ux)]`.
110110
111−### Example
111+#### Example
112112
113113```
114114> .^([life pass (unit @ux)] %j /=deed=/(scot %p our)/1)
@@ -122,7 +122,7 @@ A `%deed` scry gets the `life`, pubkey and maybe a signature if the ship in ques
122122
123123A `%earl` scry deterministically derives a private key for a moon. Note this is a holdover from an old version of moon infrastructure, the current `|moon` generator doesn't use this and instead generates moon private keys non-deterministically. It takes the moon name and your ship's current `life` (not the moon's `life`) as its `path` like `/~doznec-dozzod-dozzod-dozzod/1`. The type returned is a [$seed](/docs/arvo/jael/data-types#seed), the `life` of the moon in the `lyf` field of the `seed` will always be `1`.
124124
125−### Example
125+#### Example
126126
127127```
128128> .^(seed:jael %j /=earl=/~doznec-dozzod-dozzod-dozzod/1)
@@ -138,7 +138,7 @@ A `%earl` scry deterministically derives a private key for a moon. Note this is
138138
139139A `%sein` scry gets the sponsor for the specified ship. This scry is used implicitly by `sein:title`. It takes the target ship as its `path` and the type returned is a `@p`.
140140
141−### Example
141+#### Example
142142
143143```
144144> .^(@p %j /=sein=/~sampel-palnet)
@@ -149,7 +149,7 @@ A `%sein` scry gets the sponsor for the specified ship. This scry is used implic
149149
150150A `%saxo` scry gets the sponsorship chain for the target ship (including the target ship itself). For example, a `%saxo` scry for a planet will return the planet, its star and the star's galaxy. This scry is used implicitly by `saxo:title`. It takes the target ship as its `path` and the type returned is a `(list @p)`.
151151
152−### Example
152+#### Example
153153
154154```
155155> .^((list @p) %j /=saxo=/~sampel-palnet)
@@ -168,7 +168,7 @@ nel=(set duct) :: trackers of all
168168
169169The `yen` `jug` maps subscribed `duct`s to the `ship`s they're tracking, and `ney` is just the inverse, mapping tracked `ship`s to subscribed `duct`s. The `nel` set contains `duct`s track all public key updates.
170170
171−### Example
171+#### Example
172172
173173```
174174.^([yen=(jug duct ship) ney=(jug ship duct) nel=(set duct)] %j /=subscriptions=/1)
@@ -179,7 +179,7 @@ The `yen` `jug` maps subscribed `duct`s to the `ship`s they're tracking, and `ne
179179
180180A `%sources` scry gets the current state of Jael's sources for PKI updates. It takes no additional arguments in its `path` and returns a [$state-eth-node](/docs/arvo/jael/data-types#state-eth-node).
181181
182−### Example
182+#### Example
183183
184184```
185185> .^(state-eth-node:jael %j /=sources=)
@@ -196,7 +196,7 @@ A `%sources` scry gets the current state of Jael's sources for PKI updates. It t
196196
197197A `%turf` scry gets the list of domain prefixes for galaxies. It takes no additional arguments in its `path`. It returns a `(list turf)`, where a `turf` is a TLD-first `(list @t)`, so `urbit.org` as a `turf` is `~['org' 'urbit']`.
198198
199−### Example
199+#### Example
200200
201201```
202202> .^((list turf) %j /=turf=)
content/docs/arvo/jael/tasks.md+15−15
@@ -34,7 +34,7 @@ This `task` takes a [$dawn-event](/docs/arvo/jael/data-types#dawn-event) as its
3434
3535You would not use this `task` manually.
3636
37−### Returns
37+#### Returns
3838
3939Jael `%give`s an `%init` `gift` to Unix. This occurs after the Dill `%slip` init.
4040
@@ -54,7 +54,7 @@ The `ship` field specifies the `@p` of the fake ship being created.
5454
5555You would not use this `task` manually.
5656
57−### Returns
57+#### Returns
5858
5959Jael `%give`s a `%init` `gift` to Unix.
6060
@@ -72,7 +72,7 @@ Sets the source that the public keys for a set of `ship`s should be obtained fro
7272
7373You are unlikely to use this `task` manually.
7474
75−### Returns
75+#### Returns
7676
7777Jael will not return any `gift`s in response to a `%listen` `task`.
7878
@@ -94,7 +94,7 @@ Register moon keys or otherwise administer a moon.
9494
9595This is what is sent to Jael by `%hood` behind the scenes when you run `|moon`, `|moon-breach` or `|moon-cycle-keys`. The `ship` field is the moon's `@p`. The [$udiff:point](/docs/arvo/jael/data-types#udiffpoint) will contain the bunt of an [$id:block](/docs/arvo/jael/data-types#idblock) (since moons aren't registered in Azimuth) and one of the `udiff` actions depending on what you want to do.
9696
97−### Returns
97+#### Returns
9898
9999Jael does not return any `gift`s in response to a `%moon` `task`.
100100
@@ -110,11 +110,11 @@ If you've subscribed to public or private key updates from Jael with a [%private
110110
111111To cancel a subscription to the ship's private keys you must leave `whos` empty like `[%nuke ~]`.
112112
113−### Returns
113+#### Returns
114114
115115Jael does not return a `gift` in response to a `%nuke` `task`.
116116
117−### Examples
117+#### Examples
118118
119119See the [%public-keys and %nuke](/docs/arvo/jael/examples#public-keys-and-nuke) section of the Examples document for an example of using `%nuke` to cancel a `%public-keys` subscription. See the thread in the [%private-keys](/docs/arvo/jael/examples#private-keys) example for cancelling a `%private-keys` subscription.
120120
@@ -128,7 +128,7 @@ Subscribe to private key updates.
128128
129129Subscribe to be notified of private key updates for the local ship. The subscription will continue until Jael receives a [%nuke](#nuke) `task` to cancel it.
130130
131−### Returns
131+#### Returns
132132
133133Jael responds to a `%private-keys` `task` with a `%private-keys` `gift` which looks like:
134134
@@ -138,7 +138,7 @@ Jael responds to a `%private-keys` `task` with a `%private-keys` `gift` which lo
138138
139139The `life` is the current life of the ship and the `vein` `map` contains the private key for each life up to (and including) the current one. Upon first subscribing, Jael will immediately respond with a `%private-keys` `gift`. Then, whenever the ship's private keys are changed, it'll send a new and updated `%private-keys` `gift`.
140140
141−### Example
141+#### Example
142142
143143See the [%private-keys](/docs/arvo/jael/examples#private-keys) section of the Examples document for a practical example.
144144
@@ -152,7 +152,7 @@ Subscribe to public key (and related) updates from Jael.
152152
153153An agent or thread can subscribe to be notified of public key updates, sponsorship changes and continuity breaches for the `set` of `ship`s specified in the `ships` field. The subscription will continue until Jael receives a [%nuke](#nuke) `task` to cancel it.
154154
155−### Returns
155+#### Returns
156156
157157Jael responds to a `%public-keys` `task` with `%public-keys` `gift`s which look like:
158158
@@ -164,7 +164,7 @@ The [$public-keys-result](/docs/arvo/jael/data-types#public-keys-result) contain
164164
165165Upon subscription, Jael will immeditely respond with a `%public-keys` `gift` containing a `%full` `public-keys-result` with the public key for each `life` up until the current one for each `ship` specified in the original `task`. After than, Jael will send a `%public-keys` `gift` with either a `%diff` or `%breach` `public-keys-result` each time a change occurs for any of the `ship`s to which you're subscribed.
166166
167−### Example
167+#### Example
168168
169169See the [%public-keys and %nuke](/docs/arvo/jael/examples#public-keys-and-nuke) section of the Examples document for a practical example.
170170
@@ -178,7 +178,7 @@ Update private keys.
178178
179179This is what is sent to Jael by `%hood` when you run `|rekey`, as you must after setting new Azimuth keys or running `|cycle-moon-keys` on a moon's parent. It will update your `life` (key revision number) and private keys. The `life` field is the new `life` (typically an increment of the current `life`) and the `ring` is a private key `@`.
180180
181−### Returns
181+#### Returns
182182
183183Jael does not return any `gift` in response to a `%rekey` `task`.
184184
@@ -192,7 +192,7 @@ View domains.
192192
193193The domains returned by a `%turf` `task` are used as the base for individual galaxy domain names (e.g. from `urbit.org` you get `zod.urbit.org`, `bus.urbit.org`, etc). Jael gets these from Azimuth, then Ames gets them from Jael and passes them to the runtime, which will perform the DNS lookups and give Ames back the galaxy IP addresses. A `%turf` task takes no additional arguments. You're unlikely to use this manually - if you want the current `turf`s you'd likely want to do a [turf scry](/docs/arvo/jael/scry#turf) instead.
194194
195−### Returns
195+#### Returns
196196
197197Jael will respond to a `%turf` `task` with a `%turf` `gift`, which looks like:
198198
@@ -202,7 +202,7 @@ Jael will respond to a `%turf` `task` with a `%turf` `gift`, which looks like:
202202
203203The `turf` in the `(list turf)` is a domain as a `(list @t)`, TLD-first. The current default is `[['org' 'urbit' ~] ~]`.
204204
205−### Example
205+#### Example
206206
207207See the [%turf section of the Examples document](/docs/arvo/jael/examples#turf) for a practical example.
208208
@@ -216,10 +216,10 @@ Reset web login code.
216216
217217Jael maintains a `step` value that represents the web login code revision number, and uses it to derive the code itself. It begins at `0` and is incremented each time the code is changed. When Jael updates the web login code, it sends Eyre a `%code-changed` `task:eyre` so that Eyre can throw away all of its current cookies and sessions. A `%step` task takes no additional argument.
218218
219−### Returns
219+#### Returns
220220
221221Jael does not return a `gift` in response to a `%step` `task`.
222222
223−### Example
223+#### Example
224224
225225See the [%step](/docs/arvo/jael/examples#step) section of the Examples document for a practical example.
content/docs/hoon/reference/cheat-sheet.md+3−13
@@ -5,24 +5,14 @@ template = "doc.html"
55aliases = ["docs/reference/cheat-sheet/"]
66+++
77
8−Download the cheat sheet PDF [here](https://storage.googleapis.com/media.urbit.org/docs/hoon-cheat-sheet-2020-07-24.pdf).
8+Download the cheat sheet PDF [here](https://media.urbit.org/docs/hooncard-unbranded-2021-09-12.pdf)
99
1010We've found it's useful to have a quick reference of Hoon's syntax always at
11−hand. So above is a download link to a Hoon cheatsheet .pdf. It's missing a
12−few of the literal syntaxes, but it covers the most common ones.
11+hand. So above is a download link to a Hoon cheatsheet PDF.
1312
14−This doesn't list any of the Ford runes, although there is now at least
15−some documentation for those [here](/docs/arvo/ford/ford).
16−
17−The following runes also exist, but are currently undocumented and not listed in
13+The following rune also exists, but is currently undocumented and not listed in
1814the cheat sheet:
1915
2016```
21−!: :: turn on debugging printfs
22−!. :: turn off debugging printfs
23−!; :: using the "type of type", emit the type for an expression
24−!, :: emit AST of expression
2517$* :: bunt (irregular form is *)
26−^. :: use gate to transform type
27−^& :: zinc (covariant) -- see the docs on advanced types
2818```
content/docs/hoon/reference/style.md+108−108
@@ -79,32 +79,32 @@ across the page.
7979With one child:
8080
8181```hoon
82− !:
83− a
82+!:
83+a
8484```
8585
8686With two:
8787
8888```hoon
89− =+ a
90− b
89+=+ a
90+b
9191```
9292
9393With three:
9494
9595```hoon
96− ?: a
97− b
98− c
96+?: a
97+ b
98+c
9999```
100100
101101With four (the maximum rune fanout):
102102
103103```hoon
104− :^ a
105− b
106− c
107− d
104+:^ a
105+ b
106+ c
107+d
108108```
109109
110110It is sometimes acceptable to not backstep -- especially in tuple
@@ -118,21 +118,21 @@ they can’t self-terminate. They are terminated by a `==` marker.
118118The sequence, or its variable part, is indented and vertical:
119119
120120```hoon
121− :* a
122− b
123− c
124− ==
121+:* a
122+ b
123+ c
124+==
125125```
126126
127127Another representation wastes a line, but saves an indent. Use
128128this only for very long sequences:
129129
130130```hoon
131− :*
132− a
133− b
134− c
135− ==
131+:*
132+ a
133+ b
134+ c
135+==
136136```
137137
138138Some variable sequences are sequences of pairs; some of these
@@ -143,29 +143,29 @@ looks better for this particular code.
143143Kingside format:
144144
145145```hoon
146− ?+ x default
147− %foo 99
148− %foobar ?: y
149− 42
150− (add 2 2)
151− 37 36
152− ==
146+?+ x default
147+ %foo 99
148+ %foobar ?: y
149+ 42
150+ (add 2 2)
151+ 37 36
152+==
153153```
154154
155155Queenside format:
156156
157157```hoon
158− ?+ x
159− default
160− ::
161− %foo
162− 99
163− ::
164− %foobar
165− ?: y
166− 42
167− (add 2 2)
168− ==
158+?+ x
159+ default
160+::
161+ %foo
162+ 99
163+::
164+ %foobar
165+ ?: y
166+ 42
167+ (add 2 2)
168+==
169169```
170170
171171The queenside format is more useful when the tails are bigger.
@@ -176,13 +176,13 @@ We sometimes end up with multiple terminators on separate lines,
176176two or more spaces apart. These lines can be collapsed:
177177
178178```hoon
179− ?+ x default
180− %foo 99
181− 37 36
182− %foobar ?- y
183− %moo 9
184− %bar 10
185− == ==
179+?+ x default
180+ %foo 99
181+ 37 36
182+ %foobar ?- y
183+ %moo 9
184+ %bar 10
185+== ==
186186```
187187
188188## General naming style
@@ -220,25 +220,25 @@ Hoon suggests “breathing comments.” There should always be an
220220empty comment line between a comment and the line **below** it:
221221
222222```hoon
223− :: look, code
224− ::
225− this.is.code
226− :: we are going to need more code
227− ::
228− this.is.more.code
223+:: look, code
224+::
225+this.is.code
226+:: we are going to need more code
227+::
228+this.is.more.code
229229```
230230
231231Sometimes dense code does need air on both sides. Also
232232legitimate:
233233
234234```hoon
235− :: look, code
236− ::
237− this.is.code
238− ::
239− :: we are going to need more code
240− ::
241− this.is.more.code
235+:: look, code
236+::
237+this.is.code
238+::
239+:: we are going to need more code
240+::
241+this.is.more.code
242242```
243243
244244But air on both sides is needed less often than you may think.
@@ -257,15 +257,15 @@ In older code you’ll also see attempts to produce large, visually
257257salient geometries as separators, like:
258258
259259```
260− ::
261− ::::
262− ::
260+::
261+::::
262+::
263263```
264264
265265or
266266
267267```
268− ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
268+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
269269```
270270
271271Please don’t do this in new code. Normal conventions should be
@@ -300,14 +300,14 @@ then an udon body with paragraphs broken by empty lines, indented
300300four spaces, in ASCII mixed case:
301301
302302```hoon
303− :: $foo: the definition of a foo
304− ::
305− :: The source of the word “foo” is lost in history. Some
306− :: think it comes from the WWII military term “FUBAR,”
307− :: in which “UBAR” stands for “up beyond all retrieval.”
308− ::
309− :: Somehow “FUBAR” became “foobar.” The rest is history.
310− ::
303+:: $foo: the definition of a foo
304+::
305+:: The source of the word “foo” is lost in history. Some
306+:: think it comes from the WWII military term “FUBAR,”
307+:: in which “UBAR” stands for “up beyond all retrieval.”
308+::
309+:: Somehow “FUBAR” became “foobar.” The rest is history.
310+::
311311```
312312
313313The point is: sometimes we want to see a deep explanation;
@@ -321,9 +321,9 @@ A feature headline is `::`, then two spaces, then a qualified
321321location, then `:`, then a freeform string. Like:
322322
323323```hoon
324− :: $foo: a the definition of a foo
325− ::
326− +$ foo [head=@ tail=^]
324+:: $foo: a the definition of a foo
325+::
326++$ foo [head=@ tail=^]
327327```
328328
329329We find the feature `$foo` relative to the current location and
@@ -337,9 +337,9 @@ Product comments describe the product of the expression below.
337337The headline is a string in parentheses:
338338
339339```hoon
340− :: (a very strange $foo indeed)
341− ::
342− make:a:strange:foo
340+:: (a very strange $foo indeed)
341+::
342+make:a:strange:foo
343343```
344344
345345### Flow comments and legends
@@ -364,15 +364,15 @@ make it … `[%wtcl if=hoon then=hoon else=hoon]`. Then we
364364could write flow comments as follows:
365365
366366```hoon
367− ?: :: if, it is winter
368− ::
369− is-winter
370− :: then, why not rome?
371− ::
372− visit-italy
373− :: else, amsterdam is nice in the summer
367+?: :: if, it is winter
374368 ::
375− visit-holland
369+ is-winter
370+ :: then, why not rome?
371+ ::
372+ visit-italy
373+:: else, amsterdam is nice in the summer
374+::
375+visit-holland
376376```
377377
378378The headline of a flow comment uses a **legend**: the name of
@@ -392,35 +392,35 @@ Sometimes, just for cosmetic reasons, we like to raise the
392392comment on the first leg of a rune up above the parent:
393393
394394```hoon
395− :: if, it is winter
396− ::
397− ?: is-winter
398− :: then, why not rome?
399− ::
400− visit-italy
401− :: else, amsterdam is nice in the summer
402− ::
403− visit-holland
395+:: if, it is winter
396+::
397+?: is-winter
398+ :: then, why not rome?
399+ ::
400+ visit-italy
401+:: else, amsterdam is nice in the summer
402+::
403+visit-holland
404404```
405405
406406This can make flow comments collide, as in “else, if,” below:
407407
408408```hoon
409− :: if, it is winter
410− ::
411− ?: is-winter
412− :: then, why not rome?
413− ::
414− visit-italy
415− :: else, if, sprechen sie deutsch?
416− ::
417− ?: speak-german
418− :: then, maybe berlin?
419− ::
420− visit-berlin
421− :: else, everyone in amsterdam speaks english
422− ::
423− visit-holland
409+:: if, it is winter
410+::
411+?: is-winter
412+ :: then, why not rome?
413+ ::
414+ visit-italy
415+:: else, if, sprechen sie deutsch?
416+::
417+?: speak-german
418+ :: then, maybe berlin?
419+ ::
420+ visit-berlin
421+:: else, everyone in amsterdam speaks english
422+::
423+visit-holland
424424```
425425
426426#### Flow comments, traces, and interpolation
content/docs/userspace/graph-store/sample-application-overview.mdnew file+369
@@ -0,0 +1,369 @@
1++++
2+title = "Sample Application: Library"
3+weight = 4
4+sort_by = "weight"
5+template = "doc.html"
6+insert_anchor_links = "right"
7++++
8+
9+
10+
11+Library is a sample social media application in which you can create a collection of books, called a library, which can contain any number of books.
12+You can share individual collections per-ship. The creator of the collection has de-facto admin powers; he is the only one who can add or remove books to/from the library,
13+remove the library itself, and add comments or remove anyone's comments. Guest ships may request access to specific libraries, which if granted,
14+allows them to request any book from the library.
15+
16+Using this application, you can:
17+
18+- Create libraries, where a library is a collection of books
19+- Add and remove books from a library, if you are the owner
20+- Allow others to view your library based on various permissioning schemes (policies)
21+- Add and remove comments from a library, if you are the owner or were granted access
22+
23+## Prerequisites
24+
25+This overview is a companion document to the *Library* application, and assumes you are familiar with `%graph-store`. Before continuing, **you must familiarize yourself with the sample application**, which can be found [here](https://github.com/ynx0/library). See the [detailed usage guide](https://github.com/ynx0/library/blob/master/README.md) for download and installation instructions.
26+
27+
28+## Project Structure
29+
30+Here is the directory structure of our app.
31+```
32+├── app
33+│ └── library-proxy.hoon
34+├── lib
35+│ └── library.hoon
36+├── mar
37+│ ├── graph
38+│ │ └── validator
39+│ │ └── library.hoon
40+│ └── library
41+│ ├── action.hoon
42+│ ├── command.hoon
43+│ └── response.hoon
44+├── sur
45+│ └── library.hoon
46+└── install.sh
47+```
48+
49+- The main code of the application lives in [`app/library-proxy.hoon`](https://github.com/ynx0/library/blob/master/app/library-proxy.hoon). This contains the gall agent which proxies the `%graph-store` updates between ships.
50+
51+- [`lib/library.hoon`](https://github.com/ynx0/library/blob/master/lib/library.hoon) contains miscellaneous helper arms which the proxy uses extensively. It mainly contains arms that construct different `%graph-store` updates various actions that a user performed.
52+
53+- The `mar/library` folder contains the definitions of the pokes that `%library-proxy` uses.
54+
55+- Contained in [`mar/graph/validator/library.hoon`](https://github.com/ynx0/library/blob/master/mar/graph/validator/library.hoon) is the definition of the validation logic that `%graph-store` uses to enforce the schema of the library applications graph data.
56+
57+- [`sur/library.hoon`](https://github.com/ynx0/library/blob/master/sur/library.hoon) contains all the various type definitions used by `%library-store`.
58+
59+- [`install.sh`](https://github.com/ynx0/library/blob/master/install.sh) is a script that automates copying the source files into a ship's pier / home desk.
60+
61+
62+## Implementation Summary
63+
64+The project contains a custom `%graph-store` validator tht specifies the application's schema and a custom gall agent, called `%library-proxy`, which sends `%graph-store` updates between the host ship and subscribers.
65+The gall agent also generates `%graph-store` updates based on commands and actions that a user issues. Commands can only be issued to a proxy by the ship owner,
66+while actions can be issued by any ship, but may fail based on permissions. A host ship's `%library-proxy` talks to the subscriber ship's `%library-proxy` through the normal gall app channels (pokes/peeks)
67+to send them any updates that have taken place on the library graph. It is the host's responsibility to forward all relevant updates to subscribers
68+(kept track of in a data structure defined [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/sur/library.hoon#L14-L15)),
69+while subscribers must only trust graph updates that it receives from the owner of that resource.
70+
71+Here are the proxy's responsibilities:
72+
73+- **Application-specific API** - presents an interface for a user to interact with both his/her own library proxy and others' by directly defining and implementing a user-facing API as pokes and scries,
74+ as opposed to forcing the user to deal with the graph-store API directly. The `command`/`action`/`response` poke types are defined [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/sur/library.hoon#L17-L42),
75+ while the scries are defined [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L132).
76+- **Graph store update creation** - creates the appropriate graph update for a given user-facing action
77+- **Graph store update proxying** - handles the networking and subscription logic required to send graph store updates between host and subscriber
78+- **Access control** - allows or denies ships access to libraries, checks for proper permissions before processing a `command` or `action`
79+
80+## Interaction with `%graph-store`
81+
82+There are a couple of ways that our application uses and interacts with `%graph-store`.
83+Firstly, the app subscribes to `%graph-store` in `+on-init` on path `/updates`, meaning it will be notified of every single `graph-update` that occurs on the local ship. (*Recall the type of `graph-update`, defined [here](https://github.com/urbit/urbit/blob/ceed4b78d068d7cb70350b3cd04e7525df1c7e2d/pkg/arvo/sur/graph-store.hoon#L29), the reference for which can be found [here](https://urbit.org/docs/userspace/graph-store/data-structure-overview/#update-part-1)*). We also define a validator for our application that `%graph-store` uses for every *Library* graph. Since `%graph-store` registers all validators under the path `%/graph/validator/<name of app>/hoon`, we don't have to do anything special to register our validator. We just need to correctly name our file, and provide an `+graph-indexed-post` arm which should perform all schema validation.
84+
85+### Synchronizing Graphs Between Ships
86+
87+An owner is responsible for forwarding any updates to clients. Whenever the `%library-proxy` gall agent receives an update from it's local `%graph-store`, it checks to see whether it's for a graph it owns or not. If it's not, we skip sending out updates since we don't own the resource\*. If it is, then we generate cards to poke each subscriber with that same `graph-update`. This logic occurs within the agent's `+on-watch` arm, with the logic residing in the `handle-outgoing-graph-update` arm, found [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L345).
88+
89+On the receiving end, since we know that we are not the source of the `graph-update`, we handle the update in the `handle-incoming-graph-store` helper arm, which makes sure to only process and forward graph store updates to local graph store that are sent by the owner, and no one else. Once it passes this permissions check, it is poked into the local graph like any other graph-update.
90+
91+\**One could imagine a gossip style protocol where the data is like a fact so it doesn't matter where it came from, but in this app the library host is defined as the source of truth.*
92+
93+## Data Model
94+
95+There are two main application-side data structures that we define.
96+The first type is [`book`](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/sur/library.hoon#L4-L9) which contains a title and an isbn.
97+The second type is a [`comment`](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/sur/library.hoon#L3), which is just a simple string of text,
98+meant to represent a comment on any given book.
99+
100+### Representing Data using `%graph-store`
101+
102+The data types we defined for our application do not fit within `%graph-store` out of the box. `%graph-store` doesn't allow arbitrarily typed data in a node's content field, so we create an ad-hoc representation that we can cleanly convert to and from our own data types and `%graph-store` types.
103+
104+Here is the conversion code: [(link)](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/lib/library.hoon#L12-L19)
105+
106+```
107+++ make-meta-contents
108+ |= [=book:library]
109+ ^- (list content)
110+ ~[[%text title.book] [%text isbn.book]]
111+++ make-comment-contents
112+ |= [comment-text=comment:library]
113+ ^- (list content)
114+ ~[[%text comment-text]]
115+```
116+
117+Each arm takes in either a `book` or a `comment`, our application specific data type, and spits out a `(list content)`, which is the type that a `node` uses to store its content.
118+
119+### Organizing Data within `%graph-store`
120+
121+We define 3 core structures:
122+
123+- **Library** - The fundamental data structure. A library is a graph that contains books
124+- **Book** - A data structure that contains an entry of a given book's metadata (i.e. title and isbn) and any comments associated with it. It is represented by a top-level node within a library graph
125+- **Comment** - A node that represents a user's comment on a given book. Represented as a child node of a book's comment container node.
126+
127+We then define the layout of the data as follows:
128+
129+* Library
130+ - Book
131+ + Metadata Revision Container
132+ * Specific Metadata Revision
133+ + Comments Container
134+ * Specific Comment
135+
136+Every graph created with the `%graph-validator-library` is a *Library* graph, and thus it has the following characteristics:
137+
138+- Every Library graph represents a library, or a collection of books
139+- Every top level node in a library represents a book and all related contents
140+- Within a book there are always two structural nodes: a container node for metadata revisions, and a container node that holds all comments, modeling a comments section
141+
142+To make things more concrete, let's look at an example of an actual graph.
143+
144+(*For the sake of clarity, we have replaced each index with its representation in its original aura in following output.*)
145+
146+```
147+[ p=[entity=~zod name=%library1]
148+ q
149+ [ p
150+ { [ key=~2021.6.27..14.18.57..b3dc
151+ val
152+ [ post
153+ [ %.y
154+ p
155+ [ author=~zod
156+ index=~[~2021.6.27..14.18.57..b3dc]
157+ time-sent=~2021.6.27..14.18.57..b3dc
158+ contents=~
159+ hash=~
160+ signatures={}
161+ ]
162+ ]
163+ children
164+ [ %graph
165+ p
166+ { [ key=%comments
167+ val
168+ [ post
169+ [ %.y
170+ p
171+ [ author=~zod
172+ index=~[~2021.6.27..14.18.57..b3dc %comments]
173+ time-sent=~2021.6.27..14.18.57..b3dc
174+ contents=~
175+ hash=~
176+ signatures={}
177+ ]
178+ ]
179+ children
180+ [ %graph
181+ p
182+ { [ key=~2021.6.27..14.20.00.b5fc
183+ val
184+ [ post
185+ [ %.y
186+ p
187+ [ author=~pub
188+ index=~[~2021.6.27..14.18.57..b3dc %meta ~2021.6.27..14.20.00.b5fc]
189+ time-sent=~2021.6.27..14.20.00.b5fc
190+ contents=~[[%text text='dune is pretty good']]
191+ hash=~
192+ signatures={}
193+ ]
194+ ]
195+ children=[%empty ~]
196+ ]
197+ ]
198+ }
199+ ]
200+ ]
201+ ]
202+ [ key=%meta
203+ val
204+ [ post
205+ [ %.y
206+ p
207+ [ author=~zod
208+ index=~[~2021.6.27..14.18.57..b3dc %meta]
209+ time-sent=~2021.6.27..14.18.57..b3dc
210+ contents=~
211+ hash=~
212+ signatures={}
213+ ]
214+ ]
215+ children
216+ [ %graph
217+ p
218+ { [ key=2
219+ val
220+ [ post
221+ [ %.y
222+ p
223+ [ author=~zod
224+ index=~[~2021.6.27..14.18.57..b3dc %meta 1]
225+ time-sent=~2021.6.27..14.18.58..a4ed
226+ contents=~[[%text text='Dune'] [%text text='0441172717']]
227+ hash=~
228+ signatures={}
229+ ]
230+ ]
231+ children=[%empty ~]
232+ ]
233+ ]
234+ [ key=1
235+ val
236+ [ post
237+ [ %.y
238+ p
239+ [ author=~zod
240+ index=~[~2021.6.27..14.18.57..b3dc %meta 1]
241+ time-sent=~2021.6.27..14.18.57..b3dc
242+ contents=~[[%text text='Dune......'] [%text text='0444444444']]
243+ hash=~
244+ signatures={}
245+ ]
246+ ]
247+ children=[%empty ~]
248+ ]
249+ ]
250+ }
251+ ]
252+ ]
253+ ]
254+ }
255+ ]
256+ ]
257+ ]
258+ }
259+ q=[~ %graph-validator-library]
260+ ]
261+]
262+```
263+
264+![](https://lh5.googleusercontent.com/UTCnXumWvuPpFtduxgzmxIR_vhn9N3SylJ9h-MoUjYWiCT4AZo4BXY1mWceu0iHcJjesWoBDzMVO9QYWbnqtsGWa4nt4GlIRAtiC2zSXLkDUEttTmlMDLS7XRZ7ZhJyEayaMMl2r)
265+
266+* The above graph represents a library that has a single book, whose metadata is stored under the `%meta` revision container. The metadata associated with this specific book entry is currently with title: `"Dune"` and isbn `"0441172717"`.
267+ The reason we have a revision container for the book metadata is so that in case someone makes an error, they may correct it. A potential frontend could then simply show the most recent version.
268+* Each comment is simply a node under a book's `%comment` node with a single `%text` content.
269+ We skipped revision containers for comments to keep schema simple, but you could imagine duplicating the same logic for comments as well.
270+* Every book node has an index the datetime of when it was created.
271+* Each structural node in a book has a constant index fragment, either `%meta` for the metadata revision container node,
272+ or `%comments` for the comments container node.
273+* Every comment has an index of the datetime of when it was posted.
274+* The metadata revisions are a single incrementing number starting from 1, so the initial metadata has a revision count of `1`, and a subsequent edit has a revision count of `2`, and so on.
275+
276+### Schema Enforcement
277+
278+Recall that when using `%graph-store`, all data that is to be added to a graph passes through our [custom validator](https://github.com/ynx0/library/blob/master/mar/graph/validator/library.hoon).
279+Take a moment to read through it and cross-check your understanding with the following summary.
280+
281+**Validator logic summary**
282+
283+- None of the structural nodes require data, so we simply assert that their contents are empty
284+- For a *Specific Comment Node*, a post with an index matching the structure `[@ %comments @ ~]`, we assert that `contents` only contains a single `%text` content.
285+ This choice is arbitrary but meant to keep the application simple and so that a potential frontend does not have to do complex rendering.
286+- For a *Specific Metadata Revision*, a post with index matching the structure `[@ %meta @ ~]`, we first ensure that it's contents only contain two `%text` content instances.
287+ We then use two helper arms `+is-title-valid` and `+is-isbn-valid` to validate the two values. For us, any title value is accepted, while only strings with length 10 or 13 are valid ISBNs.
288+ (We don't do any true ISBN validation for simplicity's sake)
289+
290+## Access Control
291+
292+### Rules
293+
294+There are explicit access control rules called `policy`s, (defined [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/sur/library.hoon#L45-L49))
295+which are set by the user per-library at the time of creation (stored [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L179)).
296+These specify who can or cannot gain access to a library.
297+
298+Here's what `policy` looks like:
299+```
300++$ policy
301+ $% [%open ~] :: any ship is allowed
302+ [%children ~] :: any children (i.e. moons) are allowed
303+ [%whitelist ships=(set ship)] :: any ships in the provided set are allowed
304+ ==
305+```
306+
307+There are also implicit rules, defined as follows:
308+
309+For any given library that one owns,
310+- An owner can:
311+ + add a top level book node to any library
312+ + add a metadata revision to any book
313+ + add or remove any comment on any book
314+- A reader, that is, a another ship granted access to the library, can:
315+ + add a comment to any book that they have requested
316+ + remove any comment that they are the author of
317+
318+Implicitly, all readers are given permission to get any book when granted access to the library.
319+
320+### Implementation
321+
322+- `policies` is a `(map @tas policy)`. That is, a map between the names of libraries that we own and what policy should be enforced on each one and is a part of the agent state (shown [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L13)).
323+
324+- The `+is-allowed` arm, (found [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/lib/library.hoon#L21))
325+ implements each `policy`'s behavior, and is reproduced below.
326+ ```
327+ ++ is-allowed
328+ |= [requester=ship host=ship =policy:library]
329+ ^- ?
330+ ?: =(requester host) :: host is always allowed
331+ %.y
332+ ?- -.policy
333+ %open %.y
334+ %children (team:title host requester)
335+ %whitelist (~(has in ships.policy) requester)
336+ ==
337+ ```
338+ Given the host ship of a resource and the policy to be enforced for a resource, `+is-allowed` returns a boolean for whether or not a requesting ship should be allowed access to that resource.
339+
340+
341+- `+is-allowed` is used in `+on-watch` [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L113), where it only allows a ship to subscribe to a library if it passes the permissions check.
342+- It is also used [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L299) and [here](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L310), so that when a ship wants to know about what libraries and books exist, only data they are allowed to see gets revealed to them.
343+- Some of the more ad-hoc/implicit permission rules are implemented at the following locations: [[1](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L318) [2](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L48) [3](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L256) [4](https://github.com/ynx0/library/blob/9104145bb24e8ec949a5e9685139665b07161dd6/app/library-proxy.hoon#L299)]
344+
345+## Alternative Methods
346+
347+There are a number of architectural decisions made in our application that may not be suitable for other projects.
348+Many decisions made such as the definition of the schema, the user-facing and inter-proxy API, and the permissioning model could be done differently.
349+For instance, many decisions in the schema and thus the validator were made to keep the schema simple. One could imagine allowing any content type and amount,
350+while right now it is restricted to a single text content, and stricter validation of or a custom for the ISBN.
351+The subscription model is also just one way to handle subscriptions and by no means the only one.
352+The permissioning scheme was also implemented relatively simple; it has some limitations, like the fact that you cannot change the permissions after creating the a library, or the fact that there are only 3 policy types.
353+
354+### Comparison between `%library-proxy` and `%graph-push-hook`
355+
356+`%library-proxy` performs the functions of both `%graph-push-hook` and `%graph-pull-hook`.
357+`%graph-pull-hook` is responsible for asking other `%graph-push-hook`s for graph data
358+by trying to initiate a subscription on the host ship's `%graph-push-hook`, which the host only allows if they have permissions.
359+When it succeeds, `%graph-push-hook` then sends out all `graph-updates` to those subscribers who need to hear about it.
360+`%graph-pull-hook` then merges in graph updates it hears into the local `%graph-store`.
361+So far, all the functionality is in line with `%library-proxy`.
362+
363+The two main places where the hooks differ is in their choice of permissioning model and subscription model.
364+To know whether a person is allowed to access a resource, pull hook uses checks to see whether they are a member of a group, while `%library-proxy` uses its own policy type.
365+For subscriptions, the hooks do it per-resource, (i.e. one subscription path per resource, many ships subscribe to that),
366+while library proxy incorporates both the resource and the ship in its path, making the subscription model per-resource per-ship.
367+
368+In general, if your application's use case does not fall neatly with the permissioning and subscription model used by the hooks, you'll need to make your own proxy agent.
369+Otherwise, you will likely want to use the existing hooks and save yourself the trouble of reimplementing all of the functionality that they offer.
content/docs/userspace/overview.md+5
@@ -10,6 +10,11 @@ Urbit's userspace is managed by Arvo's Gall vane.
1010
1111Resources on Gall which manages userspace applications.
1212
13+## [Graph Store](/docs/userspace/graph-store)
14+
15+Resources on Graph Store - the graph database utilized for many Landscape
16+applications and oriented for social media in general.
17+
1318## [Landscape](/docs/userspace/landscape/)
1419
1520Resources on the flagship web client Landcape, through which most users interact with their ship.
content/docs/userspace/threads/basics/summary.md+3−3
@@ -42,16 +42,16 @@ Now here's a quick recap of the main points covered:
4242- are conventionally given the face `m`.
4343- are a core that has three main arms - `form`, `pure` and `bind`:
4444
45−#### form
45+### form
4646
4747- is the mold of the strand suitable for casting
4848- is the type returned by the other arms
4949
50−#### pure
50+### pure
5151
5252- simply returns the `form` of a `strand` that produces pure's argument without doing any IO
5353
54−#### bind
54+### bind
5555
5656- is used to chain strands together like javascript promises
5757- is used in conjunction with micgal (`;<`)
content/docs/userspace/threads/examples/child-thread.md+33−33
@@ -4,31 +4,31 @@ weight = 2
44template = "doc.html"
55+++
66
7−Here's a simple example of a thread that starts another thread:
7+Here's a simple example of a thread that starts another thread:
88
9−### parent.hoon
9+#### `parent.hoon`
1010
1111```hoon
1212/- spider
1313/+ *strandio
1414=, strand=strand:spider
15−^- thread:spider
16−|= arg=vase
17−=/ m (strand ,vase)
15+^- thread:spider
16+|= arg=vase
17+=/ m (strand ,vase)
1818^- form:m
1919;< tid=tid:spider bind:m (start-thread %child)
2020(pure:m !>(~))
2121```
2222
23−### child.hoon
23+#### `child.hoon`
2424
2525```hoon
2626/- spider
2727/+ *strandio
2828=, strand=strand:spider
29−^- thread:spider
30−|= arg=vase
31−=/ m (strand ,vase)
29+^- thread:spider
30+|= arg=vase
31+=/ m (strand ,vase)
3232^- form:m
3333%- (slog leaf+"foo" ~)
3434(pure:m !>(~))
@@ -48,7 +48,7 @@ foo
4848;< tid=tid:spider bind:m (start-thread %child)
4949```
5050
51−See here how we gave `start-thread` the name of the thread to run. It returns the `tid` of the thread, which we could then use to poke it or whatever.
51+See here how we gave `start-thread` the name of the thread to run. It returns the `tid` of the thread, which we could then use to poke it or whatever.
5252
5353`start-thread` handles creating the `tid` for the thread so is quite convenient.
5454
@@ -58,15 +58,15 @@ Note that threads we start this way will be a child of the thread that started t
5858
5959If we want to actually get the result of the thread we started, it's slightly more complicated:
6060
61−### parent.hoon
61+#### `parent.hoon`
6262
6363```hoon
6464/- spider
6565/+ *strandio
6666=, strand=strand:spider
67−^- thread:spider
68−|= arg=vase
69−=/ m (strand ,vase)
67+^- thread:spider
68+|= arg=vase
69+=/ m (strand ,vase)
7070^- form:m
7171;< =bowl:spider bind:m get-bowl
7272=/ tid `@ta`(cat 3 'strand_' (scot %uv (sham %child eny.bowl)))
@@ -84,7 +84,7 @@ If we want to actually get the result of the thread we started, it's slightly mo
8484==
8585```
8686
87−### child.hoon
87+#### `child.hoon`
8888
8989```hoon
9090/- spider
@@ -94,9 +94,9 @@ If we want to actually get the result of the thread we started, it's slightly mo
9494|%
9595++ url "https://www.whatsthelatestbasehash.com/"
9696--
97−^- thread:spider
98−|= arg=vase
99−=/ m (strand ,vase)
97+^- thread:spider
98+|= arg=vase
99+=/ m (strand ,vase)
100100^- form:m
101101;< =cord bind:m (fetch-cord url)
102102=/ hash-as-cord `@t`(end [3 (sub (met 3 cord) 1)] cord)
@@ -104,7 +104,7 @@ If we want to actually get the result of the thread we started, it's slightly mo
104104(pure:m !>(hash))
105105```
106106
107−`child.hoon` simply grabs the latest base hash from https://www.whatsthelatestbasehash.com/ and returns it.
107+`child.hoon` simply grabs the latest base hash from https://www.whatsthelatestbasehash.com/ and returns it.
108108
109109`parent.hoon` is a bit more complicated so we'll look at it line-by-line
110110
@@ -136,10 +136,10 @@ We pre-emptively subscribe for the result. Spider sends the result at `/thread-r
136136
137137Spider takes a poke with a mark %spider-start and a vase containing `[parent=(unit tid) use=(unit tid) file=term =vase]` to start a thread, where:
138138
139−* `parent` is an optional parent thread. In this case we say the parent is our tid. Specifying a parent means the child will be killed if the parent ends.
140−* `use` is the thread ID for the thread we're creating
141−* `file` is the filename of the thread we want to start
142−* `vase` is the vase it will be given as an argument when it's started
139+- `parent` is an optional parent thread. In this case we say the parent is our tid. Specifying a parent means the child will be killed if the parent ends.
140+- `use` is the thread ID for the thread we're creating
141+- `file` is the filename of the thread we want to start
142+- `vase` is the vase it will be given as an argument when it's started
143143
144144```hoon
145145;< =cage bind:m (take-fact /awaiting/[tid])
@@ -164,15 +164,15 @@ Finally we test whether the thread produced a `%thread-done` or a `%thread-fail`
164164
165165## Stop a thread
166166
167−### parent.hoon
167+#### `parent.hoon`
168168
169169```hoon
170170/- spider
171171/+ *strandio
172172=, strand=strand:spider
173−^- thread:spider
174−|= arg=vase
175−=/ m (strand ,vase)
173+^- thread:spider
174+|= arg=vase
175+=/ m (strand ,vase)
176176^- form:m
177177;< =bowl:spider bind:m get-bowl
178178=/ tid `@ta`(cat 3 'strand_' (scot %uv (sham %child eny.bowl)))
@@ -193,7 +193,7 @@ Finally we test whether the thread produced a `%thread-done` or a `%thread-fail`
193193(pure:m !>("Done"))
194194```
195195
196−### child.hoon
196+#### `child.hoon`
197197
198198```hoon
199199/- spider
@@ -213,8 +213,8 @@ Finally we test whether the thread produced a `%thread-done` or a `%thread-fail`
213213 ==
214214--
215215^- thread:spider
216−|= arg=vase
217−=/ m (strand ,vase)
216+|= arg=vase
217+=/ m (strand ,vase)
218218^- form:m
219219;< ~ bind:m looper
220220(pure:m !>(~))
@@ -232,6 +232,6 @@ Finally we test whether the thread produced a `%thread-done` or a `%thread-fail`
232232 ==
233233```
234234
235−* `%spider-stop` is the mark that tells spider to kill a thread.
236−* `tid` is the tid of the thread to kill
237−* `%.y` tells spider to suppress traceback in the result of the killed thread. If you give it `%.n` it will include the traceback.
235+- `%spider-stop` is the mark that tells spider to kill a thread.
236+- `tid` is the tid of the thread to kill
237+- `%.y` tells spider to suppress traceback in the result of the killed thread. If you give it `%.n` it will include the traceback.
content/docs/userspace/threads/examples/get-json.md+5−4
@@ -4,18 +4,19 @@ weight = 1
44template = "doc.html"
55+++
66
7−Grabbing JSON from some url is very easy.
7+Grabbing JSON from some url is very easy.
88
99`strandio` includes the `fetch-json` function which will handle the http request and response parsing and just return unformatted json data.
1010
11−Here's a simple thread that will:
11+Here's a simple thread that will:
12+
1213- grab JSON of the latest base-hash from https://www.whatsthelatestbasehash.com/latesthash.json
1314- grab JSON of a list of all base-hashes with version info from https://www.whatsthelatestbasehash.com/all.json
1415- format and cast them
1516- check `all.json` includes the latest base-hash
1617- print the details of the latest base-hash to the dojo
1718
18−### latest-hash.hoon
19+#### `latest-hash.hoon`
1920
2021```hoon
2122/- spider
@@ -86,7 +87,7 @@ Release: https://github.com/urbit/urbit/releases/tag/urbit-os-v2.39
8687~
8788```
8889
89−## Analysis
90+### Analysis
9091
9192`fetch-json` takes a url as a tape, so we've added a core with the urls like:
9293
content/docs/userspace/threads/examples/main-loop.md+8−8
@@ -18,7 +18,7 @@ This is useful if you want to (for example) take an arbitrary number of facts.
1818
1919Here's an example of a thread that subscribes to `graph-store` for updates and nicely prints the messages (an extremely basic chat reader):
2020
21−#### chat-watch.hoon
21+#### `chat-watch.hoon`
2222
2323```hoon
2424/- spider
@@ -111,15 +111,15 @@ baz
111111~
112112```
113113
114−#### tester.hoon
114+#### `tester.hoon`
115115
116116```hoon
117117/- spider
118118/+ *strandio
119119=, strand=strand:spider
120−^- thread:spider
121−|= arg=vase
122−=/ m (strand ,vase)
120+^- thread:spider
121+|= arg=vase
122+=/ m (strand ,vase)
123123^- form:m
124124;< tid=tid:spider bind:m (start-thread %tested)
125125;< our=ship bind:m get-our
@@ -129,7 +129,7 @@ baz
129129(pure:m !>(~))
130130```
131131
132−#### tested.hoon
132+#### `tested.hoon`
133133
134134```hoon
135135/- spider
@@ -163,9 +163,9 @@ baz
163163 (pure:m ~)
164164 ==
165165--
166−^- thread:spider
166+^- thread:spider
167167|= arg=vase
168−=/ m (strand ,vase)
168+=/ m (strand ,vase)
169169^- form:m
170170;< ~ bind:m looper
171171(pure:m !>(~))
content/docs/userspace/threads/examples/poke-agent.md+2−2
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Here's a thread that lets you post a message to a chat in graph-store:
88
9−#### post-msg.hoon
9+#### `post-msg.hoon`
1010
1111```hoon
1212/- spider
@@ -55,6 +55,6 @@ Save it in `/ted`, `|commit`, and run it like:
5555
5656(obviously change the channel name to whatever you have)
5757
58−## Analysis
58+### Analysis
5959
6060Pretty simple, just use `on-poke` with an argument of `[ship term] cage` where `term` is the agent and `cage` is whatever the particular agent expects.
content/docs/userspace/threads/examples/scry.md+10−10
@@ -6,12 +6,12 @@ template = "doc.html"
66
77Here's an example of a thread that scries ames for the IP address & port of a ship and nicely prints it:
88
9−#### get-ip.hoon
9+#### `get-ip.hoon`
1010
1111```hoon
1212/- spider
1313/+ strandio
14−=, strand=strand:spider
14+=, strand=strand:spider
1515=, strand-fail=strand-fail:libstrand:spider
1616|%
1717++ process-lanes
@@ -30,9 +30,9 @@ Here's an example of a thread that scries ames for the IP address & port of a sh
3030 %- (slog leaf+"{ip}:{port}" ~)
3131 (pure:m ~)
3232--
33−^- thread:spider
33+^- thread:spider
3434|= arg=vase
35−=/ m (strand ,vase)
35+=/ m (strand ,vase)
3636^- form:m
3737=/ utarget !< (unit @p) arg
3838?~ utarget
@@ -51,20 +51,20 @@ Save as `ted/get-ip.hoon`, `|commit %home`, and run it with `-get-ip ~bitbet-bol
515134.83.113.220:60659
5252```
5353
54−## Analysis
54+### Analysis
5555
5656Here we use the `strandio` function `scry` which takes an argument of `[mold path]` where:
5757
5858- `mold` is the return type of the scry
5959- `path` is the scry path formatted like:
60− 1. vane letter and care
61− 2. desk if scrying arvo or agent if scrying a gall agent
62− 3. rest of path
63−
60+ 1. vane letter and care
61+ 2. desk if scrying arvo or agent if scrying a gall agent
62+ 3. rest of path
63+
6464In our case the mold is `(list lane:ames)` and the path is `/ax//peers/(scot %p target)/forward-lane` like:
6565
6666```hoon
6767;< lanes=(list lane:ames) bind:m (scry:strandio (list lane:ames) /ax//peers/(scot %p target)/forward-lane)
6868```
6969
70−After that we just process the result in `++ process-lanes` and print it.
70+After that we just process the result in `++ process-lanes` and print it.
content/docs/userspace/threads/examples/take-fact.md+2−2
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Taking a fact from an agent, arvo or whatever is easy. First you subscribe using `watch:strandio` or `watch-our:strandio`, then you use `take-fact:strandio` to receive the fact. Here's an example that takes an update from `graph-store` and prints the message to the dojo:
88
9−#### print-msg.hoon
9+#### `print-msg.hoon`
1010
1111```hoon
1212/- spider
@@ -44,7 +44,7 @@ Taking a fact from an agent, arvo or whatever is easy. First you subscribe using
4444
4545Create a chat on your fake zod if you don't have one already, then save it in `/ted`, `|commit %home`, and run `-print-msg`. Next, type some message in your chat and you'll see it printed in the dojo.
4646
47−## Analysis
47+### Analysis
4848
4949First we call `watch-our` to subscribe:
5050
content/docs/userspace/threads/gall/poke-thread.md+8−9
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Here's a modified agent that pokes our thread. I've replaced some off the previous stuff because it was getting a little unwieldly.
88
9−#### thread-starter.hoon
9+#### `thread-starter.hoon`
1010
1111```hoon
1212/+ default-agent, dbug
@@ -70,15 +70,15 @@ Here's a modified agent that pokes our thread. I've replaced some off the previo
7070
7171And here we've modified the thread to take the poke and return it as the result:
7272
73−#### test-thread.hoon
73+#### `test-thread.hoon`
7474
7575```hoon
76−/- spider
76+/- spider
7777/+ *strandio
78−=, strand=strand:spider
79−^- thread:spider
80−|= arg=vase
81−=/ m (strand ,vase)
78+=, strand=strand:spider
79+^- thread:spider
80+|= arg=vase
81+=/ m (strand ,vase)
8282^- form:m
8383;< vmsg=vase bind:m (take-poke %foo)
8484(pure:m vmsg)
@@ -86,13 +86,12 @@ And here we've modified the thread to take the poke and return it as the result:
8686
8787Save them, `|commit` and run it like `:thread-starter [%test-thread %blah]`. You should see:
8888
89−
9089```
9190Result: blah
9291> :thread-starter [%test-thread %blah]
9392```
9493
95−## Analysis
94+### Analysis
9695
9796In our agent we've added this card:
9897
content/docs/userspace/threads/gall/start-thread.md+9−9
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Here's an example of a barebones gall agent that just starts a thread:
88
9−#### thread-starter.hoon
9+#### `thread-starter.hoon`
1010
1111```hoon
1212/+ default-agent, dbug
@@ -20,7 +20,7 @@ Here's an example of a barebones gall agent that just starts a thread:
2020++ on-init on-init:def
2121++ on-save on-save:def
2222++ on-load on-load:def
23−++ on-poke
23+++ on-poke
2424 |= [=mark =vase]
2525 ^- (quip card _this)
2626 ?+ mark (on-poke:def mark vase)
@@ -60,14 +60,14 @@ Here's an example of a barebones gall agent that just starts a thread:
6060
6161And here's a minimal thread to test it with:
6262
63−#### test-thread.hoon
63+#### `test-thread.hoon`
6464
6565```hoon
66−/- spider
67−=, strand=strand:spider
68−^- thread:spider
69−|= arg=vase
70−=/ m (strand ,vase)
66+/- spider
67+=, strand=strand:spider
68+^- thread:spider
69+|= arg=vase
70+=/ m (strand ,vase)
7171^- form:m
7272|= strand-input:strand
7373?+ q.arg [~ %fail %not-foo ~]
@@ -109,7 +109,7 @@ Thread failed to start
109109/sys/vane/gall/hoon:<[1.370 9].[1.370 37]>
110110```
111111
112−## Analysis
112+### Analysis
113113
114114We can ignore the input logic, here's the important part:
115115
content/docs/userspace/threads/gall/stop-thread.md+8−8
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Here we've added one last card to `on-poke` to stop the thread and a little extra to `on-agent` to print things for demonstrative purposes.
88
9−#### thread-starter.hoon
9+#### `thread-starter.hoon`
1010
1111```hoon
1212/+ default-agent, dbug
@@ -91,15 +91,15 @@ Here we've added one last card to `on-poke` to stop the thread and a little extr
9191
9292We've also added a `sleep` to the thread to keep it running for demonstration.
9393
94−#### test-thread.hoon
94+#### `test-thread.hoon`
9595
9696```hoon
97−/- spider
97+/- spider
9898/+ *strandio
99−=, strand=strand:spider
100−^- thread:spider
101−|= arg=vase
102−=/ m (strand ,vase)
99+=, strand=strand:spider
100+^- thread:spider
101+|= arg=vase
102+=/ m (strand ,vase)
103103^- form:m
104104;< =path bind:m take-watch
105105;< ~ bind:m (send-raw-card [%give %fact ~[path] %update !>("message 1")])
@@ -141,7 +141,7 @@ Thread cancelled successfully
141141Thread failed: cancelled
142142```
143143
144−## Analysis
144+### Analysis
145145
146146The card we've added to our agent:
147147
content/docs/userspace/threads/gall/take-facts.md+8−8
@@ -8,7 +8,7 @@ Most of the time you'll just want the final result like how we did previously. S
88
99Here we've added another card to subscribe for any facts sent by the thread and some small changes to `on-agent`:
1010
11−#### thread-starter.hoon
11+#### `thread-starter.hoon`
1212
1313```hoon
1414/+ default-agent, dbug
@@ -80,15 +80,15 @@ Here we've added another card to subscribe for any facts sent by the thread and
8080
8181We've also made some changes to the thread:
8282
83−#### test-thread.hoon
83+#### `test-thread.hoon`
8484
8585```hoon
86−/- spider
86+/- spider
8787/+ *strandio
88−=, strand=strand:spider
89−^- thread:spider
90−|= arg=vase
91−=/ m (strand ,vase)
88+=, strand=strand:spider
89+^- thread:spider
90+|= arg=vase
91+=/ m (strand ,vase)
9292^- form:m
9393;< =path bind:m take-watch
9494;< ~ bind:m (send-raw-card [%give %fact ~[path] %update !>("message 1")])
@@ -127,7 +127,7 @@ message 4
127127Thread failed: not-foo
128128```
129129
130−## Analysis
130+### Analysis
131131
132132In our agent's `on-poke` arm we've added another card to subscribe to `/thread/[tid]/updates`:
133133
content/docs/userspace/threads/gall/take-result.md+9−9
@@ -6,7 +6,7 @@ template = "doc.html"
66
77Here we've added an extra card to subscribe for the result and a couple of lines in on-agent to test if it succeeded:
88
9−#### thread-starter.hoon
9+#### `thread-starter.hoon`
1010
1111```hoon
1212/+ default-agent, dbug
@@ -71,14 +71,14 @@ Here we've added an extra card to subscribe for the result and a couple of lines
7171--
7272```
7373
74−#### test-thread.hoon
74+#### `test-thread.hoon`
7575
7676```hoon
77−/- spider
78−=, strand=strand:spider
79−^- thread:spider
80−|= arg=vase
81−=/ m (strand ,vase)
77+/- spider
78+=, strand=strand:spider
79+^- thread:spider
80+|= arg=vase
81+=/ m (strand ,vase)
8282^- form:m
8383|= strand-input:strand
8484?+ q.arg [~ %fail %not-foo ~]
@@ -101,9 +101,9 @@ Thread started successfully
101101Thread failed: not-foo
102102```
103103
104−## Analysis
104+### Analysis
105105
106−In `on-poke` we've added an extra card *before* the `%spider-start` poke to subscribe for the result:
106+In `on-poke` we've added an extra card _before_ the `%spider-start` poke to subscribe for the result:
107107
108108```hoon
109109[%pass /thread/[ta-now] %agent [our.bowl %spider] %watch /thread-result/[tid]]
content/docs/userspace/threads/http-api.md+1−1
@@ -26,7 +26,7 @@ When Spider receives an HTTP request, the following steps happen:
2626
2727Thus, it's important to understand that the original HTTP request and final HTTP response must contain JSON data, and therefore the input & output `mark`s you specify must each have a `mark` file in `/mar` that includes a conversion method for `json -> inputMark` and `outputMark -> json` respectively.
2828
29−# Example
29+## Example
3030
3131Here we'll look at running Spider threads through Eyre.
3232
content/docs/userspace/threads/reference.md+5−13
@@ -4,15 +4,7 @@ weight = 50
44template = "doc.html"
55+++
66
7−## Spider
8−
9−- [Start a thread](#start-thread)
10−- [Stop a thread](#stop-thread)
11−- [Subscribe to thread result](#subscribe-to-thread-result)
12−- [Subscribe to thread](#subscribe-to-thread)
13−- [Poke a thread](#poke-thread)
14−
15−### Start thread
7+## Start thread
168
179Poke `spider` with mark `%spider-start` and a vase containing `start-args`:
1810
@@ -34,7 +26,7 @@ Where:
3426[%pass /some-path %agent [our.bowl %spider] %poke %spider-start !>([~ `tid %foo !>(~)])]
3527```
3628
37−### Stop thread
29+## Stop thread
3830
3931Poke `spider` with mark `%spider-stop` and a vase containing `[tid ?]`, where:
4032
@@ -47,7 +39,7 @@ Poke `spider` with mark `%spider-stop` and a vase containing `[tid ?]`, where:
4739[%pass /some-path %agent [our.bowl %spider] %poke %spider-stop !>([tid %.y)]
4840```
4941
50−### Subscribe to thread result
42+## Subscribe for result
5143
5244Spider will send the result on `/thread-result/[tid]` so you can subscribe there for the result. You should subscribe before starting the thread.
5345
@@ -62,7 +54,7 @@ The result will have a mark of either `%thread-fail` or `%thread-done`.
6254[%pass /some-path %agent [our.bowl %spider] %watch /thread-result/[tid]]
6355```
6456
65−### Subscribe to thread
57+## Subscribe to thread
6658
6759You can subscribe to a thread on `/thread/[tid]/path`. Note this is for facts sent off by the thread while it's running, not the final result. The path depends on the particular thread.
6860
@@ -72,7 +64,7 @@ You can subscribe to a thread on `/thread/[tid]/path`. Note this is for facts se
7264[%pass /some-path %agent [our.bowl %spider] %watch /thread/[tid]/thread-path]
7365```
7466
75−### Poke Thread
67+## Poke thread
7668
7769To poke a thread you poke spider with a mark of `%spider-input` and a vase of `[tid cage]`.
7870
content/events/2021-09-16-developer-call-azimuth-scaled.mdnew file+46
@@ -0,0 +1,46 @@
1++++
2+title = "Developer Call: Azimuth, Scaled"
3+starts = "2021-09-16T09:00:00"
4+ends = "2021-09-16T10:00:00"
5+timezone = "America/Los_Angeles"
6+location = "Online Event"
7+image = ""
8+registration_url = "https://www.meetup.com/urbit-sf/events/280697973/"
9+description = "Discussion of Layer 2 Architecture and Implementation"
10+dark = false
11+youtube = "UScbZ6n7H-4"
12+
13+[[hosts]]
14+name = "Philip Monk"
15+patp = "~wicdev-wisryt"
16+
17+[[guests]]
18+name = "Jimmy Young"
19+patp = "~fallyn-balfus"
20+
21+[[guests]]
22+name = " Jose Cisneros"
23+patp = "~norsyr-torryn"
24+
25+[[guests]]
26+name = " Tom Holford"
27+patp = "~datder-sonnet"
28+
29+[[guests]]
30+name = " Mark"
31+patp = "~palfun-foslup"
32+
33+[[guests]]
34+name = " Poprox"
35+patp = "~datnut-pollen"
36+
37+[[guests]]
38+name = " Will Galebach"
39+patp = "~fabnev-hinmur"
40+
41+
42++++
43+
44+ETH Gas prices are too high! The growth of Urbit has been greatly hampered this year by the cost of activating planets. Join Philip Monk (`~wicdev-wisryt`) and the entire L2 team as they present the work completed to date to implement Layer 2 naive rollups. Come see a demo of the new Bridge front end, first migrating a star and spawning some planets, then activating a planet, all on L2.
45+
46+This call will be a great opportunity for galaxy owners to get their questions answered for the upcoming vote on Layer 2 implementation. It will also be good for anyone in the community that wants a comprehensive look at how naive rollups will work to bring down planet activation costs. If you're technically inclined, be sure to check out [Philip's walkthrough](https://www.youtube.com/watch?v=lQdqCwYIBoU) of the naive rollup code ahead of time so you can ask questions.
content/events/2021-09-24-urbit-outside.mdnew file+23
@@ -0,0 +1,23 @@
1++++
2+title = "Urbit Outside"
3+starts = "2021-09-24T15:00:00"
4+ends = "2021-09-24T19:00:00"
5+timezone = "America/Los_Angeles"
6+location = "Sibley Staging Area, 6800 Skyline Blvd. Oakland, CA"
7+image = "https://secure.meetupstatic.com/photos/event/3/0/5/7/clean_498732375.jpeg"
8+registration_url = "https://www.meetup.com/urbit-sf/events/280696590/?isFirstPublish=true"
9+description = "Urbit but outside"
10+dark = true
11++++
12+
13+![](https://secure.meetupstatic.com/photos/event/3/0/5/7/clean_498732375.jpeg)
14+
15+For our second post-Covid Urbit IRL, we're heading to the East Bay!
16+
17+Please join us at Sibley Preserve for a hangout and (optional) hike.
18+
19+We'll meet at the Sibley Staging Area at 3:00PM, and leave for a casual walk to the caldera and labyrinth at 4:00PM.
20+
21+Hiking shoes, snacks, water, and picnic blankets/chairs are encouraged! There are restrooms and water at the Staging Area.
22+
23+Please join the channel 'Urbit Outside ~[masked]' in ~bitbet-bolbel/urbit-community to get in touch with the gang.
content/events/2021-10-15-assembly.mdnew file+26
@@ -0,0 +1,26 @@
1++++
2+title = "Assembly"
3+starts = "2021-10-15T15:30:00"
4+ends = "2021-10-17T18:00:00"
5+timezone = "America/Chicago"
6+location = "Distribution Hall, 1500 East 4th St. Austin, TX"
7+image = "https://storage.googleapis.com/media.urbit.org/site/events/assembly-image.png"
8+registration_url = "http://assembly.urbit.org/"
9+description = "Grow a new internet"
10+dark = true
11++++
12+
13+![](https://storage.googleapis.com/media.urbit.org/site/events/assembly-event-image.png)
14+
15+Assembly 2021 marks a pivotal moment in Urbit: software distribution. Yes, Urbit is ready to be built upon. We’ll spend three days together doing just that.
16+
17+- Learn about building applications on Urbit (it’s easy).
18+- Find out how to work and create on a platform that is fully owned by you.
19+- Meet with the growing ecosystem of communities and businesses built on Urbit.
20+
21+If you’re simply into calm computing, well, there will be plenty of that too.
22+
23+You’ll choose between two tracks:
24+
25+- Assembly: Assemble software or work together on projects in small groups
26+- Workshop: A two day workshop taught by Neal Davis, Urbit for Developers
content/getting-started/planet.md+4−2
@@ -26,8 +26,10 @@ Using hosting does mean that you're trusting your provider with your data, but s
2626
2727Current hosting providers are:
2828
29−- [Tlon Corporation](https://tlon.io): Tlon is the creator of Landscape and is primarily responsible for the creation and maintenance of the Urbit project itself.
30−- [Get an Urbit ID](https://www.geturbitid.com/): Get an Urbit ID was the first hosting provider and is run by a member of the Urbit community.
29+- [escape pod store](https://www.escapepod.store/)
30+- [Third Earth](https://third.earth/)
31+- [Tlon Corporation](https://tlon.io)
32+- [UrbitHost](https://urbithost.com)
3133
3234### Choose to run your planet on your computer or on a cloud service
3335
content/grants/a-collaborative-web-mapping-system-for-urbit.md → content/grants/a-network-enabled-spatial-database-and-demonstration-app.mdrenamed+26−21
@@ -1,5 +1,5 @@
11+++
2−title = "A Collaborative Web Mapping System for Urbit"
2+title = "A network enabled spatial database and demonstration app for Urbit"
33date = "2020-09-02"
44
55[taxonomies]
@@ -16,7 +16,7 @@ completed = false
1616link = ""
1717+++
1818
19−# A Collaborative Web Mapping System for Urbit
19+# A network enabled spatial database and demonstration app for Urbit
2020
2121##### Goal
2222
@@ -24,15 +24,15 @@ Allow small scale spatial data to be stored and retrieved in Urbit, for use in l
2424
2525##### Background/Motivation
2626
27−This proposal has grown out of a previous bounty which called for Open Street Map data to be ingested into Urbit. Through discussion it was decided that as OSM already provides a free, open and widely available base map and street network, a more useful system for Urbit would provide a method of storing spatial documents, which can overlay upon existing spatial data such as Open Street Map tiles, be collected together for use in landscape apps, and combined, shared and collaboratively edited with other ships as 'Portals'. A spatial document, for the purpose of this proposal, is a single valid GeoJSON[^1] document. A spatial document could contain a single geometry, a feature (geometry with associated properties), or a collection of features (a GeoJSON 'featurecollection'). A Portal is a collection of spatial documents, with associated metadata and can include spatial documents from multiple ships, whose owners can collaboratively edit or construct maps.
27+This proposal has grown out of a previous bounty which called for Open Street Map data to be ingested into Urbit. Through discussion it was decided that as OSM already provides a free, open and widely available base map and street network, a more useful system for Urbit would provide a method of storing spatial documents, which can overlay upon existing spatial data such as Open Street Map tiles, be collected together for use in landscape apps, ...and poast .... A spatial document, for the purpose of this proposal, is a single valid GeoJSON[^1] document. A spatial document could contain a single geometry, a feature (geometry with associated properties), or a collection of features (a GeoJSON 'featurecollection'). The fridge store is a collection of poastcards, both those stored locally and those shared from other ships.
2828
2929##### Overview
3030
3131- The proposed system will follow the Urbit store-hook-view pattern as described here https://docs.google.com/document/d/1hS_UuResG1S4j49_H-aSshoTOROKBnGoJAaRgOipf54/edit?ts=5d533e42
3232
33−- The system will be implemented in two stages. The first stage will implement simple spatial types and a spatial document store, demonstrate input and output of spatial data, and provide a minimal landscape app for creating and editing spatial documents, but limited to the local ship. The second stage will build on the first and provide Portals; collections of spatial documents and associated metadata, which can be shared across ships - similar to the 'canvas' landscape app (https://github.com/yosoyubik/canvas).
33+- The system will be implemented in two milestones. The first milestone will implement simple spatial types and a spatial document store, demonstrate input and output of spatial data, and provide a minimal landscape app for creating and editing spatial documents, but limited to the local ship. The second milestone will build on the first and provide a network enabled store and web application for sharing 'poastcards'; spatial documents with text and an image, which can be shared with other ships and viewed through the web application ('Poast').
3434
35−#### Stage 1 - A spatial store, spatial types, simple demo app
35+#### Milestone 1 - A spatial store, spatial types, simple demo app
3636
3737##### Spatial Document Store and Tools
3838
@@ -42,28 +42,32 @@ This proposal has grown out of a previous bounty which called for Open Street Ma
4242- Implement a generator which can read a GeoJSON document in to the store, and can write a constructed document out of the store.
4343- Implement a test suite based around GeoJSON specification and the above generator.
4444
45−##### Hook, View and Landscape App
45+##### Hook, View and (not Landscape) App
4646
4747- Implement the hook and view components of store-hook-view pattern atop the spatial data store.
48−- In this stage the system will only allow data to be viewed/edited on the local ship.
48+- In this milestone the system will only allow data to be viewed/edited on the local ship.
4949- Integrating spatial information with Landscape apps (which can render vector features using available javascript libraries/clients such as Openlayers and Leaflet).
5050- Overall, somewhat like 'urlayers' hackathon project (github.com/gusmacaulay/urlayers), but with a sane data store and following the store-hook-view pattern.
5151
52−#### Stage 2 - A Portal Store and Collaboration System
52+#### Milestone 2 - A networked spatial store, and full fledged demo app
5353
54−##### Store (Metadata, Portals)
54+##### Network enabled store ('Fridge')
5555
56−- Expands on the Stage 1 architecture with additional store
57−- The additional store which will house Portals. Portals consists of a map document with metadata (area of interest bounds, styling[^3], author/s etc.), and which may contain many spatial documents, which may be stored locally or on other ships.
58−- It may be that the Urbit graph store is the appropriate place for this information, exploring using graph store vs a custom Portal store will be undertaken as part of Stage 2.
56+- Expands on the Stage 1 architecture with an enhanced store ('Fridge') and an app ('Poast').
57+- The enhanced store (a 'Fridge'), which will house 'Poastcards'. Poastcards consist of an image,text, a geometry. A poastcard can be represented in a geojson feature, making heavy use of the the feature properties (a key value object, part of the geojson specification) to store the non-spatial information.
58+- It may be that the Urbit graph store is the appropriate way to share/link Poastcards, exploring using graph store vs a custom Fridge store will be undertaken as part of Stage 2.
59+- 'Poast' is a PoastCard viewing and sharing app, for landscape. We will implement a mvp/polished demo for this milestone.
60+- 'Poast' will be made available through the upcoming software distribution system as part of this milestone.
5961
60−##### Hook/View/App
61−
62−- A hook/view/landscape app which allows for displaying and interacting with Portals;
63− - Editing of individual Portals
64− - Sharing of Portals. As a resource owned by a ship, a Portal is somewhat analogous to a chat channel. Individual spatial documents within the Portal can come from local or foreign ship. The owner of the Portal will be able to set permissions on spatial documents within the portal.
65−- The overall concept of the Portal app can be likened to the 'canvas' landscape app, but for maps and cartography. The key feature is to present a Portal map with spatial data from different ships and collaborative editing.
62+##### Rich demonstration App ('Poast')
6663
64+- A hook/view/landscape app which allows for displaying and interacting with Poastcards/Fridge store;
65+ - Editing/Creation of Poastcards via a web ui.
66+ - Sharing of Poastcards; a Poastcard is a resource owned by a ship, once create they can be shared read-only with others.
67+- The overall concept of the Poast app can be likened to the experience of sending postcards and personal letters in the pre internet era. Unlike most present day social media, Poast will not have a feed.
68+- Poast will be minimally landscape integrated, insofar is it can be launched from a tile and follow landscape styling.
69+- For this milestone only a minimal set of features will be included to allow for creating, sharing, and viewing of Poastcards.
70+
6771##### Footnotes
6872
6973[^1]:
@@ -81,12 +85,13 @@ I am a beef farmer and software engineer (geospatial web applications). I work w
8185
8286## Milestones
8387
84−### Stage 1
88+### Milestone 1
8589
86902 stars
8791A spatial store, spatial types, simple demo landscape app
8892
89−### Stage 2
93+### Milestone 2
9094
91952 stars
92−A Portal Store and Collaboration System
96+A network enabled spatial store, for sharing spatial data in urbit.
97+A rich demostration app, for sharing spatial data, images and text.
content/grants/ethereum-abi-parser.md+1
@@ -13,6 +13,7 @@ reward = 2
1313assignee = "xiphiness"
1414id = "1241532070"
1515completed = false
16+canceled = true
1617link = ""
1718+++
1819
content/grants/machine-learning.md+1−1
@@ -1,6 +1,6 @@
11+++
22title = "Urbit Supervised Machine Learning Library"
3−date = 2021-08-25
3+date = "2021-08-25"
44[taxonomies]
55grant_type = ["Proposal"]
66grant_category = ["App Dev: Other"]
content/grants/matrices-in-hoon.md+1−1
@@ -12,7 +12,7 @@ description = "Add array types to Hoon."
1212reward = 5
1313assignee = "N E Davis"
1414id = "611660914"
15−completed = false
15+completed = true
1616link = ""
1717+++
1818
content/grants/planet-sales.md+1−1
@@ -1,6 +1,6 @@
11+++
22title = "Planet Sales via Urbit"
3−date = 2021-08-25
3+date = "2021-08-25"
44[taxonomies]
55grant_type = ["Proposal"]
66grant_category = ["App Dev: Other"]
content/grants/urbit-notes.md+1−1
@@ -1,6 +1,6 @@
11+++
22title = "Urbit Notes"
3−date = 2021-08-25
3+date = "2021-08-25"
44[taxonomies]
55grant_type = ["Proposal"]
66grant_category = ["App Dev: Other"]
content/grants/~home-urbit.mdnew file+120
@@ -0,0 +1,120 @@
1++++
2+title = "~Home-Urbit"
3+date = "2021-01-01"
4+[taxonomies]
5+grant_type = ["Proposal"]
6+grant_category = ["App Dev", "Dev tool"]
7+[extra]
8+image = "https://user-images.githubusercontent.com/13405632/121785641-98599100-cbc3-11eb-861c-77a95a7db030.png"
9+description = "A turn-key Urbit deployment for your personal server"
10+reward = 4
11+assignee = "~sipsen-pilser"
12+id = ""
13+completed = false
14+link = ""
15++++
16+
17+
18+# Urbit Grant for ~Home-Urbit
19+
20+Home-Urbit is a turn-key solution for self-hosting Urbit
21+
22+
23+Goal: Create a productized version of Home-Urbit with an impecable UX. Setup Urbit on a dedicated piece of hardware (or a virtualized one) with as much less friction as possible.
24+
25+Urbit is your last computer.
26+
27+Vision: Create the software foundations for a dedicated piece of hardware that you can order, your own Urbit. It will enable to easily:
28+1) Install Urbit on the hardware during manufacturing.
29+2) Manage Urbit (and the hw) during usage.
30+
31+Current implementation is based on balenaCloud and balenaOS.
32+
33+[balenaOS](https://www.balena.io/os/?) is a lightweight OS that is crafted for container workloads. It has various optimizations for IoT, including minimal write by the OS on the disk.
34+
35+[balenaCloud](https://www.balena.io/cloud/) is a PaaS to build, deploy and manage IoT workloads. Basically you send a docker-compose project, it is build on their build-servers and the container images are sent to the IoT devices to run. You can manage the whole lifecycle of the app through their web dashboard.
36+
37+balenaCloud has a free tier for up to 10 devices per account, so that's perfect for our ~Home-Urbit application.
38+
39+Moreover, with balenaCloud, users can easily run other applications alongisde Urbit. Whatever application that can run inside a container.
40+
41+The angle is to run the software you need, on your own. With containers that's easier than before. A sovereign individual should own their device, applications and data.
42+
43+Examples:
44+
45+- Netdata monitoring agent
46+- [Radicle seed node](https://radicle.xyz/)
47+- Personal website server
48+- Media center
49+- NAS
50+- etc.
51+
52+
53+## Milestones
54+
55+### Refine current setup (aarch64 architecture) - 1 Star
56+
57+- Fix slow reverse-proxy by nginx (or replace nginx). This is needed so that the user can access Urbit and the various services that are hosted alongside (e.g minio S3) without having to remember ports.
58+- Make ~home-urbit play nice with external SSD. The plan is to install urbit on the SSD and the OS on the sd card. It should give us the speed bost that we are looking for.
59+
60+
61+### Make Home-Urbit compatible with x86 architecture - 1 Star
62+
63+balenaOS is compatible with many different architectures. Make home-urbit compatible with x86, so that users can install balenaOS and run the urbit in computers such as Intel NUCs.
64+
65+A NUC could house multiple Urbits or could enable the user to host many different groups with many users.
66+
67+
68+### Solve the wide accessibility issue - 2 Stars
69+
70+Home-Urbit should be widely accessible for 2 reasons:
71+1) You should be able to access it no matter your location
72+2) MinioS3 demands accessibility by all the parties to be able to view the asset that you linked
73+
74+BalenaCloud offers VPN accessibility that is hardcoded to port 80, doesn't cover requirement 2.
75+
76+Another VPN service would cover both requirements, but not for everybody, just the users of the VPN. It's great to access your device, but not great for others to access your Minio instance.
77+
78+Router forwarding is an option, but it's involved and requires the user to use 3rd-party intructions, since it's different for every router.
79+
80+A universal solution should be found. For example, create a publicly accessible server that sustains reverse `ssh` tunnels to the home-urbit. It simply pipes all the connections that target a specific port to the `home-urbit` at another port. It works as a proxy.
81+
82+Since it's a publicly accessible VM, it solves both requirements. The connection originate from home-urbit itself and it's a tunnel, so there is no need for port forwarding.
83+
84+Alternative solutions will be researched. The chosen solution will be:
85+
86+1) The most reliable
87+2) The easiest
88+3) The fastest
89+
90+We will consider the above attributes in that order of importance.
91+
92+For the upkeep cost, there are a couple of options:
93+1) Discuss with Urbit foundation, generalise solution and offer it to all Urbits. Upkeep cost to be covered by UF.
94+2) Create a simple SaaS with a very cheap free/paid option
95+3) Offer it for free, shoulder cost and use something like Patreon or a similar service.
96+
97+### Make offline version - (> 2 Stars)
98+
99+It is understandable that some users may not want to manage their Urbit via balenaCloud. This milestone will give enable them to install home-urbit in the same* frictionless manner, while doing that offline.
100+
101+Most likely, it will be more invovled than using balenaCloud.
102+
103+It should have feature-parity with the balenaCloud deployment. For example, in place of the balenaCloud's VPN service, there will be an extra container that runs tailscale.
104+
105+3 options:
106+
107+- balenaOS (it's OSS) without the balenaCloud component. You manage it locally via balena-cli.
108+- Other OS angled for running container workloads (e.g bottleRocket )
109+- docker-compose + helper scripts.
110+
111+### Make Content - 2 Stars
112+
113+- Produce all relevant docs
114+- Produce a first-aid handbook (if X, do Y). Talk with Urbit employees and community members to identify causes.
115+- Produce video about the project
116+- Write technical blog post
117+ - Why home-urbit
118+ - Design choices (e.g why containers, why balena). Intro to technologies.
119+ - Running urbit in container, gotchas and bash scripts that were used.
120+ - Tips on running Urbit commands via HTTP
content/using/id/guide-to-breaches.md+1−3
@@ -21,9 +21,7 @@ has forgotten about it.
2121Breaches often fix connectivity issues, but should only be used as a last
2222resort. Before performing a breach, look at alternative fixes in the [Ship
2323Troubleshooting](/using/os/ship-troubleshooting) guide. Also reach out for help in the
24−Help channel in the Urbit Community group `~bitbet-bolbel/urbit-community`, or,
25−failing that, in the `#ship-starting-support` channel in our [Discord
26−server](https://discord.gg/n9xhMdz) to see if there is another option.
24+Help channel in the Urbit Community group `~bitbet-bolbel/urbit-community` to see if there is another option.
2725Connectivity issues are typically related to a bug, and you may be able to help
2826us fix it by emailing us at `support@urbit.org`.
2927
content/using/os/getting-started.md+3−3
@@ -144,16 +144,16 @@ moon: ~faswep-navred-sampel-palnet
144144\/ \/
145145```
146146
147−You must manually edit the output of `|moon` to get the correct format for the `<key>`:
147+You must manually edit the output of `|moon` to get the correct format for the `key`:
148148
149149- strip out the `\/`
150150
151151- combine into one line, omitting spaces
152152
153−`<key>` would be `0w5cT5t.wCO6i.~e1xg.Oz0qb.QNO6I.3Kt2T.h9M9F.U3vU~.X3Qu0.gtb19.IYTkY.80kWZ.SIEUE.DXa8i.TiDof.o3-1C.RHLKS.k81M0.ecz5o.ic0Bg.600g1`
153+`key` would be `0w5cT5t.wCO6i.~e1xg.Oz0qb.QNO6I.3Kt2T.h9M9F.U3vU~.X3Qu0.gtb19.IYTkY.80kWZ.SIEUE.DXa8i.TiDof.o3-1C.RHLKS.k81M0.ecz5o.ic0Bg.600g1`
154154in this example.
155155
156−Put `<key>` in a file and that file becomes `<keyfile>`.
156+Put `key` in a file and that file becomes `keyfile`.
157157
158158You can use the resulting output in the same installation flow from the [Installing Urbit](/getting-started/) guide, following the same scheme as for booting a planet. That scheme is:
159159
content/using/os/ship-troubleshooting.md+2−2
@@ -72,10 +72,10 @@ Personal breaches often fix connectivity issues, but should only be used as a
7272last resort. To find out how to perform a personal breach, check out our [Guide
7373to Breaches](/using/id/guide-to-breaches). Before taking such a drastic measure, try
7474other methods in this guide. You can also ask for help on
75−in the Help channel in the Urbit Community group at `~bitbet-bolbel/urbit-community` or, failing that, in the `#ship-starting-support`
76−channel in our [Discord server](https://discord.gg/n9xhMdz).
75+in the Help channel in the Urbit Community group at `~bitbet-bolbel/urbit-community`.
7776
7877### Reset `+code`
78+
7979**WARNING:** Do not reset your +code if you are using a hosted ship. You will be locked out. Please contact your hosting provider for more information.
8080
8181You login to Landscape using the camel-case phrase obtained from dojo by
lib/constants.js+55−1
@@ -3,8 +3,62 @@ export const contact = {
33 email: "support@urbit.org",
44 twitter: "https://twitter.com/urbit",
55 github: "https://github.com/urbit/urbit",
6− discord: "https://discord.gg/Mae2bk7",
76 urbitCommunity: "~bitbet-bolbel/urbit-community",
87 newsletter:
98 "https://urbit.us11.list-manage.com/subscribe/post?u=972a03db9e0c6c25bb58de8c8&amp;id=be143888d2",
109};
10+
11+export const eventKeys = [
12+ "title",
13+ "ends",
14+ "location",
15+ "image",
16+ "registration_url",
17+ "youtube",
18+ "description",
19+ "starts",
20+ "hosts",
21+ "slug",
22+ "guests",
23+ "content",
24+ "dark",
25+ "timezone",
26+];
27+
28+export const postKeys = [
29+ "title",
30+ "slug",
31+ "date",
32+ "description",
33+ "content",
34+ "extra",
35+];
36+
37+export const grantsKeys = [
38+ "title",
39+ "slug",
40+ "date",
41+ "description",
42+ "content",
43+ "extra",
44+ "taxonomies",
45+];
46+
47+export const mediaKeys = [
48+ "title",
49+ "slug",
50+ "date",
51+ "description",
52+ "content",
53+ "extra",
54+];
55+
56+export const updatesKeys = [
57+ "title",
58+ "slug",
59+ "date",
60+ "description",
61+ "content",
62+ "author",
63+ "ship",
64+];
lib/hooks.js+22
@@ -35,3 +35,25 @@ export function useCopyToClipboard(text, notifyTimeout = 1000) {
3535
3636 return [copyStatus, copy];
3737}
38+
39+function getStorageValue(key, defaultValue) {
40+ // getting stored value
41+ if (typeof window !== "undefined") {
42+ const saved = window.localStorage.getItem(key);
43+ const initial = saved !== null ? JSON.parse(saved) : defaultValue;
44+ return initial;
45+ }
46+}
47+
48+export const useLocalStorage = (key, defaultValue) => {
49+ const [value, setValue] = useState(() => {
50+ return getStorageValue(key, defaultValue);
51+ });
52+
53+ useEffect(() => {
54+ // storing input name
55+ window.localStorage.setItem(key, JSON.stringify(value));
56+ }, [key, value]);
57+
58+ return [value, setValue];
59+};
lib/lib.js+1−1
@@ -182,7 +182,7 @@ export function formatTimeZone(dateTimeObject) {
182182
183183// Formats a DateTime object to something like "Jul 15, 2021"
184184export function formatDate(dateTimeObject) {
185− return dateTimeObject.toLocaleString(DateTime.DATE_HUGE);
185+ return dateTimeObject.toLocaleString(DateTime.DATE_FULL);
186186}
187187
188188// export function generateCrumbs(path) {
next.config.js+1
@@ -1,4 +1,5 @@
11module.exports = {
2+ reactStrictMode: false,
23 // target: 'serverless',
34 webpack: (config, { isServer }) => {
45 // Fixes npm packages that depend on `fs` module
package-lock.json+5
@@ -5748,6 +5748,11 @@
57485748 "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.1.2.tgz",
57495749 "integrity": "sha512-vfZtOZNivwd/aI+ZZH1Grx0eQBdbV1UI3pB9p65jbW5guHHdSIPpKsND6XmaiZXP5REOOc9Ckfr36ChswPqwsA=="
57505750 },
5751+ "react-use-localstorage": {
5752+ "version": "3.5.3",
5753+ "resolved": "https://registry.npmjs.org/react-use-localstorage/-/react-use-localstorage-3.5.3.tgz",
5754+ "integrity": "sha512-1oNvJmo72G4v5P9ytJZZTb6ywD3UzWBiainTtfbNlb+U08hc+SOD5HqgiLTKUF0MxGcIR9JSnZGmBttNLXaQYA=="
5755+ },
57515756 "readable-stream": {
57525757 "version": "2.3.7",
57535758 "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
package.json+2−1
@@ -24,6 +24,7 @@
2424 "fuzzy-search": "^3.2.1",
2525 "gray-matter": "^4.0.3",
2626 "html-entities": "^2.3.2",
27+ "luxon": "^2.0.2",
2728 "next": "10.2.3",
2829 "patch-package": "^6.4.7",
2930 "postcss": "^8.3.0",
@@ -31,6 +32,7 @@
3132 "react-dom": "17.0.2",
3233 "react-hotkeys": "^2.0.0",
3334 "react-swipeable": "^6.1.2",
35+ "react-use-localstorage": "^3.5.3",
3436 "remark": "^13.0.0",
3537 "remark-gfm": "^1.0.0",
3638 "remark-heading-id": "^1.0.0",
@@ -39,7 +41,6 @@
3941 "remark-slug": "^6.0.0",
4042 "tailwindcss": "^2.1.4",
4143 "toml": "^3.0.0",
42− "luxon": "^2.0.2",
4344 "unified": "^9.2.1"
4445 },
4546 "devDependencies": {
pages/blog/[slug].js+9−18
@@ -18,6 +18,7 @@ import SingleColumn from "../../components/SingleColumn";
1818import Section from "../../components/Section";
1919import Contact from "../../components/Contact";
2020import PostPreview from "../../components/PostPreview";
21+import TwoUp from "../../components/TwoUp";
2122import { decode } from "html-entities";
2223
2324export default function Post({
@@ -64,24 +65,14 @@ export default function Post({
6465 <Contact />
6566 </Section>
6667 <Section wide className="flex">
67− {previousPost === null ? (
68− <div className={"w-1/2 mr-4"} />
69− ) : (
70− <PostPreview
71− title="Previous Post"
72− post={previousPost}
73− className="mr-4 w-1/2"
74− />
75− )}
76− {nextPost === null ? (
77− <div className={"w-1/2 ml-4"} />
78− ) : (
79− <PostPreview
80− title="Next Post"
81− post={nextPost}
82− className="ml-4 w-1/2"
83− />
84− )}
68+ <TwoUp>
69+ {nextPost ? (
70+ <PostPreview title="Next Post" post={nextPost} />
71+ ) : null}
72+ {previousPost ? (
73+ <PostPreview title="Previous Post" post={previousPost} />
74+ ) : null}
75+ </TwoUp>
8576 </Section>
8677 </SingleColumn>
8778 <Footer />
pages/events.js+5−19
@@ -19,6 +19,8 @@ import {
1919 formatTime,
2020} from "../lib/lib";
2121
22+import { eventKeys } from "../lib/constants";
23+
2224export default function Events({ events, search }) {
2325 const post = {
2426 title: "Events",
@@ -71,7 +73,7 @@ export default function Events({ events, search }) {
7173 <h3 className="text-green-400">Happening Now</h3>
7274 </div>
7375 {happeningNow.map((post) => {
74− return <EventPreview big event={post} />;
76+ return <EventPreview big event={post} className="mb-8" />;
7577 })}
7678 </>
7779 ) : null}
@@ -81,7 +83,7 @@ export default function Events({ events, search }) {
8183 <h3 className="text-green-400">Coming Soon</h3>
8284 </div>
8385 {futureEvents.map((post) => {
84− return <EventPreview big event={post} />;
86+ return <EventPreview big event={post} className="mb-8" />;
8587 })}
8688 </>
8789 ) : null}
@@ -100,23 +102,7 @@ export default function Events({ events, search }) {
100102}
101103
102104export async function getStaticProps() {
103− const events = getAllEvents(
104− [
105− "title",
106− "ends",
107− "location",
108− "image",
109− "registration_url",
110− "youtube",
111− "description",
112− "starts",
113− "hosts",
114− "guests",
115− "dark",
116− "slug",
117− ],
118− "events"
119− );
105+ const events = getAllEvents(eventKeys, "events");
120106
121107 return {
122108 props: { events },
pages/events/[slug].js+37−90
@@ -21,8 +21,16 @@ import SingleColumn from "../../components/SingleColumn";
2121import Contact from "../../components/Contact";
2222import EventPreview from "../../components/EventPreview";
2323import Section from "../../components/Section";
24−import { Person, ReadableList, ShowOrHide } from "../../components/Snippets";
24+import TwoUp from "../../components/TwoUp";
25+
26+import {
27+ Person,
28+ ReadableList,
29+ ShowOrHide,
30+ DateRange,
31+} from "../../components/Snippets";
2532import { decode } from "html-entities";
33+import { eventKeys } from "../../lib/constants";
2634
2735export default function Event({
2836 event,
@@ -50,17 +58,11 @@ export default function Event({
5058 <Section narrow short>
5159 <h1>{event.title}</h1>
5260 <h3 className="mt-6">{event.description}</h3>
53− <p className="text-wall-600 mt-6">{formatDate(starts)}</p>
54− <p className="text-wall-600">
55− {formatTime(starts)}
56− <ShowOrHide condition={typeof event.ends !== "undefined"}>
57− {` to ${formatTime(ends)}`}
58− {" " + formatTimeZone(ends)}
59− </ShowOrHide>
60− </p>
61− <div className="mt-6">
61+ <p className="mt-6">{event.location}</p>
62+ <DateRange starts={starts} ends={ends} className="text-wall-400" />
63+ <div>
6264 <ShowOrHide condition={event.hosts}>
63− <p>
65+ <p className="mt-6">
6466 {"Hosted by "}
6567 <ReadableList>
6668 {event.hosts?.map((host, index) => {
@@ -103,8 +105,9 @@ export default function Event({
103105 </div>
104106 ) : null}
105107 </Section>
106− <Section short wide>
107− {event.youtube ? (
108+
109+ {event.youtube ? (
110+ <Section short wide>
108111 <iframe
109112 className="rounded-xl"
110113 width="100%"
@@ -114,8 +117,9 @@ export default function Event({
114117 allow="encrypted-media"
115118 allowFullScreen
116119 ></iframe>
117− ) : null}
118− </Section>
120+ </Section>
121+ ) : null}
122+
119123 <Section short narrow className="markdown">
120124 <article
121125 className="pt-12 w-full"
@@ -126,22 +130,21 @@ export default function Event({
126130 <Contact />
127131 </Section>
128132 <Section wide className="flex">
129− {previousEvent === null ? (
130− <div className={"w-1/2 mr-4"} />
131− ) : (
132− <div className="mr-4 w-1/2">
133− <h3 className="mb-2">Next Event</h3>
134− <EventPreview event={previousEvent} />
135− </div>
136− )}
137− {nextEvent === null ? (
138− <div className={"w-1/2 ml-4"} />
139− ) : (
140− <div className="mr-4 w-1/2">
141− <h3 className="mb-2">Previous Event</h3>
142− <EventPreview event={nextEvent} />
143− </div>
144− )}
133+ <TwoUp>
134+ {previousEvent ? (
135+ <div>
136+ <h3 className="mb-2">Next Event</h3>
137+ <EventPreview event={previousEvent} />
138+ </div>
139+ ) : null}
140+
141+ {nextEvent ? (
142+ <div>
143+ <h3 className="mb-2">Previous Event</h3>
144+ <EventPreview event={nextEvent} />
145+ </div>
146+ ) : null}
147+ </TwoUp>
145148 </Section>
146149 </SingleColumn>
147150 <Footer />
@@ -151,68 +154,12 @@ export default function Event({
151154
152155//
153156export async function getStaticProps({ params }) {
154− const nextEvent =
155− getNextPost(
156− params.slug,
157− [
158− "title",
159− "slug",
160− "ends",
161− "location",
162− "image",
163− "registration_url",
164− "youtube",
165− "description",
166− "starts",
167− "hosts",
168− "guests",
169− "dark",
170− "timezone",
171− ],
172− "events"
173− ) || null;
157+ const nextEvent = getNextPost(params.slug, eventKeys, "events") || null;
174158
175159 const previousEvent =
176− getPreviousPost(
177− params.slug,
178− [
179− "title",
180− "slug",
181− "ends",
182− "location",
183− "image",
184− "registration_url",
185− "youtube",
186− "description",
187− "starts",
188− "hosts",
189− "guests",
190− "dark",
191− "timezone",
192− ],
193− "events"
194− ) || null;
160+ getPreviousPost(params.slug, eventKeys, "events") || null;
195161
196− const event = getPostBySlug(
197− params.slug,
198− [
199− "title",
200− "ends",
201− "location",
202− "image",
203− "registration_url",
204− "youtube",
205− "description",
206− "starts",
207− "hosts",
208− "slug",
209− "guests",
210− "content",
211− "dark",
212− "timezone",
213− ],
214− "events"
215− );
162+ const event = getPostBySlug(params.slug, eventKeys, "events");
216163
217164 const markdown = await Markdown({ post: event });
218165
pages/grants.js+10−3
@@ -106,12 +106,19 @@ export default function Grants({
106106 activeTags.includes(category)
107107 );
108108
109+ const notCanceled = !post.extra.canceled;
110+
109111 const noTagsSelected = activeTags.length === 0;
110112 const hasType = post.taxonomies.grant_type.some((type) =>
111113 activeTypes.includes(type)
112114 );
113115
114− return (hasCategory || noTagsSelected) && byStatus(post) && hasType;
116+ return (
117+ (hasCategory || noTagsSelected) &&
118+ byStatus(post) &&
119+ hasType &&
120+ notCanceled
121+ );
115122 });
116123
117124 const allCount = postsByStatus.length;
@@ -262,13 +269,13 @@ export default function Grants({
262269 post={giftPosts[0]}
263270 className={`w-full md:w-1/2 pr-0 pb-8 md:pr-4`}
264271 key={giftPosts[0].slug}
265− section={giftPosts[0].section}
272+ section="updates"
266273 />
267274 <PostPreview
268275 post={giftPosts[1]}
269276 className={`w-full md:w-1/2 pl-0 pb-8 md:pl-4`}
270277 key={giftPosts[1].slug}
271− section={giftPosts[1].section}
278+ section="blog"
272279 />
273280 </div>
274281 </div>
pages/grants/[slug].js+2−1
@@ -17,6 +17,7 @@ import Footer from "../../components/Footer";
1717import SingleColumn from "../../components/SingleColumn";
1818import GrantPreview from "../../components/GrantPreview";
1919import Section from "../../components/Section";
20+import { DateTime } from "luxon";
2021
2122export default function Grant({ post, markdown, search, similarGrants }) {
2223 const router = useRouter();
@@ -47,7 +48,7 @@ export default function Grant({ post, markdown, search, similarGrants }) {
4748 </div>
4849 ) : null}
4950 <div className="type-ui text-wall-500 mt-4 md:mt-8 lg:mt-10">
50− {formatDate(new Date(post.date))}
51+ {formatDate(DateTime.fromISO(post.date))}
5152 </div>
5253 <div className="flex items-center flex-wrap mt-4 md:mt-8 lg:mt-10">
5354 {post.taxonomies.grant_type.map((category) => {
pages/index.js+66−37
@@ -1,29 +1,58 @@
11import Head from "next/head";
22import Link from "next/link";
33import React, { useState, useEffect } from "react";
4+
45import Container from "../components/Container";
56import Section from "../components/Section";
67import SingleColumn from "../components/SingleColumn";
78import Header from "../components/Header";
89import Footer from "../components/Footer";
910import BackgroundImage from "../components/BackgroundImage";
10−import TabCarousel from "../components/TabCarousel";
1111import Contact from "../components/Contact";
1212import PostPreview from "../components/PostPreview";
1313import EventPreview from "../components/EventPreview";
14+import Cross from "../components/icons/Cross";
15+import TwoUp from "../components/TwoUp";
1416import {
1517 getAllPosts,
1618 getAllEvents,
1719 formatDate,
1820 getOpenGrantsCount,
1921} from "../lib/lib";
20−import { contact } from "../lib/constants";
22+import { contact, eventKeys } from "../lib/constants";
23+import { useLocalStorage } from "../lib/hooks";
24+
25+const Banner = ({ children, isOpen, href, dismiss }) => {
26+ return (
27+ <div className="w-full flex justify-center bg-green-100">
28+ <SingleColumn>
29+ <div className="w-full layout">
30+ <div className="w-full flex justify-between items-center px-4 md:px-8 py-4">
31+ <a href={href} target="_blank">
32+ {children}
33+ </a>
34+ <button
35+ className="type-ui w-6 h-6 bg-green-400 flex items-center justify-center rounded-full text-white hover:opacity-70"
36+ onClick={(e) => {
37+ e.stopPropagation();
38+ dismiss();
39+ }}
40+ >
41+ <Cross width="10" height="10" fill="#E5F7F1" />
42+ </button>
43+ </div>
44+ </div>
45+ </SingleColumn>
46+ </div>
47+ );
48+};
2149
2250export default function Home({ posts, events, openGrantsCount, search }) {
23− const [tab, setTab] = useState(0);
2451 const [heroButton, setHeroButton] = useState(<div />);
52+ const [bannerElement, setBannerElement] = useState(null);
53+ const [isBannerOpen, setBanner] = useLocalStorage("isBannerOpen", true);
2554
26− const detectOS = () => {
55+ const selectDownloadButton = () => {
2756 const agent = window.navigator.appVersion;
2857 if (agent.includes("Win")) {
2958 return (
@@ -49,17 +78,37 @@ export default function Home({ posts, events, openGrantsCount, search }) {
4978 };
5079
5180 useEffect(() => {
52− setHeroButton(detectOS());
81+ setHeroButton(selectDownloadButton());
5382 }, []);
5483
84+ // Use this pattern when depending on client-side state or conditions like localStorage, user OS or geolocation.
85+ const selectBanner = (isBannerOpen) => {
86+ if (isBannerOpen) {
87+ return (
88+ <Banner
89+ href="http://assembly.urbit.org/"
90+ dismiss={() => setBanner(false)}
91+ >
92+ <p className="text-green-400 font-semibold hover:opacity-70">{`-> Join us October 15-17 for Assembly`}</p>
93+ </Banner>
94+ );
95+ } else {
96+ return null;
97+ }
98+ };
99+
100+ useEffect(() => {
101+ setBannerElement(selectBanner(isBannerOpen));
102+ }, [isBannerOpen]);
103+
55104 return (
56105 <Container>
57106 <Head>
58107 <title>urbit.org</title>
59108 </Head>
109+ {bannerElement}
60110 <SingleColumn>
61111 <Header search={search} />
62−
63112 {
64113 // Hero Statement
65114 }
@@ -197,14 +246,11 @@ export default function Home({ posts, events, openGrantsCount, search }) {
197246 <h2>Blog</h2>
198247 </div>
199248
200− <div className="flex flex-wrap">
201− <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
202− <PostPreview post={posts[0]} key={posts[0].slug} />
203− </div>
204− <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
205− <PostPreview post={posts[1]} key={posts[1].slug} />
206− </div>
207− </div>
249+ <TwoUp>
250+ <PostPreview post={posts[0]} key={posts[0].slug} />
251+ <PostPreview post={posts[1]} key={posts[1].slug} />
252+ </TwoUp>
253+
208254 <Link href="/blog">
209255 <button className="button-lg type-ui text-white bg-green-400">
210256 See More
@@ -220,14 +266,11 @@ export default function Home({ posts, events, openGrantsCount, search }) {
220266 <h2>Events</h2>
221267 </div>
222268
223− <div className="flex flex-wrap">
224− <div className="w-full md:w-1/2 pr-0 pb-8 md:pr-4">
225− <EventPreview event={events[0]} key={events[0].slug} />
226− </div>
227− <div className="w-full md:w-1/2 pl-0 pb-8 md:pl-4">
228− <EventPreview event={events[1]} key={events[1].slug} />
229− </div>
230− </div>
269+ <TwoUp>
270+ <EventPreview event={events[0]} key={events[0].slug} />
271+ <EventPreview event={events[1]} key={events[1].slug} />
272+ </TwoUp>
273+
231274 <Link href="/events">
232275 <button className="button-lg type-ui text-white bg-wall-600">
233276 More Events
@@ -256,21 +299,7 @@ export async function getStaticProps() {
256299 "blog"
257300 );
258301
259− const events = getAllEvents(
260− [
261− "title",
262− "slug",
263− "starts",
264− "guests",
265− "hosts",
266− "registration_url",
267− "ends",
268− "timezone",
269− "youtube",
270− "image",
271− ],
272− "events"
273− );
302+ const events = getAllEvents(eventKeys, "events");
274303
275304 return {
276305 props: { posts, events, openGrantsCount },
styles/globals.css+22−25
@@ -1,5 +1,4 @@
1−@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap");
2−@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500;600;700&display=swap");
1+@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Source+Code+Pro:wght@400;500;600;700;800&display=swap");
32
43:root {
54 --tray-time: 0ms;
@@ -9,69 +8,67 @@ html,
98body {
109 padding: 0;
1110 margin: 0;
11+ /* Make all text wall-600 by default */
1212 @apply text-wall-600;
1313}
1414
15+/* Reset a tags */
1516a {
1617 color: inherit;
1718 text-decoration: none;
1819 cursor: pointer;
1920}
2021
21−b,
22−strong {
23− @apply font-semibold;
24−}
25−
22+/* Links in p tags should be green */
2623p a {
2724 @apply text-green-400 font-semibold;
2825}
2926
30−button {
31− border: none;
27+/* Reduce b and strong tag weight default */
28+b,
29+strong {
30+ @apply font-semibold;
3231}
3332
3433/* Typography */
3534
3635h1,
3736.type-h1 {
38− @apply text-3xl md:text-4xl lg:text-5xl font-bold leading-tight text-wall-600;
37+ @apply text-4xl lg:text-5xl font-extrabold leading-tight text-wall-600;
3938}
4039
4140h2,
4241.type-h2 {
43− @apply text-2xl md:text-3xl lg:text-4xl font-bold leading-tight;
42+ @apply text-3xl lg:text-4xl font-bold leading-tight;
4443}
4544
4645h3,
4746.type-h3 {
48− @apply text-lg md:text-xl lg:text-2xl font-semibold;
47+ @apply text-xl font-semibold;
4948}
5049
5150h4,
5251.type-ui {
53− @apply text-sm md:text-base lg:text-lg font-semibold;
52+ @apply text-lg font-semibold;
5453}
5554
56−.type-ui-small {
57− @apply text-xs lg:text-base font-medium;
55+h5,
56+.type-h5,
57+.type-bold {
58+ @apply text-base font-semibold leading-relaxed;
5859}
5960
6061p,
6162.type-p {
62− @apply text-sm md:text-base lg:text-base leading-relaxed;
63−}
64−
65−.type-bold {
66− @apply text-sm md:text-base lg:text-base font-semibold leading-relaxed;
63+ @apply text-base leading-relaxed;
6764}
6865
6966.type-sub {
70− @apply text-xs md:text-sm;
67+ @apply text-sm;
7168}
7269
7370.type-sub-bold {
74− @apply text-xs md:text-sm font-semibold;
71+ @apply text-sm font-semibold;
7572}
7673
7774code .type-code {
@@ -109,15 +106,15 @@ code .type-code {
109106}
110107
111108.button-lg {
112− @apply flex-center px-4 lg:px-6 py-1 rounded-xl h-10 md:h-14 type-ui;
109+ @apply flex-center px-6 py-1 rounded-xl h-14 type-ui;
113110}
114111
115112.badge-sm {
116− @apply flex-center type-ui-small px-4 lg:px-4 h-8 md:h-8 rounded-full;
113+ @apply flex-center text-base font-medium px-3 h-8 rounded-full;
117114}
118115
119116.badge-lg {
120− @apply flex-center type-ui px-4 lg:px-8 h-10 md:h-12 rounded-full;
117+ @apply flex-center type-ui px-5 h-12 rounded-full;
121118}
122119
123120.button-sm:hover,
styles/markdown.css+2−2
@@ -36,11 +36,11 @@
3636}
3737
3838.markdown p {
39− @apply text-sm md:text-lg leading-relaxed;
39+ @apply text-base md:text-lg leading-relaxed;
4040}
4141
4242.markdown li {
43− @apply py-2 ml-6 text-lg;
43+ @apply py-2 ml-6 text-base md:text-lg;
4444}
4545
4646.markdown hr {