on master help sign in
Merge branch 'master' into multiple-grantees-2
commit2569b30773667d057b2b8f2a55825f0c207cb7f9 authorMatilde Park <matilde.park@urbit.org> dateWed 17 Aug 2022, 13:19 (4 years ago) parentc11848a55874c8
51 files changed+312−122
components/EventPreview.js+36−36 content/applications/~midsum-salrux/faux.md+11 content/applications/~paldev/face.md+1−1 content/applications/~paldev/fafa.md+1−1 content/applications/~paldev/pals.md+3−1 content/applications/~paldev/picture.md+1−1 content/applications/~paldev/rumors.md+3−1 content/applications/~paldev/verse.md+1−1 content/blog/the-shape-of-dao-governance-to-come.md+103 content/events/2022-09-22-assembly.md+15 content/grants/app-openbsd.md+1−1 content/grants/app-uv-native-notificatons.md+1−1 content/grants/apprenticeship-studio-collections.md+1−1 content/grants/apprenticeship-studio-email.md+1−1 content/grants/authenticate-with-urbit.md+1−1 content/grants/azimuth-cli.md+1−1 content/grants/clojure-urbit-client.md+1−1 content/grants/cue.md+1−1 content/grants/cypherscope-bot.md+1−1 content/grants/digital-editor-MROB.md+1−1 content/grants/digital-storefront.md+1−1 content/grants/dusko-valve.md+1−1 content/grants/eth-provider.md+1−1 content/grants/feed-aggregator.md+1−1 content/grants/filesharer.md+1−1 content/grants/general-web-permissions.md+1−1 content/grants/go-urbit.md+1−1 content/grants/hoon-markdown-library.md+1−1 content/grants/machine-learning.md+1−1 content/grants/meetup-bom.md+1−1 content/grants/native-planet-2.md+1−1 content/grants/nested-goals.md+1−1 content/grants/notifications-protocol.md+1−1 content/grants/planet-sales.md+1−1 content/grants/pokur.md+1−1 content/grants/port-maintenance.md+1−1 content/grants/regex-library.md+1−1 content/grants/seguro-prototype.md+1−1 content/grants/send-emails-to-a-mailing-list.md+1−1 content/grants/slam.md+1−1 content/grants/slaw-scot-jets.md+1−1 content/grants/speech-recognition.md+1−1 content/grants/static-site-generation.md+1−1 content/grants/symmetric-routing.md+1−1 content/grants/trill-ufa.md+1−1 content/grants/urbit-notes.md+1−1 content/grants/urbitswap.md+1−1 content/grants/uturn.md+1−1 content/grants/uv-extension-library.md+1−1 lib/lib.js+9 pages/events.js+89−41
components/EventPreview.js+36−36
@@ -20,18 +20,16 @@ export default function EventPreview({ event, className, big }) {
2020 generateRealtimeDate(event.starts) > DateTime.now() && !inFuture;
2121
2222 return (
23− <div
24− className={`cursor-pointer aspect-w-4 aspect-h-5 md:aspect-w-5 md:aspect-h-4 ${className}`}
25− >
23+ <div className={`cursor-pointer h-full ${className}`}>
2624 <div
2725 key={event.slug}
28− className={`bg-wall-100 rounded-xl bg-cover bg-center bg-no-repeat `}
26+ className={`bg-wall-100 min-h-[320px] h-full flex flex-col rounded-xl bg-cover bg-center bg-no-repeat `}
2927 style={{ backgroundImage: `url(${event.image})` || "" }}
3028 >
3129 <Link href={`/events/${event.slug}`}>
3230 <div
3331 className={classNames(
34− "flex flex-col p-6 justify-between items-between h-full relative",
32+ "flex flex-col p-6 min-h-[320px] justify-between items-between h-full relative",
3533 {
3634 "backdrop-brightness-50 rounded-xl": Boolean(
3735 event?.darken_image
@@ -50,38 +48,40 @@ export default function EventPreview({ event, className, big }) {
5048 </p>
5149 </div>
5250
53− <div className="absolute p-6 left-0 bottom-0 w-full pr-32">
54− <p className={`${blackText} type-sub mb-1`}>{event.location}</p>
55− <DateRange
56− starts={starts}
57− ends={ends}
58− className={`${grayText} type-sub`}
59− />
60− </div>
61−
62− {inFuture && event.registration_url ? (
63− <div className="absolute right-0 bottom-0 p-6">
64− <a
65− className="button-sm bg-green-400 text-white"
66− href={event.registration_url}
67− onClick={(e) => e.stopPropagation()}
68− target="_blank"
69− >
70− RSVP
71− </a>
72− </div>
73− ) : event.youtube ? (
74− <div className="absolute right-0 bottom-0 p-6">
75− <a
76− className="button-sm bg-wall-600 text-white"
77− href={`https://www.youtube.com/watch?v=${event.youtube}`}
78− onClick={(e) => e.stopPropagation()}
79− target="_blank"
80− >
81− ▶ Watch
82− </a>
51+ <div className="flex justify-between items-end">
52+ <div className="">
53+ <p className={`${blackText} type-sub mb-1`}>{event.location}</p>
54+ <DateRange
55+ starts={starts}
56+ ends={ends}
57+ className={`${grayText} type-sub`}
58+ />
8359 </div>
84− ) : null}
60+
61+ {inFuture && event.registration_url ? (
62+ <div>
63+ <a
64+ className="button-sm bg-green-400 text-white"
65+ href={event.registration_url}
66+ onClick={(e) => e.stopPropagation()}
67+ target="_blank"
68+ >
69+ RSVP
70+ </a>
71+ </div>
72+ ) : event.youtube ? (
73+ <div className="shrink-0">
74+ <a
75+ className="button-sm bg-wall-600 text-white shrink-0"
76+ href={`https://www.youtube.com/watch?v=${event.youtube}`}
77+ onClick={(e) => e.stopPropagation()}
78+ target="_blank"
79+ >
80+ ▶ Watch
81+ </a>
82+ </div>
83+ ) : null}
84+ </div>
8585 </div>
8686 </Link>
8787 </div>
content/applications/~midsum-salrux/faux.mdnew file+11
@@ -0,0 +1,11 @@
1++++
2+title = "Faux"
3+developer = "~midsum-salrux"
4+shortcode = "~tindys-botter-dozzod-tomdys/faux"
5+description = "An Urbit-native Discord bridge"
6+license = "MIT"
7+website = "https://github.com/midsum-salrux/faux"
8+bgColor = "#000000"
9++++
10+
11+Faux, a bridge between discord and urbit groups. When a member posts a new message in your discord group, a bot reposts it in your urbit group, and vice versa. This is implemented as a native gall agent and includes a frontend app to configure the connections between your discord and urbit servers.
content/applications/~paldev/face.md+1−1
@@ -8,6 +8,6 @@ website = "https://pal.dev/suite/face"
88bgColor = "#3b5998"
99+++
1010
11−see your friends
11+Face is an image rolodex of your urbit friends. Compatible with [pals](/applications/~paldev/pals), face lets you share a photo of yourself with mutuals and also see a gallery of their faces. To share, enter an image link in the url field.
1212
1313![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/face.png)
content/applications/~paldev/fafa.md+1−1
@@ -9,6 +9,6 @@ bgColor = "#1a1a2a"
99+++
1010
1111
12−2fa otp authenticator app
12+Fafa is a two-factor authentication app. Log in to your saved accounts with a one-time passcode generated on your urbit. (Only access this app over a secure connection and remember to also save your backup codes somewhere secure.)
1313
1414![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/fafa.png)
content/applications/~paldev/pals.md+3−1
@@ -8,6 +8,8 @@ website = "https://pal.dev/suite/pals"
88bgColor = "#99D3BD"
99+++
1010
11−friendlist for peer discovery
11+Pals is a contact list for your urbit friends. Send and receive friend requests with intuitive ease. Once you're pals, a smiley face icon indicates mutual status.
1212
1313![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/pals.png)
14+
15+Keep track of who your pals are or how you know them with tags, which exist mostly for your own book-keeping, but could be used in the future by other applications to group content. Similarly, some apps work better with pals, like [rumors](/applications/~paldev/rumors), an anonymous gossip app, and [sphinx](/applications/~nocsyx-lassul/sphinx), an Urbit search engine.
content/applications/~paldev/picture.md+1−1
@@ -8,6 +8,6 @@ website = "https://pal.dev/suite/picture"
88bgColor = "#DDDDDD"
99+++
1010
11−a picture frame for the garden
11+Picture is a digital frame for your urbit. Simply upload an image to see it every time you open up Landscape. (Refrain from uploading huge images to avoid memory pressure.)
1212
1313![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/picture.png)
content/applications/~paldev/rumors.md+3−1
@@ -8,6 +8,8 @@ website = "https://pal.dev/suite/rumors"
88bgColor = "#bb77dd"
99+++
1010
11−anonymous gossip from friends of friends
11+Rumors is an anonymous gossip feed powered by [pals](/applications/~paldev/pals). The more mutuals you have, the more rumors you'll see: messages posted by your pals, and their pals, populate the feed.
1212
1313![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/rumors.png)
14+
15+Built using a p2p gossip protocol, rumors anonymizes posts and orders them chronologically. Drop links, ask questions, lurk, or whisper secrets into the void …
content/applications/~paldev/verse.md+1−1
@@ -8,6 +8,6 @@ website = "https://pal.dev/suite/verse"
88bgColor = "#EEEEDD"
99+++
1010
11−a random Bible verse, every day
11+Verse displays a random Bible verse. Whenever you open Landscape, a new daily passage awaits.
1212
1313![Screenshot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/verse.png)
content/blog/the-shape-of-dao-governance-to-come.mdnew file+103
@@ -0,0 +1,103 @@
1++++
2+title = "The Shape of DAO Governance to Come"
3+date = "2022-08-17"
4+description = "When building the Combine DAO, we conducted a survey of DAO governance and tooling and came to the conclusion that the many theoretical approaches to the problem of governance were tied to the implementation details of the DAO stack. Since we were building everything on Urbit—as opposed to through the typical combination of Solidity contracts, Web2 tools and Snapshot—we realized that we’d have to do some rethinking. A new approach for a new stack."
5+
6+[extra]
7+author = "Anthony Arroyo"
8+ship = "~poldec-tonteg"
9+image = "https://storage.googleapis.com/media.urbit.org/blog/2022.7.29..17.14.22-image.jpg"
10++++
11+
12+![Image](https://storage.googleapis.com/media.urbit.org/blog/2022.7.29..17.14.22-image.jpg)
13+
14+On Urbit, the risk of a governance attack is reduced because the Combine DAO’s founding team was selected based on their on-Urbit track records. All participants had skin in the game. This allowed us to focus on workaday consensus building, which forms the bulk of any organization’s activities. We use Holium’s Ballot app, which integrates with all aspects of Urbit OS and gives complete flexibility for automating the execution of a winning proposal via [Custom Actions](https://docs.holium.com/ballot/custom-actions/intro).
15+
16+Let’s walk through the thought process that led us to adopt an Urbit-centric approach to DAO governance.
17+
18+## Reputation’s Effect on DAO Governance
19+
20+In our last blog, we discussed how Urbit’s pseudonymous reputation model frees DAOs from the constraints of “trustless” systems. Urbit IDs also mitigate the inflexibility that plagues DAO governance. Here’s what we mean.
21+
22+One of the primary objectives of DAO governance systems is to prevent governance attacks. A governance attack occurs when one or more actors changes the internal processes to benefit themselves, for example transfering the entire treasury to himself.
23+
24+The risk of governance attacks means DAO governance is either 1) very rigid or 2) vulnerable. The vast majority of DAOs, Moloch for example, opt for the former. Thus the rules around upgrading smart contracts are constrained. Once systems are in place, they can’t be changed. On-chain activity is limited. Voting procedures are either distilled or taken off-chain (and usually onto Web2.) This leaves DAOs inflexible.
25+
26+Here’s an example of said inflexibility gone wrong. In March of this year, the DAO that runs the Ethereum Name Service [held a vote](https://www.coindesk.com/layer2/2022/03/07/ens-and-the-limitations-of-dao-governance/) to oust its director Brantly Millegan. Brantly had recently been canceled for 2016 tweets dug up by an anonymous DAO member, fired from his role as director of operations at ENS’ non-DAO corporate parent True Names Limited. ENS iself, however, was governed by a typical 1 share: 1 vote system (plus delegates). Unsurprisingly, Millegan owned or controlled a large block of votes, and was able to overcome the ouster vote 1.6 million tokens to 1.4 million, with holders of roughly one third of the tokens abstaining or not participating.
27+
28+Now, this is indeed a DAO working as it should, a democratic decision unswayed by the influence of insiders or elites. At the same time, the natural functioning of the DAO was derailed by mass distraction. Instead of focusing on ENS, for nearly a year the DAO’s function was to address a controversy raised by an anonymous member who, if we’re being honest, most likely intended to disrupt its active functioning from the start.
29+
30+We arrive back again at the inflexibility inherent to anonymity—if just anyone can buy a token, bad actors will buy them. Yet currently most DAOs intentionally eschew reputation. The incentives for good behaviors are thus only economic.
31+
32+[Pseudonymous reputation](https://urbit.org/blog/pseudonymous-reputation) changes this calculus, adding incentives beyond money for both security and participation. This means when built on Urbit, DAOs are less susceptible to governance attacks, and more flexible. Having a baseline of trust allows DAOs to experiment with different governance tools and structures.
33+
34+In order to understand how DAOs are more free to experiment on Urbit and what they can experiment with, it’s first important to understand the history of DAO governance, why it developed the way they did, and where it’s at currently.
35+
36+
37+## A Little DAO Governance History Lesson
38+
39+The first DAO, The DAO, was also the most disastrous. A recursive calls flaw in the governance code led to the only hack-related hard fork in Ethereum History.
40+
41+Recursive call weaknesses in DAO code are somewhat like endlessly automated micro-pyramid schemes. An attacker can call a transaction, mark its benefit, then initiate another transaction before the original transaction is completed, over and over until the block limit is reached. Bernie Madoff paid off old investments with new investments while marking fake gains, took out fees, then reinvested before the prior investments could be properly marked. Both schemes work when automated governance fails.
42+
43+While The DAO’s shoddy governance was its downfall, it was Ethereum’s good governance that saved ETH. Ethereum leadership proposed a hard fork and 97% of holders voted in favor. The transaction was undone. Money returned. Ethereum Classic was born.
44+
45+While The DAO failed, its happy ending paved the way for the DAO boom of today. DAO creators learned the security lesson: there hasn’t been a major DAO hack since 2016. They built better governance—a combination of visionary leadership, trustless voting, and vigorous participation—that steered the ETH ship to calmer waters.
46+
47+
48+## Birth of the Ragequit
49+
50+Moloch DAO was born in the shadow of The DAO fiasco; its governance was expressly designed in response to it. Unlike The DAO, Moloch is a charity; it exists only to give money away to Ethereum infrastructure projects. Its on-chain governance is extremely_ _simple. Members can do only one of two things.
51+
52+The first thing is submitting and voting on proposals for new members. Votes are majority rule with one vote per Moloch share. Only an existing member can propose a new member, so prospective members must convince an existing one to nominate them.
53+
54+New member proposals double as funding proposals, a clever wrinkle which demonstrates just how much can be done on-chain with a single function. Every proposal must include a fundable ETH infrastructure project and a fundee to execute it. If approved, Moloch accepts the fundee as a new member and grants them a number of Moloch shares exchangeable 1:1 for ETH.
55+
56+![Moloch DAO](https://storage.googleapis.com/media.urbit.org/blog/moloch-dao.png)
57+
58+How the fundee exchanges the shares for ETH brings us to the second thing Moloch members can do: ragequitting.
59+
60+Ragequitting is a smart contract function that allows members to rescind their shares—and thus their membership—during a seven day waiting period between approval of new members and disbursement of their funds. Its primary purpose is to allow members to opt out of the DAO if they don’t like a new member or proposal. If they ragequit, they automatically receive ETH equal to their shares.
61+
62+Ragequitting thus provides a major safeguard against the kinds of attack that took down The DAO. Members can simply ragequit en masse to invalidate any harmful action, thereby looting their own DAO and stopping bad actors in their tracks. Ragequitting is Moloch’s “ultimate safety net for malicious attacks.”
63+
64+Like member voting, ragequitting also has a dual purpose. In order to receive their funding, new fundees must ragequit, discharging their shares, receiving ETH, and thereby immediately becoming non-members. Thus, the only time someone becomes a permanent member is if they are granted more shares than they ragequit, which usually happens for non-fundee members, i.e. philanthropic members, not fundees.
65+
66+This is all to say that Moloch, a very successful DAO, has reduced its governance to the simplest imaginable framework. Members can take one of two actions—that’s it. While it makes for a functional secure on-chain charity, the ragequit process isn’t exactly dynamic governance. It’s too low-res for the vast majority of organizations.
67+
68+## Ouroboros
69+
70+In a way, ragequitting uses pyramid scheme logic to its advantage. The only thing members can do is add other members or quit; and it’s through these cyclical actions that the DAO gains members, disburses funds, and maintains security. But it also opens up new problems. For example, what happens if a disgruntled member intentionally votes yes on a harmful proposal, only to ragequit their shares just after the proposal passes? You might call it a murder suicide problem.
71+
72+DAOs supposedly solve the Principal Agent Dilemma; an ancient problem faced by all resource-distributing organizations where an agent’s self-interest inevitably runs counter to principles’ assets. The most common example is a non-shareholding manager of a company (agent) versus its shareholders (principal). The manager is in charge of spending shareholders’ money, but doesn’t suffer the financial consequences from her decisions. Theoretically, DAO principles and DAO agents are indistinguishable—everyone is both investor and employee—which solves the dilemma.
73+
74+But anonymous shareholders often act badly, or they don’t act at all. Even the [DAOs with the highest participation](https://www.businessofbusiness.com/articles/ranking-daos-we-computed-their-net-community-score-to-see-how-they-stack-up/) top out around 75%, and that’s rare. A DAO with low voter participation can become either paralyzed, if a quorum is required, or oligarchical if one isn’t. On-chain voting requires high gas fees and time spend, prohibitively high when it comes to the many technical decisions that need to be made. This is why every successful DAO has naturally developed sets of working groups who make decisions off-chain with the blessing of the body politic. The vast majority of these “governance forums” use a patchwork of Web2 apps.
75+
76+For the bigger decisions, avoiding high gas fees and low participation can mean voting with literal Discord emojis, then having small groups of trusted “delegates” enter on-chain decisions via multi-sigs. This is both sloppy and defeats the purpose of the DAO.
77+
78+More serious solutions make up the primary class L2 products. “Dynamic governance” adds granularity to voting systems, allowing for more complex on-chain voting with different rights for different member classes. “Holographic consensus” gamifies participation by appending a prediction market to every vote. Some are even trying “[Meta Governance](https://indexcoop.com/blog/early-2022-survey-of-metagovernance)”—DAOs governing DAOs. L2 decisions are even further analyzed by deep risk analyzers like Gauntlet that stress test decisions via simulated protocols before they’re executed on-chain.
79+
80+The simple solution is conducting votes using decentralized apps that verify shareholder status. Snapshot is an IPFS voting app that holds votes off-chain, weighing and verifying member votes by reading members’ wallets. It’s a quick and secure way to stage off-chain votes, but it’s still pretty limited. Snapshot is called that because all it really does is take a picture of the result of a vote. All the activity resulting from said vote must still be manually undertaken by DAO operators.
81+
82+Traditional DAOs suffer from what we like to call the Self-Enforcement Problem. Smart contracts enforce themselves, a stark difference from traditional corporations where bylaws and operating rules are met by employees using the best tools for the job. With DAOs, the bylaws and the tools that enforce them are one and the same. This adds up to yet another layer on inflexibility: traditional orgs can adopt new tools for old problems, DAOs cannot.
83+
84+
85+## The Right Tool for the Job
86+
87+This all brings us finally back to Urbit. In the Urbit OS using Urbit IDs, DAOs can integrate every governance solution discussed above without touching Web2. Tailor DAO governance to your own precise needs. Download someone else’s voting app, or spin up your own. Using Urbit blurs the boundaries between on-chain and off-chain actions, eliminating the need for ad hoc fixes then fixes to fix those fixes. It all occurs in one landscape, and can pivot with new information
88+
89+One Urbit app is called Ballot. Ballot most closely resembles Snapshot. It’s like a decentralized version of Snapshot that works without the need for Discord—everything is on the Urbit OS—that can also be modified to execute custom off-chain actions after a vote is held.
90+
91+![Ballot](https://storage.googleapis.com/media.urbit.org/site/ecosystem/applications/ballot.png)
92+
93+Ballot allows for this sort of dynamic governance enhancement, or what we call Custom Actions. In a way, Custom Actions make the dream of smart contracts a reality: instead of being constrained to ragequitting or not ragequitting, you can create automated granular responses to Ballot votes. In other words, smart contracts that are actually smart. One action could be “tweet something” or “ping someone” once a vote is recorded in Ballot. You can write any if-then you want on the outcomes of a vote. For example, instead of relegating membership to simplistic on-chain democracy, a la ragequitting, Ballot has a function for inviting or kicking members via Urbit native logic. Imagine if Snapshot could both conduct a Moloch-style membership vote, and then automatically add or kick the member depending on the result. That’s Ballot.
94+
95+What about the small technical decisions, like the ones that took down The DAO, like booting someone from a chat or paying a contractor through treasury? All of those actions are programmatic, irreversible, and immutable on Urbit, eliminating concerns about gas fees and participation.
96+
97+If Ballot doesn’t suit your DAO’s needs, it’s easy to use Urbit’s primitives to write a custom voting application. Since Urbit has ring signature support built in, you can build a simple, anonymous voting app in just 15 minutes, following [this tutorial](https://developers.urbit.org/guides/quickstart/voting-guide). The combination of cryptographic identity and an append-only event log means that you are free to experiment with different tools to support whatever governance process your DAO adheres to.
98+
99+The Combine DAO uses Ballot for voting on proposals as it integrates cleanly with Urbit ID and provides flexibility for setting different quora for votes and Custom Actions to automate outcomes. There is thus no need to distinguish between the tool used for a “real vote” such as deploying Stars in exchange for tokens and the tool used for a “fun vote” like choosing a date for a meetup.This flexibility and interoperability is a hallmark to the burgeoning supply of [applications](https://urbit.org/ecosystem?type=applications) being built for Urbit.
100+
101+—
102+
103+*Anthony Arroyo is the Director of [The Combine DAO](https://blog.the-combine.org/introduction-to-the-combine-dao)*
content/events/2022-09-22-assembly.mdnew file+15
@@ -0,0 +1,15 @@
1++++
2+title = "Assembly"
3+starts = "2022-09-22T09:00:00"
4+ends = "2022-09-25T19:00:00"
5+timezone = "America/New_York"
6+location = "In Person Event"
7+image = ""
8+description = "Urbit Assembly 2022 is the second ever Urbit confluence coming to you this year from Miami Beach."
9+
10++++
11+
12+Last year we showcased the release of software distribution which enabled other developers to use Urbit as infrastructure for their creations. This year we’ll be showcasing an entire ecosystem of New World Energy.
13+
14+Visit assembly.urbit.org for information on speakers, scheduling, and registration.
15+
content/grants/app-openbsd.md+1−1
@@ -12,7 +12,7 @@ description = "Get the project to the point where it works on OpenBSD"
1212reward = "1 star"
1313mentor = "~silsyn-wathep"
1414assignee = ["~tidsut-radryd"]
15−grant_id = "A0124"
15+grant_id = "A0103"
1616completed = false
1717work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=A0124&prefill_Grant+Name=Resurrect%20OpenBSD%20Build"
1818+++
content/grants/app-uv-native-notificatons.md+1−1
@@ -13,7 +13,7 @@ description = "Build native notifications into Urbit Visor"
1313reward = "1 star"
1414assignee = ["~toclec-baldeg"]
1515mentor = "~haddef-sigwen"
16−grant_id = "A0145"
16+grant_id = "A0119"
1717completed = false
1818canceled = false
1919link = ""
content/grants/apprenticeship-studio-collections.md+1−1
@@ -12,7 +12,7 @@ description = "Extend ~tirrel's studio app content offering to include groups co
1212reward = "1 star"
1313mentor = "~novlud-padtyv"
1414assignee = ["~dachus-tiprel"]
15−grant_id = "A0174"
15+grant_id = "A0147"
1616completed = false
1717canceled = false
1818work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=A0174&prefill_Grant+Name=Studio%20Collections"
content/grants/apprenticeship-studio-email.md+1−1
@@ -12,7 +12,7 @@ description = "Build out email capabilities of ~tirrel's Studio app"
1212reward = "1 star"
1313mentor = "~novlud-padtyv"
1414assignee = ["~rivmud-fabwen"]
15−grant_id = "A0139"
15+grant_id = "A0114"
1616completed = true
1717work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=A0139&prefill_Grant+Name=~tirrel%20studio%20email%20capabilities"
1818+++
content/grants/authenticate-with-urbit.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Exernal service/websites can verify that a user is who they say they are"
1010reward = "1 star"
1111assignee = ["dcSpark"]
12−grant_id = "P0081"
12+grant_id = "P0065"
1313completed = true
1414canceled = false
1515link = ""
content/grants/azimuth-cli.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Bridge for the command line"
1010reward = "3 stars"
1111assignee = ["~lavlyn-litmeg"]
12−grant_id = "P0080"
12+grant_id = "P0064"
1313completed = true
1414canceled = false
1515link = ""
content/grants/clojure-urbit-client.md+1−1
@@ -10,7 +10,7 @@ description = "Urbit client for the Clojure programming language"
1010reward = "3 stars"
1111champion = "~littel-wolfur"
1212assignee = ["~sorted-falnyd"]
13−grant_id = "P0092"
13+grant_id = "P0072"
1414completed = false
1515canceled = false
1616link = ""
content/grants/cue.md+1−1
@@ -13,7 +13,7 @@ description = "A bookmarking tool that reminds you of content you saved for late
1313reward = "4 stars"
1414assignee = ["~mallus-fabres"]
1515champion = "~fabled-faster"
16−grant_id = "P0173"
16+grant_id = "P0146"
1717completed = false
1818canceled = false
1919link = ""
content/grants/cypherscope-bot.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "On-demand financial charts chat bot"
1010reward = "2 stars"
1111assignee = ["~mitten-dapper", "~ristyc-ridwyx"]
12−grant_id = "P0068"
12+grant_id = "P0054"
1313completed = false
1414canceled = false
1515link = ""
content/grants/digital-editor-MROB.md+1−1
@@ -12,7 +12,7 @@ description = "Digital Editor for the Mars Review of Books"
1212reward = "$4,500 in WSTR"
1313assignee = ["~randem-wishes"]
1414champion = "~librex-dozryc"
15−grant_id = "B0140"
15+grant_id = "B0115"
1616completed = false
1717work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0140&prefill_Grant+Name=Digital%20Editor%2C%20Mars%20Review%20of%20Books"
1818+++
content/grants/digital-storefront.md+1−1
@@ -13,7 +13,7 @@ description = "Online Store for Selling Digital Content (Text / Audio / Video)"
1313reward = "3 stars"
1414assignee = ["~tirrel"]
1515champion = "~tacryt-socryp"
16−grant_id = "P0167"
16+grant_id = "P0140"
1717completed = false
1818canceled = false
1919link = ""
content/grants/dusko-valve.md+1−1
@@ -12,7 +12,7 @@ description = "a tool which allows NFT artists to offer planets for redemption t
1212reward = "1.0 WSTR"
1313assignee = ["~tasset-norlux"]
1414champion = "~tacryt-socryp"
15−grant_id = "P0143"
15+grant_id = "P0118"
1616completed = true
1717canceled = false
1818link = ""
content/grants/eth-provider.md+1−1
@@ -11,7 +11,7 @@ image = ""
1111description = "Eth-provider is a Gall agent that allows Urbit users to configure their Ethereum connection."
1212reward = "3 stars"
1313assignee = ["~pontus-fadpun"]
14−grant_id = "B0142"
14+grant_id = "B0117"
1515completed = false
1616work_request_link="https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0142&prefill_Grant+Name=Ethereum%20Provider"
1717+++
content/grants/feed-aggregator.md+1−1
@@ -11,7 +11,7 @@ image = ""
1111description = "A feed aggregation application"
1212reward = "5 stars"
1313assignee = ["~mirtyl-wacdec"]
14−grant_id = "P0043"
14+grant_id = "P0030"
1515completed = false
1616canceled = true
1717link = ""
content/grants/filesharer.md+1−1
@@ -11,7 +11,7 @@ image = ""
1111description = "Simple file sharing on Urbit"
1212reward = "2 stars"
1313assignee = ["~tinden-pontev"]
14−grant_id = "P0123"
14+grant_id = "P0102"
1515completed = false
1616link = ""
1717+++
content/grants/general-web-permissions.md+1−1
@@ -13,7 +13,7 @@ description = "Enabling the ability to log in and view private or user-specific
1313reward = "3 stars"
1414assignee = ["~tirrel"]
1515champion = "~tacryt-socryp"
16−grant_id = "P0169"
16+grant_id = "P0142"
1717completed = false
1818canceled = false
1919link = ""
content/grants/go-urbit.md+1−1
@@ -13,7 +13,7 @@ description = "Create a go-urbit library that contains the required functions fo
1313reward = "3 stars"
1414assignee = ["~litryl-tadmev"]
1515champion = "~wicdev-wisryt"
16−grant_id = "P0125"
16+grant_id = "P0111"
1717completed = false
1818canceled = false
1919link = ""
content/grants/hoon-markdown-library.md+1−1
@@ -13,7 +13,7 @@ description = "CommonMark-based Library for Hoon Markdown"
1313reward = "3 stars"
1414assignee = ["~tirrel"]
1515champion = "~tacryt-socryp"
16−grant_id = "P0168"
16+grant_id = "P0141"
1717completed = false
1818canceled = false
1919link = ""
content/grants/machine-learning.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Create a supervised machine learning library"
1010reward = "7 stars"
1111assignee = ["~tamdev-nocdun"]
12−grant_id = "P0075"
12+grant_id = "P0059"
1313completed = false
1414canceled = false
1515link = ""
content/grants/meetup-bom.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Provide meetup bounty hunters with the persuasive narratives and content"
1010reward = "3 stars"
1111assignee = ["~sarlev-sarsen"]
12−grant_id = "P0093"
12+grant_id = "P0073"
1313completed = true
1414canceled = false
1515link = ""
content/grants/native-planet-2.md+1−1
@@ -12,7 +12,7 @@ description = "Native Planet - A domestic hosting solution for Urbit"
1212reward = "6 stars"
1313assignee = ["~dalhec-banler"]
1414champion = "~poldec-tonteg"
15−grant_id = "P0152"
15+grant_id = "P0126"
1616completed = false
1717canceled = false
1818+++
content/grants/nested-goals.md+1−1
@@ -10,7 +10,7 @@ description = "Urbit application for invidividual and social management of high-
1010reward = "3 stars"
1111champion = "~tacryt-socryp"
1212assignee = ["~niblyx-malnus"]
13−grant_id = "P0170"
13+grant_id = "P0143"
1414completed = false
1515canceled = false
1616link = ""
content/grants/notifications-protocol.md+1−1
@@ -12,7 +12,7 @@ description = "Build a new network protocol to Urbit to handle notifying subscri
1212reward = "1 star"
1313mentor = "~rovnys-ricfer"
1414assignee = ["~novned-tidsyl", "~boldur-dashes"]
15−grant_id = "A0163"
15+grant_id = "A0136"
1616completed = false
1717canceled = false
1818work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=A0163&prefill_Grant+Name=Notifications%20Protocol"
content/grants/planet-sales.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Layer 2 Planet Sales via Urbit"
1010reward = "6 stars"
1111assignee = ["~tacryt-socryp"]
12−grant_id = "P0071"
12+grant_id = "P0057"
1313completed = true
1414canceled = false
1515link = ""
content/grants/pokur.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Multiplayer Poker App on Urbit"
1010reward = "2 stars"
1111assignee = ["~hodzod-walrus"]
12−grant_id = "P0084"
12+grant_id = "P0068"
1313completed = true
1414canceled = false
1515link = ""
content/grants/port-maintenance.md+1−1
@@ -10,7 +10,7 @@ description = "Maintain the Port application"
1010reward = "2 stars"
1111champion = "~nocsyx-lassul"
1212assignee = ["~latter-bolden"]
13−grant_id = "B0137"
13+grant_id = "B0112"
1414completed = false
1515work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0137&prefill_Grant+Name=Port%20Maintenance"
1616+++
content/grants/regex-library.md+1−1
@@ -12,7 +12,7 @@ description = "Implement a regular expressions library in Hoon"
1212reward = "3 stars"
1313mentor = "~lagrev-nocfep"
1414assignee = ["~macrep-racdec"]
15−grant_id = "B0177"
15+grant_id = "B0149"
1616completed = false
1717canceled = false
1818work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0177&prefill_Grant+Name=Regular%20Expressions%20Library"
content/grants/seguro-prototype.md+1−1
@@ -13,7 +13,7 @@ description = ""
1313reward = "2 stars"
1414assignee = ["~mastyr-bottec"]
1515champion = "~master-morzod"
16−grant_id = "P0157"
16+grant_id = "P0130"
1717completed = false
1818canceled = false
1919link = ""
content/grants/send-emails-to-a-mailing-list.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "An agent that works with SendGrid, to be used for sending subscriber content to a mailing list."
1010reward = "4 stars"
1111assignee = ["~novlud-padtyv"]
12−grant_id = "P0082"
12+grant_id = "P0066"
1313completed = true
1414canceled = false
1515link = ""
content/grants/slam.md+1−1
@@ -10,7 +10,7 @@ description = "Slam Action Game"
1010reward = "3 stars"
1111assignee = ["~hanfel-dovned"]
1212champion = "~rabsef-bicrym"
13−grant_id = "P0122"
13+grant_id = "P0101"
1414completed = false
1515link = ""
1616+++
content/grants/slaw-scot-jets.md+1−1
@@ -12,7 +12,7 @@ description = "Tune slaw/scot jets for better perfomance."
1212reward = "1 star"
1313mentor = "~master-morzod"
1414assignee = [""]
15−grant_id = "B0164"
15+grant_id = "B0137"
1616completed = false
1717work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0164&prefill_Grant+Name=Tune%20slaw%2Fscot%20Jets"
1818+++
content/grants/speech-recognition.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Bring Automatic Speech Recognition to Urbit"
1010reward = "2 stars"
1111assignee = ["~hosted-fornet"]
12−grant_id = "P0053"
12+grant_id = "P0040"
1313completed = true
1414canceled = false
1515link = ""
content/grants/static-site-generation.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Publish Urbit notebooks to the clearweb"
1010reward = "5 stars"
1111assignee = ["~tacryt-socryp"]
12−grant_id = "P0072"
12+grant_id = "P0058"
1313completed = true
1414canceled = false
1515link = ""
content/grants/symmetric-routing.md+1−1
@@ -12,7 +12,7 @@ description = "Change Urbit's peer discovery, routing, and packet forwarding to
1212reward = "1 star"
1313mentor = "~rovnys-ricfer"
1414assignee = ["~novned-tidsyl", "~boldur-dashes"]
15−grant_id = "A0162"
15+grant_id = "A0135"
1616completed = false
1717canceled = false
1818work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=A0162&prefill_Grant+Name=Symmetric%20Routing"
content/grants/trill-ufa.md+1−1
@@ -13,7 +13,7 @@ description = "Urbit microblogging platform and social feed aggregator"
1313reward = "5 stars"
1414assignee = ["~mirtyl-wacdec"]
1515champion = "~poldec-tonteg"
16−grant_id = "P0153"
16+grant_id = "P0127"
1717completed = false
1818canceled = false
1919link = ""
content/grants/urbit-notes.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "Bring your note-taking game into the 31st century!"
1010reward = "2 stars"
1111assignee = ["~ribben-donnyl"]
12−grant_id = "P0070"
12+grant_id = "P0056"
1313completed = false
1414link = ""
1515+++
content/grants/urbitswap.md+1−1
@@ -11,7 +11,7 @@ image = ""
1111description = "Host a Uniswap frontend trading UI on your urbit"
1212reward = "1 star"
1313assignee = [""]
14−grant_id = "B0186"
14+grant_id = "B0155"
1515completed = false
1616work_request_link = "https://airtable.com/shr4qt9t9kz7RaOIa?prefill_Grant+ID=B0186&prefill_Grant+Name=Uniswap"
1717+++
content/grants/uturn.md+1−1
@@ -9,7 +9,7 @@ image = ""
99description = "TURN server support for WebRTC"
1010reward = "1 star"
1111assignee = ["~martyr-binbex"]
12−grant_id = "P0108"
12+grant_id = "P0088"
1313completed = false
1414link = ""
1515+++
content/grants/uv-extension-library.md+1−1
@@ -12,7 +12,7 @@ image = ""
1212description = "A typescript library which streamlines the experience of building Urbit Visor powered chrome extensions."
1313reward = "2 stars"
1414assignee = ["dcSpark"]
15−grant_id = "P0094"
15+grant_id = "P0074"
1616completed = true
1717canceled = false
1818link = ""
lib/lib.js+9
@@ -287,3 +287,12 @@ export function getOpenGrantsCount() {
287287 }, 0);
288288 return count;
289289}
290+
291+// Groups arrays into an array of arrays with 2 members
292+export const pair = (arr) =>
293+ arr.reduce(function (rows, key, index) {
294+ return (
295+ (index % 2 == 0 ? rows.push([key]) : rows[rows.length - 1].push(key)) &&
296+ rows
297+ );
298+ }, []);
pages/events.js+89−41
@@ -1,23 +1,24 @@
11import Head from "next/head";
2−import Link from "next/link";
2+import cn from "classnames";
33import { DateTime } from "luxon";
4−
4+import { useState } from "react";
55import Meta from "../components/Meta";
66import {
77 Container,
88 SingleColumn,
99 Section,
1010 IntraNav,
11+ TwoUp,
1112} from "@urbit/foundation-design-system";
1213import Footer from "../components/Footer";
1314import Header from "../components/Header";
1415import EventPreview from "../components/EventPreview";
15−import { contact } from "../lib/constants";
16−import { getAllEvents, generateRealtimeDate } from "../lib/lib";
16+import { getAllEvents, generateRealtimeDate, pair } from "../lib/lib";
1717
1818import { eventKeys } from "../lib/constants";
1919
2020export default function Events({ events, search }) {
21+ const [selected, setSelected] = useState("All");
2122 const post = {
2223 title: "Events",
2324 description: "In-person, remote, and recorded events about Urbit.",
@@ -25,32 +26,63 @@ export default function Events({ events, search }) {
2526
2627 const now = DateTime.now();
2728
28− const pastEvents = events.filter((event) => {
29− const ends = generateRealtimeDate(event.ends);
30− return ends < now;
31− });
29+ const selectionFilter = (event) => {
30+ if (selected === "All") {
31+ return true;
32+ } else if (selected === "In-person") {
33+ return event.location !== "Online Event";
34+ } else if (selected === "Online") {
35+ return event.location === "Online Event";
36+ }
37+ };
38+
39+ const pastEvents = pair(
40+ events.filter(selectionFilter).filter((event) => {
41+ const ends = generateRealtimeDate(event.ends);
42+ return ends < now;
43+ })
44+ );
3245
33− const futureEvents = events
34− .filter((event) => {
46+ const futureEvents = pair(
47+ events
48+ .filter(selectionFilter)
49+ .filter((event) => {
50+ const starts = generateRealtimeDate(event.starts);
51+ return starts > now;
52+ })
53+ .sort((a, b) => {
54+ const aStarts = generateRealtimeDate(a.starts).ts;
55+ const bStarts = generateRealtimeDate(b.starts).ts;
56+ if (aStarts > bStarts) {
57+ return 1;
58+ } else if (aStarts === bStarts) {
59+ return 0;
60+ }
61+ return -1;
62+ })
63+ );
64+
65+ const happeningNow = pair(
66+ events.filter(selectionFilter).filter((event) => {
3567 const starts = generateRealtimeDate(event.starts);
36− return starts > now;
68+ const ends = generateRealtimeDate(event.ends);
69+ return starts < DateTime.now() && ends > now;
3770 })
38− .sort((a, b) => {
39− const aStarts = generateRealtimeDate(a.starts).ts;
40− const bStarts = generateRealtimeDate(b.starts).ts;
41− if (aStarts > bStarts) {
42− return 1;
43− } else if (aStarts === bStarts) {
44− return 0;
45− }
46− return -1;
47− });
71+ );
4872
49− const happeningNow = events.filter((event) => {
50− const starts = generateRealtimeDate(event.starts);
51− const ends = generateRealtimeDate(event.ends);
52− return starts < DateTime.now() && ends > now;
53− });
73+ const SelectedButton = ({ selection, text }) => {
74+ return (
75+ <a
76+ className={cn("badge-lg w-fit cursor-pointer type-ui", {
77+ "bg-wall-600 text-white": selected === selection,
78+ "bg-wall-100 text-wall-500": selected !== selection,
79+ })}
80+ onClick={() => setSelected(selection)}
81+ >
82+ {text}
83+ </a>
84+ );
85+ };
5486
5587 return (
5688 <Container>
@@ -61,27 +93,31 @@ export default function Events({ events, search }) {
6193 <IntraNav ourSite="https://urbit.org" search={search} />
6294 <SingleColumn>
6395 <Header />
64− <Section narrow>
96+ <Section>
6597 <div className="measure">
6698 <h1 className="pb-16">Events</h1>
67− <p className="pb-6">
68− Events of all kinds: In-person, Remote, and Recorded.
69− </p>
70− <p>
71− <a href={contact.newsletter}>Subscribe</a> to the Urbit Newsletter
72− for regular updates, including new blog posts and events.
73− </p>
99+ <div className="flex space-x-2">
100+ <SelectedButton selection="All" text="All Events" />
101+ <SelectedButton selection="In-person" text="In-person" />
102+ <SelectedButton selection="Online" text="Online" />
103+ </div>
74104 </div>
75105 </Section>
76106
77− <Section narrow>
107+ <Section>
78108 {happeningNow.length > 0 ? (
79109 <>
80110 <div className="mb-8 mt-16 table">
81111 <h3 className="text-green-400">Happening Now</h3>
82112 </div>
83− {happeningNow.map((post) => {
84− return <EventPreview big event={post} className="mb-8" />;
113+ {happeningNow.map((group) => {
114+ return (
115+ <TwoUp>
116+ {group.map((post) => (
117+ <EventPreview big event={post} className="mb-8" />
118+ ))}
119+ </TwoUp>
120+ );
85121 })}
86122 </>
87123 ) : null}
@@ -90,8 +126,14 @@ export default function Events({ events, search }) {
90126 <div className="mb-8 mt-16 table">
91127 <h3 className="">Coming Soon</h3>
92128 </div>
93− {futureEvents.map((post) => {
94− return <EventPreview big event={post} className="mb-8" />;
129+ {futureEvents.map((group) => {
130+ return (
131+ <TwoUp>
132+ {group.map((post) => (
133+ <EventPreview event={post} className="mb-8" />
134+ ))}
135+ </TwoUp>
136+ );
95137 })}
96138 </>
97139 ) : null}
@@ -99,8 +141,14 @@ export default function Events({ events, search }) {
99141 <h3 className="text-wall-500">Past Events</h3>
100142 </div>
101143
102− {pastEvents.map((post) => {
103− return <EventPreview big event={post} className="mb-8" />;
144+ {pastEvents.map((group) => {
145+ return (
146+ <TwoUp>
147+ {group.map((post) => (
148+ <EventPreview event={post} className="mb-8" />
149+ ))}
150+ </TwoUp>
151+ );
104152 })}
105153 </Section>
106154 </SingleColumn>