Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Format *.ts, *.js, *.css, *.html, *.json and *.svelte
Rūdolfs Ošiņš committed 3 years ago
commit b3cec1f314fdc864ec146aa930166bb927fa43b7
parent 687174a8e0019b7866904dcec7d2ef829a4ebc70
148 files changed +4040 -2555
modified .eslintrc.json
@@ -4,10 +4,7 @@
    "browser": true,
    "node": true
  },
-
  "extends": [
-
    "eslint:recommended",
-
    "plugin:@typescript-eslint/recommended"
-
  ],
+
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2020,
@@ -25,18 +22,31 @@
    "curly": ["error", "multi-line", "consistent"],
    "keyword-spacing": ["error"],
    "no-implicit-globals": ["error"],
-
    "no-restricted-globals": ["error", "name", "event", "frames", "history", "length", "content", "origin", "status"],
+
    "no-restricted-globals": [
+
      "error",
+
      "name",
+
      "event",
+
      "frames",
+
      "history",
+
      "length",
+
      "content",
+
      "origin",
+
      "status"
+
    ],
    "no-trailing-spaces": ["error"],
    "no-multi-spaces": ["error"],
    "no-multiple-empty-lines": ["error"],
    "space-before-blocks": ["error"],
    "object-curly-spacing": ["error", "always"],
    "array-bracket-spacing": ["error", "never"],
-
    "space-before-function-paren": ["error", {
-
      "anonymous": "always",
-
      "named": "never",
-
      "asyncArrow": "always"
-
    }],
+
    "space-before-function-paren": [
+
      "error",
+
      {
+
        "anonymous": "always",
+
        "named": "never",
+
        "asyncArrow": "always"
+
      }
+
    ],
    "eol-last": ["error"],
    "key-spacing": ["error"],
    "@typescript-eslint/object-curly-spacing": ["error", "always"],
@@ -77,7 +87,8 @@
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/semi": ["error"],
    "@typescript-eslint/member-delimiter-style": [
-
      "error", {
+
      "error",
+
      {
        "multiline": {
          "delimiter": "semi",
          "requireLast": true
@@ -90,7 +101,10 @@
    ],
    // Disallow Unused Variables.
    // https://eslint.org/docs/rules/no-unused-vars
-
    "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+
    "@typescript-eslint/no-unused-vars": [
+
      "error",
+
      { "argsIgnorePattern": "^_" }
+
    ],
    // Require using arrow functions as callbacks.
    // https://eslint.org/docs/rules/prefer-arrow-callback
    "prefer-arrow-callback": "error",
modified .nycrc.json
@@ -1,10 +1,5 @@
{
  "all": true,
-
  "extension": [
-
    ".svelte",
-
    ".ts"
-
  ],
-
  "include": [
-
    "src/**/*"
-
  ]
+
  "extension": [".svelte", ".ts"],
+
  "include": ["src/**/*"]
}
modified cypress/e2e/home.spec.ts
@@ -3,17 +3,37 @@ import { MockExtensionProvider } from "../support/e2e";

describe("Landing page", () => {
  it("Loads projects", () => {
-
    cy.intercept("https://willow.radicle.garden:8777/", { fixture: "projectHome.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
-
    cy.intercept("https://pine.radicle.garden:8777/", { fixture: "projectHome.json" });
-
    cy.intercept("https://pine.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
-
    cy.intercept("https://maple.radicle.garden:8777/", { fixture: "projectHome.json" });
-
    cy.intercept("https://maple.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
-
    cy.intercept("https://maple.radicle.garden:8777/v1/projects/*", { fixture: "projectInfo.json" });
-
    cy.intercept({ pathname: "/v1/projects/*" }, { fixture: "projectInfo.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/", {
+
      fixture: "projectHome.json",
+
    });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", {
+
      fixture: "projectPeer.json",
+
    });
+
    cy.intercept("https://pine.radicle.garden:8777/", {
+
      fixture: "projectHome.json",
+
    });
+
    cy.intercept("https://pine.radicle.garden:8777/v1/peer", {
+
      fixture: "projectPeer.json",
+
    });
+
    cy.intercept("https://maple.radicle.garden:8777/", {
+
      fixture: "projectHome.json",
+
    });
+
    cy.intercept("https://maple.radicle.garden:8777/v1/peer", {
+
      fixture: "projectPeer.json",
+
    });
+
    cy.intercept("https://maple.radicle.garden:8777/v1/projects/*", {
+
      fixture: "projectInfo.json",
+
    });
+
    cy.intercept(
+
      { pathname: "/v1/projects/*" },
+
      { fixture: "projectInfo.json" },
+
    );
    cy.visit("/", {
      onBeforeLoad(win) {
-
        win.ethereum = new MockExtensionProvider("homestead", "0x3256a804085C24f3451cAb2C98a37e16DEEc5721");
+
        win.ethereum = new MockExtensionProvider(
+
          "homestead",
+
          "0x3256a804085C24f3451cAb2C98a37e16DEEc5721",
+
        );
      },
    });
    cy.get(".project .name")
modified cypress/e2e/project.spec.ts
@@ -13,12 +13,12 @@ const groupedCommits = [
          description: "",
          committer: {
            name: "dabit3",
-
            mail: "dabit3@gmail.com"
+
            mail: "dabit3@gmail.com",
          },
-
          committerTime: "06:51 GMT+1"
-
        }
-
      }
-
    ]
+
          committerTime: "06:51 GMT+1",
+
        },
+
      },
+
    ],
  },
  {
    groupDate: "Wednesday, March 2, 2022",
@@ -30,10 +30,10 @@ const groupedCommits = [
          description: "",
          committer: {
            name: "dabit3",
-
            mail: "dabit3@gmail.com"
+
            mail: "dabit3@gmail.com",
          },
-
          committerTime: "17:14 GMT+1"
-
        }
+
          committerTime: "17:14 GMT+1",
+
        },
      },
      {
        header: {
@@ -42,37 +42,80 @@ const groupedCommits = [
          description: "this is the first commit of many",
          committer: {
            name: "dabit3",
-
            mail: "dabit3@gmail.com"
+
            mail: "dabit3@gmail.com",
          },
-
          committerTime: "16:58 GMT+1"
-
        }
-
      }
-
    ]
-
  }
+
          committerTime: "16:58 GMT+1",
+
        },
+
      },
+
    ],
+
  },
];

describe("Project view", () => {
  beforeEach(() => {
-
    cy.intercept("https://willow.radicle.garden:8777/", { fixture: "projectHome.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", { fixture: "projectPeer.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/bright-forest-protocol", { fixture: "projectInfo.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy", { fixture: "projectInfo.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes", { fixture: "projectRemotes.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/issues", { fixture: "projectIssues.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/patches", { fixture: "projectPatches.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/56e4e029c294b08546386e1fb706b772c7433c49", { fixture: "projectTree56e4e02.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectTreecbf5df4.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke", { fixture: "projectBranches.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/56e4e029c294b08546386e1fb706b772c7433c49", { fixture: "projectReadme.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits?parent=cbf5df499ab4f4a908f1756fbe2c236a4530516a?verified=true", { fixture: "projectCommits.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectReadme.json" });
-
    cy.intercept("https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a", { fixture: "projectCommit.json" });
+
    cy.intercept("https://willow.radicle.garden:8777/", {
+
      fixture: "projectHome.json",
+
    });
+
    cy.intercept("https://willow.radicle.garden:8777/v1/peer", {
+
      fixture: "projectPeer.json",
+
    });
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/bright-forest-protocol",
+
      { fixture: "projectInfo.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy",
+
      { fixture: "projectInfo.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes",
+
      { fixture: "projectRemotes.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/issues",
+
      { fixture: "projectIssues.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/patches",
+
      { fixture: "projectPatches.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/56e4e029c294b08546386e1fb706b772c7433c49",
+
      { fixture: "projectTree56e4e02.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/tree/cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      { fixture: "projectTreecbf5df4.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke",
+
      { fixture: "projectBranches.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/56e4e029c294b08546386e1fb706b772c7433c49",
+
      { fixture: "projectReadme.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits?parent=cbf5df499ab4f4a908f1756fbe2c236a4530516a?verified=true",
+
      { fixture: "projectCommits.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/readme/cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      { fixture: "projectReadme.json" },
+
    );
+
    cy.intercept(
+
      "https://willow.radicle.garden:8777/v1/projects/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      { fixture: "projectCommit.json" },
+
    );
  });

  it("Page header", () => {
    cy.visit("/seeds/willow.radicle.garden/bright-forest-protocol", {
      onBeforeLoad(win) {
-
        win.ethereum = new MockExtensionProvider("homestead", "0x3256a804085C24f3451cAb2C98a37e16DEEc5721");
+
        win.ethereum = new MockExtensionProvider(
+
          "homestead",
+
          "0x3256a804085C24f3451cAb2C98a37e16DEEc5721",
+
        );
      },
    });
    cy.get("div.title").contains("bright-forest-protocol");
@@ -81,25 +124,41 @@ describe("Project view", () => {
  });

  it("Browser", () => {
-
    cy.get("div.column-right article div.markdown h1").should("have.text", "Basic Sample Hardhat Project");
+
    cy.get("div.column-right article div.markdown h1").should(
+
      "have.text",
+
      "Basic Sample Hardhat Project",
+
    );
  });

  it("Project Header", () => {
    cy.get("div.stat.seed span").should("have.text", "willow.radicle.garden");
-
    cy.get("div.stat.commit-count").should("have.text", "3 commit(s)");
-
    cy.get("div.stat.contributor-count").should("have.text", "1 contributor(s)");
-
    cy.get("div.stat.branch").should("have.class", "not-allowed").should("have.text", "main");
+
    cy.get("div.stat.commit-count").should("have.text", "3\n    commit(s)");
+
    cy.get("div.stat.contributor-count").should(
+
      "have.text",
+
      "1\n    contributor(s)",
+
    );
+
    cy.get("div.stat.branch")
+
      .should("have.class", "not-allowed")
+
      .should("have.text", "main");
    cy.get("div.hash.desktop").should("have.text", "56e4e02");
    cy.get("div.clone-button").click();
  });

  it("Peer selector", () => {
    cy.get("div.selector").click();
-
    cy.get("div.dropdown-item").contains("dabit3 hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke delegate").click();
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree');
+
    cy.get("div.dropdown-item")
+
      .contains(
+
        "dabit3 hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke delegate",
+
      )
+
      .click();
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree",
+
      );
    });
-
    cy.get("span.peer-id span[title='hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke']").should("have.text", "hyndc7…j9oeke");
+
    cy.get(
+
      "span.peer-id span[title='hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke']",
+
    ).should("have.text", "hyndc7…j9oeke");
    cy.get("div.stat.peer span.peer-id").should("have.text", "dabit3");
    cy.get("div.stat.peer span.badge").should("have.text", "delegate");
  });
@@ -112,78 +171,109 @@ describe("Project view", () => {
      .next()
      .contains("master")
      .click();
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree/master');
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/tree/master",
+
      );
    });
    cy.get("div.stat.branch").should("have.text", "master");
    cy.get("div.hash.desktop").should("have.text", "cbf5df4");
  });

  it("Commits button", () => {
-
    cy.get("div.stat.commit-count")
-
      .should("not.have.class", "active")
-
      .click();
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/history/master');
+
    cy.get("div.stat.commit-count").should("not.have.class", "active").click();
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/history/master",
+
      );
    });
    cy.get("div.stat.commit-count").should("have.class", "active");
  });

  it("Commit group & commit trailer", () => {
    // This iterates over the commit groups and then over each commit.
-
    cy.get(".commit-group").should("have.length", 2)
+
    cy.get(".commit-group")
+
      .should("have.length", 2)
      .each((item, index) => {
-
        expect(Cypress.$(item.children(".commit-date")).text()).to.eq(groupedCommits[index].groupDate);
+
        expect(Cypress.$(item.children(".commit-date")).text()).to.eq(
+
          groupedCommits[index].groupDate,
+
        );
        const $el = Cypress.$(item.find(".commit-teaser"));
        cy.wrap($el).each((commit, commitIndex) => {
-
          expect(Cypress.$(commit).find(".hash").text()).to.eq(groupedCommits[index].commits[commitIndex].header.sha);
-
          expect(Cypress.$(commit).find(".summary").text()).to.eq(groupedCommits[index].commits[commitIndex].header.summary);
-
          expect(Cypress.$(commit).find(".committer").first().text()).to.eq(groupedCommits[index].commits[commitIndex].header.committer.name);
+
          expect(Cypress.$(commit).find(".hash").text()).to.eq(
+
            groupedCommits[index].commits[commitIndex].header.sha,
+
          );
+
          expect(Cypress.$(commit).find(".summary").text()).to.eq(
+
            groupedCommits[index].commits[commitIndex].header.summary,
+
          );
+
          expect(Cypress.$(commit).find(".committer").first().text()).to.eq(
+
            groupedCommits[index].commits[commitIndex].header.committer.name,
+
          );
        });
      });

-
    cy.get('.commit-teaser .badge').trigger('mouseenter');
+
    cy.get(".commit-teaser .badge").trigger("mouseenter");
    // Checking that the initial commit has the Verified badge
-
    cy.get(".popup .header").should("have.text", "✔ This commit was signed\n          with the committer's radicle key.");
-
    cy.get(".popup .peer").should("contain.text", "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue");
+
    cy.get(".popup .header").should(
+
      "have.text",
+
      "✔ This commit was signed\n          with the committer's radicle key.",
+
    );
+
    cy.get(".popup .peer").should(
+
      "contain.text",
+
      "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
    );
    cy.get(".popup .committer").should("contain.text", "dabit3");

    cy.get(".commit").last().click();
  });

  it("Commit detail view", () => {
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a');
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/commits/cbf5df499ab4f4a908f1756fbe2c236a4530516a",
+
      );
    });
-
    cy.get("header .summary .text-medium").should("have.text", "initial commit");
-
    cy.get("header pre.description").should("have.text", "this is the first commit of many");
+
    cy.get("header .summary .text-medium").should(
+
      "have.text",
+
      "initial commit",
+
    );
+
    cy.get("header pre.description").should(
+
      "have.text",
+
      "this is the first commit of many",
+
    );
    cy.get("header .committer").should("have.text", "dabit3");
-
    cy.get("div.changeset-summary").should("have.text", "1 file(s) changed, 1 file(s) created, 1 file(s) deleted\n  with\n  0 addition(s)\n  and\n  0 deletion(s)");
+
    cy.get("div.changeset-summary").should(
+
      "have.text",
+
      "1 file(s) changed, 1 file(s) created, 1 file(s) deleted\n  with\n  0 addition(s)\n  and\n  0 deletion(s)",
+
    );
    cy.get("header.file-header:nth-child(1) p.file-path")
      .first()
      .should("have.text", "test.md")
      .next()
      .should("have.text", "created");
    cy.get("tr.diff-line td.diff-line-number").contains("16");
-
    cy.get("tr.diff-line td.diff-line-content").contains("To prevent front-running, the RAD/USDC balances are set through the Uniswap router *proxy* contract");
+
    cy.get("tr.diff-line td.diff-line-content").contains(
+
      "To prevent front-running, the RAD/USDC balances are set through the Uniswap router *proxy* contract",
+
    );
  });

  it("Issues button", () => {
-
    cy.get("div.stat.issue-count")
-
      .click();
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/issues');
+
    cy.get("div.stat.issue-count").click();
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/issues",
+
      );
    });
    cy.get("div.stat.issue-count").should("have.class", "active");
  });

  it("Patches button", () => {
-
    cy.get("div.stat.patch-count")
-
      .click();
-
    cy.location().should((location) => {
-
      expect(location.pathname).to.eq('/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/patches');
+
    cy.get("div.stat.patch-count").click();
+
    cy.location().should(location => {
+
      expect(location.pathname).to.eq(
+
        "/seeds/willow.radicle.garden/rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy/remotes/hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke/patches",
+
      );
    });
    cy.get("div.stat.patch-count").should("have.class", "active");
  });
});
-

modified cypress/fixtures/ceramicStream.json
@@ -5,9 +5,7 @@
    "content": "did:3:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383",
    "metadata": {
      "family": "caip10-eip155:1",
-
      "controllers": [
-
        "0x5e813e48a81977c6fdd565ed5097eb600c73c4f0@eip155:1"
-
      ]
+
      "controllers": ["0x5e813e48a81977c6fdd565ed5097eb600c73c4f0@eip155:1"]
    },
    "signature": 2,
    "anchorStatus": "ANCHORED",
modified cypress/fixtures/projectCommit.json
@@ -142,8 +142,5 @@
      }
    ]
  },
-
  "branches": [
-
    "main",
-
    "master"
-
  ]
+
  "branches": ["main", "master"]
}
modified cypress/fixtures/projectInfo.json
@@ -7,9 +7,7 @@
    {
      "type": "indirect",
      "urn": "rad:git:hnrkqz68g6nddigodpgjmc1u8ydpzb8fqq8ro",
-
      "ids": [
-
        "hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"
-
      ]
+
      "ids": ["hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"]
    }
  ],
  "head": "56e4e029c294b08546386e1fb706b772c7433c49",
modified cypress/fixtures/projectIssues.json
@@ -27,18 +27,12 @@
        }
      },
      "body": "We should define the scope we want to test in integration vs component testing (I don't mention E2E here on purpose since in our E2E tests we stubbed all responses and never hit a real server which equals to integration testing).\n\nWhile integration testing allows us to traverse the application and test navigation, component testing allows us to mount a component in isolation and check all the possible edge cases and possible regressions, and unit testing goes one step further to just individual functions on their correctness.\n\nIntegration testing npm run test:e2e eventually should be npm run test:integration\n\n**Connection through mocked Web3Provider**\n- Navigation through different URLs (e.g. the project pages, commits, issues, patches, etc.)\n- Assert the URLs we're hitting and that they update correctly, when state changes\n- Assert the least amount of display logic (we should leave that to component testing)\n  If we are able to navigate to the correct URL and get the correct props, we shouldn't need to assert every piece of text.\nComponent testing npm run test:components\n\n- Assert the correct rendering\n- Test that the state of the component updates accordingly where applicable.\n**Unit tests npm run test:unit**\n\n- Util functions\nClasses (static and methods)\nI'll keep updating this issue.\nP.S.: Testing is not my expertise so any feedback is welcome.",
-
      "reactions": {
-

-
      },
+
      "reactions": {},
      "replies": null,
      "timestamp": 1656509367
    },
-
    "discussion": [
-

-
    ],
-
    "labels": [
-
      "discussion"
-
    ],
+
    "discussion": [],
+
    "labels": ["discussion"],
    "timestamp": 1656509367
  },
  {
@@ -69,18 +63,12 @@
        }
      },
      "body": "We should define the scope we want to test in integration vs component testing (I don't mention E2E here on purpose since in our E2E tests we stubbed all responses and never hit a real server which equals to integration testing).\n\nWhile integration testing allows us to traverse the application and test navigation, component testing allows us to mount a component in isolation and check all the possible edge cases and possible regressions, and unit testing goes one step further to just individual functions on their correctness.\n\nIntegration testing npm run test:e2e eventually should be npm run test:integration\n\n**Connection through mocked Web3Provider**\n- Navigation through different URLs (e.g. the project pages, commits, issues, patches, etc.)\n- Assert the URLs we're hitting and that they update correctly, when state changes\n- Assert the least amount of display logic (we should leave that to component testing)\n  If we are able to navigate to the correct URL and get the correct props, we shouldn't need to assert every piece of text.\nComponent testing npm run test:components\n\n- Assert the correct rendering\n- Test that the state of the component updates accordingly where applicable.\n**Unit tests npm run test:unit**\n\n- Util functions\nClasses (static and methods)\nI'll keep updating this issue.\nP.S.: Testing is not my expertise so any feedback is welcome.",
-
      "reactions": {
-

-
      },
+
      "reactions": {},
      "replies": null,
      "timestamp": 1656509367
    },
-
    "discussion": [
-

-
    ],
-
    "labels": [
-
      "discussion"
-
    ],
+
    "discussion": [],
+
    "labels": ["discussion"],
    "timestamp": 1656509367
  }
]
modified cypress/fixtures/projectList.json
@@ -1,16 +1,16 @@
-
[{
-
  "urn": "rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy",
-
  "name": "mocked-seed-protocol",
-
  "description": "bfc-sc",
-
  "defaultBranch": "main",
-
  "delegates": [
-
    {
-
      "type": "indirect",
-
      "urn": "rad:git:hnrkqz68g6nddigodpgjmc1u8ydpzb8fqq8ro",
-
      "ids": [
-
        "hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"
-
      ]
-
    }
-
  ],
-
  "head": "56e4e029c294b08546386e1fb706b772c7433c49"
-
}]
+
[
+
  {
+
    "urn": "rad:git:hnrk8mbpirp7ua7sy66o4t9soasbq4y8uwgoy",
+
    "name": "mocked-seed-protocol",
+
    "description": "bfc-sc",
+
    "defaultBranch": "main",
+
    "delegates": [
+
      {
+
        "type": "indirect",
+
        "urn": "rad:git:hnrkqz68g6nddigodpgjmc1u8ydpzb8fqq8ro",
+
        "ids": ["hyndc7nx9keq76p1bkw9831arcndeeu3trwsc7kxt3osmpi6j9oeke"]
+
      }
+
    ],
+
    "head": "56e4e029c294b08546386e1fb706b772c7433c49"
+
  }
+
]
modified cypress/fixtures/projectPatches.json
@@ -29,9 +29,7 @@
            }
          },
          "body": "Signed-off-by: Sebastian Martinez <me@sebastinez.dev>",
-
          "reactions": {
-

-
          },
+
          "reactions": {},
          "replies": null,
          "timestamp": 1656666433
        },
modified cypress/support/component.ts
@@ -1,4 +1,4 @@
-
import '@testing-library/cypress/add-commands';
+
import "@testing-library/cypress/add-commands";
import { Buffer } from "buffer";

//@ts-expect-error We need Buffer on the window object in the test env for component testing
modified cypress/support/e2e.ts
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-
import '@cypress/code-coverage/support';
-
import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers';
-
import '@testing-library/cypress/add-commands';
-
import { BigNumber, ethers } from 'ethers';
+
import "@cypress/code-coverage/support";
+
import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers";
+
import "@testing-library/cypress/add-commands";
+
import { BigNumber, ethers } from "ethers";
import { Resolver } from "@ethersproject/providers";

declare global {
@@ -27,7 +27,11 @@ export class MockExtensionProvider extends ethers.providers.BaseProvider {
  }

  getSigner(addressOrIndex?: string | number): JsonRpcSigner {
-
    return new JsonRpcSigner({}, this as unknown as JsonRpcProvider, addressOrIndex);
+
    return new JsonRpcSigner(
+
      {},
+
      this as unknown as JsonRpcProvider,
+
      addressOrIndex,
+
    );
  }

  async getResolver(name: string): Promise<null | Resolver> {
@@ -35,7 +39,9 @@ export class MockExtensionProvider extends ethers.providers.BaseProvider {
    return new Resolver(this, address, name, address);
  }

-
  async lookupAddress(address: string | Promise<string>): Promise<string | null> {
+
  async lookupAddress(
+
    address: string | Promise<string>,
+
  ): Promise<string | null> {
    return "mock.eth";
  }

@@ -44,27 +50,33 @@ export class MockExtensionProvider extends ethers.providers.BaseProvider {
    this.emit("accountsChanged", [address]);
  }

-
  async request({ method, params }: { method: string; params: any }): Promise<any> {
+
  async request({
+
    method,
+
    params,
+
  }: {
+
    method: string;
+
    params: any;
+
  }): Promise<any> {
    switch (method) {
-
      case 'eth_chainId':
+
      case "eth_chainId":
        return this.network.chainId;
-
      case 'net_version':
+
      case "net_version":
        return this.network.chainId;
-
      case 'eth_call':
+
      case "eth_call":
        return resolveEthCall(params);
-
      case 'eth_accounts':
+
      case "eth_accounts":
        return [this.currentAddress];
-
      case 'eth_requestAccounts':
+
      case "eth_requestAccounts":
        return [this.currentAddress];
-
      case 'eth_getCode':
+
      case "eth_getCode":
        return "0x";
-
      case 'eth_blockNumber':
+
      case "eth_blockNumber":
        return BigNumber.from(1);
-
      case 'eth_estimateGas':
+
      case "eth_estimateGas":
        return BigNumber.from(21000);
-
      case 'eth_sendTransaction':
+
      case "eth_sendTransaction":
        return "0x8829dea7e20ebcf6dbfd942e3613d7ac49b9aef3ecbed396acfc5901713f5983";
-
      case 'eth_getTransactionByHash':
+
      case "eth_getTransactionByHash":
        return {
          hash: "0x8829dea7e20ebcf6dbfd942e3613d7ac49b9aef3ecbed396acfc5901713f5983",
          to: "0x0000000000000000000000000000000000000000",
@@ -76,26 +88,29 @@ export class MockExtensionProvider extends ethers.providers.BaseProvider {
          value: BigNumber.from(0),
          chainId: 4,
        };
-
      case 'eth_getTransactionCount':
+
      case "eth_getTransactionCount":
        return BigNumber.from(123);
-
      case 'eth_getTransactionReceipt':
+
      case "eth_getTransactionReceipt":
        return {
-
          to: '0x01139F82659A3bD56D1f051D57D4Bc96a3b9Ef05',
-
          from: '0x00192Fb10dF37c9FB26829eb2CC623cd1BF599E8',
+
          to: "0x01139F82659A3bD56D1f051D57D4Bc96a3b9Ef05",
+
          from: "0x00192Fb10dF37c9FB26829eb2CC623cd1BF599E8",
          contractAddress: null,
          transactionIndex: 338,
-
          gasUsed: BigNumber.from('0x5208'),
-
          logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
-
          blockHash: '0xdbe7ca0dd26310cd5415202c67467d46795cfec922dbb47e4cc4ff388e7856d2',
-
          transactionHash: '0x8829dea7e20ebcf6dbfd942e3613d7ac49b9aef3ecbed396acfc5901713f5983',
+
          gasUsed: BigNumber.from("0x5208"),
+
          logsBloom:
+
            "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+
          blockHash:
+
            "0xdbe7ca0dd26310cd5415202c67467d46795cfec922dbb47e4cc4ff388e7856d2",
+
          transactionHash:
+
            "0x8829dea7e20ebcf6dbfd942e3613d7ac49b9aef3ecbed396acfc5901713f5983",
          logs: [],
          blockNumber: 14451272,
          confirmations: 53,
-
          cumulativeGasUsed: BigNumber.from('0x01b11d57'),
-
          effectiveGasPrice: BigNumber.from('0x0dffd03ca0'),
+
          cumulativeGasUsed: BigNumber.from("0x01b11d57"),
+
          effectiveGasPrice: BigNumber.from("0x0dffd03ca0"),
          status: 1,
          type: 2,
-
          byzantium: true
+
          byzantium: true,
        };
      default:
        console.debug("Unknown method", method);
@@ -110,19 +125,33 @@ function resolveEthCall(params: { to: string; data: string }[]): string {
  // Get Resolver
  if (to === "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e") {
    return "0x0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41";
-
  // Get ENS Attributes
-
  } else if (to === "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41" && data === "0x59d1d43c567c364804de7bbedb53f583e483f6b73513fd2f44299e281024e4719da0b332000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066176617461720000000000000000000000000000000000000000000000000000") {
+
    // Get ENS Attributes
+
  } else if (
+
    to === "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41" &&
+
    data ===
+
      "0x59d1d43c567c364804de7bbedb53f583e483f6b73513fd2f44299e281024e4719da0b332000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000066176617461720000000000000000000000000000000000000000000000000000"
+
  ) {
    return "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001f68747470733a2f2f636c6f7564686561642e696f2f6176617461722e706e6700";
-
  // Get Org informations
-
  } else if (to === "0x8152237402e0f194176154c3a6ea1eb99b611482" && data === "0x8da5cb5b") {
+
    // Get Org informations
+
  } else if (
+
    to === "0x8152237402e0f194176154c3a6ea1eb99b611482" &&
+
    data === "0x8da5cb5b"
+
  ) {
    return "0x000000000000000000000000ceab094641905c209cc796fc8037dd9ecc87ca2f";
    // Get Token Balance
-
  } else if (to === "0x31c8eacbffdd875c74b94b077895bd78cf1e64a3" && data === "0x70a082310000000000000000000000003256a804085c24f3451cab2c98a37e16deec5721") {
+
  } else if (
+
    to === "0x31c8eacbffdd875c74b94b077895bd78cf1e64a3" &&
+
    data ===
+
      "0x70a082310000000000000000000000003256a804085c24f3451cab2c98a37e16deec5721"
+
  ) {
    return "0x00000000000000000000000000000000000000000000000246DDF97976680000";
-
  // getMaxWithdrawAmount
-
  } else if (to === "0x9aa75397ed632a3060acb5de7f96e2457bceed8d" && data === "0xf516440c") {
+
    // getMaxWithdrawAmount
+
  } else if (
+
    to === "0x9aa75397ed632a3060acb5de7f96e2457bceed8d" &&
+
    data === "0xf516440c"
+
  ) {
    return "0x000000000000000000000000000000000000000000000001D7D843DC3B480000";
-
  // Get resolved address from ENS
+
    // Get resolved address from ENS
  } else if (to === "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41") {
    return "0x000000000000000000000000394b920c5d39e0ca40fca2871569b6b90d750c7c";
    // Return 0 for token balances else
modified index.html
@@ -3,21 +3,33 @@
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
-
    <meta name="name" content="Radicle Interface">
+
    <meta name="name" content="Radicle Interface" />
    <meta name="description" content="Interact with Radicle" />
-
    <meta name="theme-color" content="#0e171f">
+
    <meta name="theme-color" content="#0e171f" />
    <meta property="og:title" content="Radicle Interface" />
    <meta property="og:description" content="Interact with Radicle." />
    <meta property="og:url" content="https://app.radicle.xyz" />
-
    <meta property="og:image" content="https://app.radicle.xyz/images/app.png" />
+
    <meta
+
      property="og:image"
+
      content="https://app.radicle.xyz/images/app.png" />
    <meta property="og:image:width" content="240" />
    <meta property="og:image:height" content="240" />
    <meta property="og:type" content="website" />
    <link rel="stylesheet" type="text/css" href="/index.css" />
-
    <link rel="icon" href="/favicon.svg" type="image/svg+xml">
-
    <link rel="icon" href="/favicon.ico">
-
    <link rel="preload" href="/fonts/Inter-Regular.woff" as="font" type="font/woff" crossorigin="anonymous">
-
    <link rel="preload" href="/fonts/Inter-SemiBold.otf" as="font" type="font/otf" crossorigin="anonymous">
+
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
+
    <link rel="icon" href="/favicon.ico" />
+
    <link
+
      rel="preload"
+
      href="/fonts/Inter-Regular.woff"
+
      as="font"
+
      type="font/woff"
+
      crossorigin="anonymous" />
+
    <link
+
      rel="preload"
+
      href="/fonts/Inter-SemiBold.otf"
+
      as="font"
+
      type="font/otf"
+
      crossorigin="anonymous" />
    <script type="text/javascript">
      // Workaround to get `global` to work in old-school transitive
      // dependencies.
modified public/index.css
@@ -1,109 +1,109 @@
@import url(./typography.css);

* {
-
	outline: none !important;
-
	box-sizing: border-box;
+
  outline: none !important;
+
  box-sizing: border-box;
}

:root {
-
	--color-primary: #ff55ff;
-
	--color-primary-1: #382847;
-
	--color-primary-2: #62326d;
-
	--color-primary-5: #dd44dd;
-
	--color-primary-6: #ffd4ff;
-
	--color-primary-faded: #ff55ff77;
-
	--color-primary-background: #ff55ff11;
-
	--color-primary-background-lighter: #ff55ff22;
-
	--color-secondary: #5555ff;
-
	--color-secondary-background: #5555ff11;
-
	--color-secondary-background-darker: #5555ff09;
-
	--color-secondary-faded: #5555ff77;
-
	--color-secondary-darker: #4343cc;
-
	--color-secondary-1: #212847;
-
	--color-secondary-2: #2c326d;
-
	--color-secondary-6: #e3e3ff;
-
	--color-tertiary: #55ffff;
-
	--color-tertiary-faded: #ade4e4;
-
	--color-tertiary-background: #55ffff11;
-
	--color-tertiary-1: #214047;
-
	--color-tertiary-2: #2c326d;
-
	--color-tertiary-6: #e3e3ff;
-
	--color-light: #add0e4;
-
	--color-yellow: #ffff99;
-
	--color-yellow-background: #ffff9911;
-
	--color-yellow-background-solid: #222929;
-
	--color-yellow-background-lighter: #ffff9922;
-
	--color-positive: #53db53;
-
	--color-positive-background: #53db5311;
-
	--color-positive-1: #11332b;
-
	--color-positive-2: #2c6837;
-
	--color-positive-6: #e3ffe3;
-
	--color-caution: #ffc555;
-
	--color-caution-1: #2f312d;
-
	--color-caution-2: #524a34;
-
	--color-caution-6: #ffefcf;
-
	--color-negative: #ff5555;
-
	--color-negative-background: #ff555511;
-
	--color-negative-1: #35202b;
-
	--color-negative-2: #623237;
-
	--color-negative-6: #ffd4d4;
-
	--color-foreground: #ffffff;
-
	--color-foreground-90: #ddddee;
-
	--color-foreground-80: #aaaab6;
-
	--color-foreground-70: #9999aa;
-
	--color-foreground-faded: #777788;
-
	--color-foreground-subtle: #444455;
-
	--color-foreground-subtler: #333344;
-
	--color-foreground-even-subtler: #292936;
-
	--color-foreground-background: #121a21;
-
	--color-foreground-background-subtle: #10171e;
-
	--color-foreground-background-lighter: #151f24;
-
	--color-foreground-1: #242e38;
-
	--color-foreground-2: #29343d;
-
	--color-foreground-3: #333e47;
-
	--color-foreground-4: #5e6d7a;
-
	--color-foreground-5: #8594a1;
-
	--color-foreground-6: #d3dee8;
-
	--color-background: #0b131a;
-
	--color-shadow: rgba(0, 0, 0, 0.2);
-
	--color-glow: #5555ff22;
-
	--color-glow-error: #ff555522;
-
	--color-scrollbar: var(--color-foreground-subtler);
-

-
	--font-family-sans-serif: Inter, sans-serif;
-
	--font-family-monospace: monospace;
-
	--font-weight-medium: 600;
-
	--font-weight-bold: 700;
-
	--border-radius: 0.75rem;
-
	--border-radius-small: 0.5rem;
-
	--border-radius-round: 10rem;
-
	--box-shadow-color: var(--color-secondary-2);
-

-
	--content-max-width: 1920px;
-
	--content-min-width: 480px;
-

-
	--scrollbar-width: 0.5rem;
-

-
	--button-regular-height: 2.5rem;
-
	--button-small-height: 2rem;
-
	--button-tiny-height: 1.5rem;
+
  --color-primary: #ff55ff;
+
  --color-primary-1: #382847;
+
  --color-primary-2: #62326d;
+
  --color-primary-5: #dd44dd;
+
  --color-primary-6: #ffd4ff;
+
  --color-primary-faded: #ff55ff77;
+
  --color-primary-background: #ff55ff11;
+
  --color-primary-background-lighter: #ff55ff22;
+
  --color-secondary: #5555ff;
+
  --color-secondary-background: #5555ff11;
+
  --color-secondary-background-darker: #5555ff09;
+
  --color-secondary-faded: #5555ff77;
+
  --color-secondary-darker: #4343cc;
+
  --color-secondary-1: #212847;
+
  --color-secondary-2: #2c326d;
+
  --color-secondary-6: #e3e3ff;
+
  --color-tertiary: #55ffff;
+
  --color-tertiary-faded: #ade4e4;
+
  --color-tertiary-background: #55ffff11;
+
  --color-tertiary-1: #214047;
+
  --color-tertiary-2: #2c326d;
+
  --color-tertiary-6: #e3e3ff;
+
  --color-light: #add0e4;
+
  --color-yellow: #ffff99;
+
  --color-yellow-background: #ffff9911;
+
  --color-yellow-background-solid: #222929;
+
  --color-yellow-background-lighter: #ffff9922;
+
  --color-positive: #53db53;
+
  --color-positive-background: #53db5311;
+
  --color-positive-1: #11332b;
+
  --color-positive-2: #2c6837;
+
  --color-positive-6: #e3ffe3;
+
  --color-caution: #ffc555;
+
  --color-caution-1: #2f312d;
+
  --color-caution-2: #524a34;
+
  --color-caution-6: #ffefcf;
+
  --color-negative: #ff5555;
+
  --color-negative-background: #ff555511;
+
  --color-negative-1: #35202b;
+
  --color-negative-2: #623237;
+
  --color-negative-6: #ffd4d4;
+
  --color-foreground: #ffffff;
+
  --color-foreground-90: #ddddee;
+
  --color-foreground-80: #aaaab6;
+
  --color-foreground-70: #9999aa;
+
  --color-foreground-faded: #777788;
+
  --color-foreground-subtle: #444455;
+
  --color-foreground-subtler: #333344;
+
  --color-foreground-even-subtler: #292936;
+
  --color-foreground-background: #121a21;
+
  --color-foreground-background-subtle: #10171e;
+
  --color-foreground-background-lighter: #151f24;
+
  --color-foreground-1: #242e38;
+
  --color-foreground-2: #29343d;
+
  --color-foreground-3: #333e47;
+
  --color-foreground-4: #5e6d7a;
+
  --color-foreground-5: #8594a1;
+
  --color-foreground-6: #d3dee8;
+
  --color-background: #0b131a;
+
  --color-shadow: rgba(0, 0, 0, 0.2);
+
  --color-glow: #5555ff22;
+
  --color-glow-error: #ff555522;
+
  --color-scrollbar: var(--color-foreground-subtler);
+

+
  --font-family-sans-serif: Inter, sans-serif;
+
  --font-family-monospace: monospace;
+
  --font-weight-medium: 600;
+
  --font-weight-bold: 700;
+
  --border-radius: 0.75rem;
+
  --border-radius-small: 0.5rem;
+
  --border-radius-round: 10rem;
+
  --box-shadow-color: var(--color-secondary-2);
+

+
  --content-max-width: 1920px;
+
  --content-min-width: 480px;
+

+
  --scrollbar-width: 0.5rem;
+

+
  --button-regular-height: 2.5rem;
+
  --button-small-height: 2rem;
+
  --button-tiny-height: 1.5rem;
}

body {
-
	font-size: 16px;
-
	font-weight: 400;
-
	line-height: 1.5;
-
	min-width: var(--content-min-width);
-
	height: 100%;
-
	margin: 0;
-
	padding: 0;
-
	color: white;
-
	text-align: left;
-
	background-color: var(--color-background);
-
	-webkit-font-smoothing: antialiased;
-
	scrollbar-width: thin;
-
	scrollbar-height: thin;
-
	scrollbar-color: var(--color-scrollbar) transparent;
+
  font-size: 16px;
+
  font-weight: 400;
+
  line-height: 1.5;
+
  min-width: var(--content-min-width);
+
  height: 100%;
+
  margin: 0;
+
  padding: 0;
+
  color: white;
+
  text-align: left;
+
  background-color: var(--color-background);
+
  -webkit-font-smoothing: antialiased;
+
  scrollbar-width: thin;
+
  scrollbar-height: thin;
+
  scrollbar-color: var(--color-scrollbar) transparent;
}

@media (max-width: 720px) {
@@ -113,250 +113,262 @@ body {
}

html {
-
	height: 100%;
-
	overflow-y: scroll;
-
	-webkit-text-size-adjust: 100%;
-
	-ms-text-size-adjust: 100%;
-
	-ms-overflow-style: scrollbar;
-
	-webkit-tap-highlight-color: transparent;
+
  height: 100%;
+
  overflow-y: scroll;
+
  -webkit-text-size-adjust: 100%;
+
  -ms-text-size-adjust: 100%;
+
  -ms-overflow-style: scrollbar;
+
  -webkit-tap-highlight-color: transparent;
}

::-moz-selection {
-
	background: var(--color-primary);
-
	color: var(--color-background);
+
  background: var(--color-primary);
+
  color: var(--color-background);
}
::selection {
-
	background: var(--color-primary);
-
	color: var(--color-background);
+
  background: var(--color-primary);
+
  color: var(--color-background);
}

/* Chrome/Edge/Safari scrollbar */
*::-webkit-scrollbar {
-
	width: var(--scrollbar-width);
-
	height: var(--scrollbar-width);
+
  width: var(--scrollbar-width);
+
  height: var(--scrollbar-width);
}
*::-webkit-scrollbar-track {
-
	background: transparent;
+
  background: transparent;
}
*::-webkit-scrollbar-thumb {
-
	background: transparent;
-
	border-radius: var(--border-radius);
+
  background: transparent;
+
  border-radius: var(--border-radius);
}
*::-webkit-scrollbar-corner {
-
	background: transparent;
+
  background: transparent;
}
*:hover::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb {
-
	background-color: var(--color-scrollbar);
+
  background-color: var(--color-scrollbar);
}

-
.error::selection, .error ::selection {
-
	background: var(--color-negative);
+
.error::selection,
+
.error ::selection {
+
  background: var(--color-negative);
}

code {
-
	font-family: monospace;
-
	font-size: 0.95rem;
+
  font-family: monospace;
+
  font-size: 0.95rem;
}

-
main, section {
-
	display: block;
+
main,
+
section {
+
  display: block;
}

-
body, input, textarea, button {
-
	font-family: var(--font-family-sans-serif);
-
	font-feature-settings: "ss01", "ss02", "cv01", "cv03";
+
body,
+
input,
+
textarea,
+
button {
+
  font-family: var(--font-family-sans-serif);
+
  font-feature-settings: "ss01", "ss02", "cv01", "cv03";
}

button {
-
	color: var(--color-foreground-6);
-
	background: transparent;
-
	font-size: 1rem;
-
	padding: 0rem 1.5rem;
-
	border: 1px solid var(--color-foreground-6);
-
	border-radius: var(--border-radius-round);
-
	cursor: pointer;
-
	min-width: 6rem;
-
	height: var(--button-regular-height);
-
	display: inline-flex;
-
	align-items: center;
-
	justify-content: center;
+
  color: var(--color-foreground-6);
+
  background: transparent;
+
  font-size: 1rem;
+
  padding: 0rem 1.5rem;
+
  border: 1px solid var(--color-foreground-6);
+
  border-radius: var(--border-radius-round);
+
  cursor: pointer;
+
  min-width: 6rem;
+
  height: var(--button-regular-height);
+
  display: inline-flex;
+
  align-items: center;
+
  justify-content: center;
}
button:not([disabled]):hover {
-
	color: var(--color-background);
-
	background-color: var(--color-foreground);
-
	border-color: transparent;
+
  color: var(--color-background);
+
  background-color: var(--color-foreground);
+
  border-color: transparent;
}
button.waiting {
-
	cursor: wait;
+
  cursor: wait;
}
button.secondary {
-
	color: var(--color-secondary);
-
	border-color: var(--color-secondary);
+
  color: var(--color-secondary);
+
  border-color: var(--color-secondary);
}
button.secondary[disabled] {
-
	color: var(--color-secondary-faded);
-
	border-color: var(--color-secondary-faded);
+
  color: var(--color-secondary-faded);
+
  border-color: var(--color-secondary-faded);
}
button.secondary:not([disabled]):hover {
-
	background-color: var(--color-secondary);
+
  background-color: var(--color-secondary);
}
button.primary {
-
	color: var(--color-primary);
-
	border-color: var(--color-primary);
+
  color: var(--color-primary);
+
  border-color: var(--color-primary);
}
-
button.primary:hover, button.primary.active {
-
	color: var(--color-background);
-
	background-color: var(--color-primary);
+
button.primary:hover,
+
button.primary.active {
+
  color: var(--color-background);
+
  background-color: var(--color-primary);
}
button.primary[disabled] {
-
	color: var(--color-primary-faded) !important;
-
	border-color: var(--color-primary-faded) !important;
-
	background-color: transparent;
+
  color: var(--color-primary-faded) !important;
+
  border-color: var(--color-primary-faded) !important;
+
  background-color: transparent;
}
button.faded {
-
	color: var(--color-foreground-90);
-
	border-color: var(--color-foreground-90);
+
  color: var(--color-foreground-90);
+
  border-color: var(--color-foreground-90);
}
button[disabled] {
-
	cursor: not-allowed !important;
-
	color: var(--color-foreground-faded);
-
	border-color: var(--color-foreground-faded);
+
  cursor: not-allowed !important;
+
  color: var(--color-foreground-faded);
+
  border-color: var(--color-foreground-faded);
}
button[data-waiting] {
-
	cursor: wait !important;
+
  cursor: wait !important;
}
-
button.unstyled, button.unstyled:hover, button.unstyled:focus {
-
	padding: 0;
-
	margin: 0;
-
	min-width: 0;
-
	border: none;
-
	color: var(--color-foreground);
-
	background: transparent;
+
button.unstyled,
+
button.unstyled:hover,
+
button.unstyled:focus {
+
  padding: 0;
+
  margin: 0;
+
  min-width: 0;
+
  border: none;
+
  color: var(--color-foreground);
+
  background: transparent;
}

button.text {
-
	color: var(--color-foreground-6);
-
	background-color: transparent;
-
	border: none;
-
	min-width: 3rem;
+
  color: var(--color-foreground-6);
+
  background-color: transparent;
+
  border: none;
+
  min-width: 3rem;
}
button.text:hover {
-
	background-color: var(--color-foreground);
+
  background-color: var(--color-foreground);
}

button.regular {
-
	padding-top: 0.5rem;
-
	padding-bottom: 0.5rem;
-
	min-width: 6rem;
-
	height: var(--button-regular-height);
+
  padding-top: 0.5rem;
+
  padding-bottom: 0.5rem;
+
  min-width: 6rem;
+
  height: var(--button-regular-height);
}
button.small {
-
	min-width: auto;
-
	padding: 0 0.75rem;
-
	height: 2rem;
+
  min-width: auto;
+
  padding: 0 0.75rem;
+
  height: 2rem;
}

a {
-
	color: inherit;
-
	text-decoration: none;
+
  color: inherit;
+
  text-decoration: none;
}
a.link {
-
	color: #d0d0d0;
-
	text-decoration: none;
-
	border-bottom: 1px dashed #444;
+
  color: #d0d0d0;
+
  text-decoration: none;
+
  border-bottom: 1px dashed #444;
}
a.link:hover {
-
	color: var(--color-foreground);
-
	border-bottom-color: var(--color-foreground);
+
  color: var(--color-foreground);
+
  border-bottom-color: var(--color-foreground);
}
a.link.primary {
-
	color: var(--color-primary);
-
	border-bottom-color: var(--color-primary-faded);
+
  color: var(--color-primary);
+
  border-bottom-color: var(--color-primary-faded);
}
a.link.primary:hover {
-
	color: var(--color-primary-faded);
-
	border-bottom-color: var(--color-primary-faded);
+
  color: var(--color-primary-faded);
+
  border-bottom-color: var(--color-primary-faded);
}
a.address {
-
	border-bottom-color: transparent;
+
  border-bottom-color: transparent;
}

-
input[type="text"], button {
-
	line-height: 1.6;
+
input[type="text"],
+
button {
+
  line-height: 1.6;
}
input[type="text"] {
-
	outline: none;
-
	border: none;
-
	font-size: 1rem;
-
	color: var(--color-foreground);
-
	background: transparent;
-
	border: 1px solid var(--color-secondary);
-
	border-radius: var(--border-radius-round);
-
	padding: 1rem 1.5rem;
-
	margin: 1rem;
-
	height: var(--button-regular-height);
+
  outline: none;
+
  border: none;
+
  font-size: 1rem;
+
  color: var(--color-foreground);
+
  background: transparent;
+
  border: 1px solid var(--color-secondary);
+
  border-radius: var(--border-radius-round);
+
  padding: 1rem 1.5rem;
+
  margin: 1rem;
+
  height: var(--button-regular-height);
}
input[type="text"]::placeholder {
-
	color: var(--color-secondary);
-
	opacity: 1 !important;
+
  color: var(--color-secondary);
+
  opacity: 1 !important;
}
input[type="text"].small {
-
	font-size: 0.875rem;
+
  font-size: 0.875rem;
}
input.wide {
-
	width: 44ch;
+
  width: 44ch;
}

label.input {
-
	text-align: left;
-
	display: block;
+
  text-align: left;
+
  display: block;
}

.error {
-
	color: var(--color-negative) !important;
-
	border-color: var(--color-negative) !important;
-
	--box-shadow-color: var(--color-negative-2) !important;
+
  color: var(--color-negative) !important;
+
  border-color: var(--color-negative) !important;
+
  --box-shadow-color: var(--color-negative-2) !important;
}
.error-message {
-
	background-color: var(--color-negative-background);
-
	word-wrap: break-word;
-
	text-overflow: ellipsis;
-
	overflow-x: hidden;
-
	padding: 1rem;
+
  background-color: var(--color-negative-background);
+
  word-wrap: break-word;
+
  text-overflow: ellipsis;
+
  overflow-x: hidden;
+
  padding: 1rem;
}
-
.highlight, .secondary, .label {
-
	color: var(--color-secondary);
+
.highlight,
+
.secondary,
+
.label {
+
  color: var(--color-secondary);
}
.subtle {
-
	color: var(--color-foreground-faded);
-
	font-style: italic;
+
  color: var(--color-foreground-faded);
+
  font-style: italic;
}
.faded {
-
	color: var(--color-foreground-faded);
+
  color: var(--color-foreground-faded);
}
.bold {
-
	font-weight: bold !important;
+
  font-weight: bold !important;
}
.yellow {
-
	color: var(--color-yellow);
-
	background: var(--color-yellow-background-solid);
+
  color: var(--color-yellow);
+
  background: var(--color-yellow-background-solid);
}
.off-centered {
-
	height: 100%;
-
	padding-top: 5rem;
-
	padding-bottom: 24vh;
-
	display: flex;
-
	flex-direction: column;
-
	justify-content: center;
-
	align-items: center;
+
  height: 100%;
+
  padding-top: 5rem;
+
  padding-bottom: 24vh;
+
  display: flex;
+
  flex-direction: column;
+
  justify-content: center;
+
  align-items: center;
}

.font-mono {
  font-family: var(--font-family-monospace);
}

-
.mobile, .mobile-inline {
+
.mobile,
+
.mobile-inline {
  display: none !important;
}
.desktop {
@@ -373,75 +385,78 @@ label.input {
  .mobile-inline {
    display: inline !important;
  }
-
  .desktop, .desktop-inline {
+
  .desktop,
+
  .desktop-inline {
    display: none !important;
  }
}

span.align {
-
	display: flex;
-
	align-items: center;
-
	justify-content: flex-start;
+
  display: flex;
+
  align-items: center;
+
  justify-content: flex-start;
}
-
span.small, .text-xsmall {
-
	font-size: 0.75rem;
+
span.small,
+
.text-xsmall {
+
  font-size: 0.75rem;
}
.text-small {
-
	font-size: 0.875rem;
+
  font-size: 0.875rem;
}
.text-medium {
-
	font-size: 1.25rem;
+
  font-size: 1.25rem;
}
.text-big {
-
	font-size: 1.5rem;
+
  font-size: 1.5rem;
}
.text-truncate {
-
	white-space: nowrap;
-
	text-overflow: ellipsis;
-
	overflow-x: hidden;
+
  white-space: nowrap;
+
  text-overflow: ellipsis;
+
  overflow-x: hidden;
}
.text-faded {
-
	color: var(--color-foreground-faded);
+
  color: var(--color-foreground-faded);
}

button.error:hover {
-
	color: var(--color-background);
-
	background-color: var(--color-negative);
+
  color: var(--color-background);
+
  background-color: var(--color-negative);
}

table {
-
	table-layout: fixed;
-
	border-collapse: separate;
-
	border-spacing: 2rem 0;
+
  table-layout: fixed;
+
  border-collapse: separate;
+
  border-spacing: 2rem 0;
}
td {
-
	text-align: left;
-
	text-overflow: ellipsis;
+
  text-align: left;
+
  text-overflow: ellipsis;
}
td strong {
-
	font-weight: 600;
+
  font-weight: 600;
}

-
h1, .title {
-
	font-size: 1.75rem;
-
	font-weight: normal;
-
	color: var(--color-secondary);
-
	text-align: left;
-
	text-overflow: ellipsis;
-
	overflow-x: hidden;
+
h1,
+
.title {
+
  font-size: 1.75rem;
+
  font-weight: normal;
+
  color: var(--color-secondary);
+
  text-align: left;
+
  text-overflow: ellipsis;
+
  overflow-x: hidden;
}
h1 {
-
	margin-bottom: 2rem;
+
  margin-bottom: 2rem;
}

h2 {
-
	margin: 1.5rem 0;
+
  margin: 1.5rem 0;
}

h3 {
-
	margin: 1rem 0;
+
  margin: 1rem 0;
}

p {
-
	margin: 1rem 0;
+
  margin: 1rem 0;
}
modified public/typography.css
@@ -1,37 +1,37 @@
@font-face {
-
	font-family: "Inter";
-
	font-style: normal;
-
	font-weight: 400;
-
	font-display: swap;
-
	src: url("fonts/Inter-Regular.woff");
+
  font-family: "Inter";
+
  font-style: normal;
+
  font-weight: 400;
+
  font-display: swap;
+
  src: url("fonts/Inter-Regular.woff");
}

@font-face {
-
	font-family: "Inter";
-
	font-style: bold;
-
	font-weight: 600;
-
	font-display: swap;
-
	src: url("fonts/Inter-SemiBold.otf");
+
  font-family: "Inter";
+
  font-style: bold;
+
  font-weight: 600;
+
  font-display: swap;
+
  src: url("fonts/Inter-SemiBold.otf");
}

@font-face {
-
	font-family: "Inter";
-
	font-style: bolder;
-
	font-weight: 700;
-
	font-display: swap;
-
	src: url("fonts/Inter-Bold.otf");
+
  font-family: "Inter";
+
  font-style: bolder;
+
  font-weight: 700;
+
  font-display: swap;
+
  src: url("fonts/Inter-Bold.otf");
}

@font-face {
-
	font-family: "Source Code Pro";
-
	font-style: bold;
-
	font-weight: 600;
-
	src: url("fonts/SourceCodePro-Semibold.otf");
+
  font-family: "Source Code Pro";
+
  font-style: bold;
+
  font-weight: 600;
+
  src: url("fonts/SourceCodePro-Semibold.otf");
}

@font-face {
-
	font-family: "Source Code Pro";
-
	font-style: bold;
-
	font-weight: 700;
-
	src: url("fonts/SourceCodePro-Bold.otf");
+
  font-family: "Source Code Pro";
+
  font-style: bold;
+
  font-weight: 700;
+
  src: url("fonts/SourceCodePro-Bold.otf");
}
modified src/Address.svelte
@@ -1,12 +1,19 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { link } from 'svelte-routing';
-
  import { ethers } from 'ethers';
-
  import { safeLink, explorerLink, identifyAddress, formatAddress, AddressType, parseEnsLabel } from '@app/utils';
-
  import { Profile, ProfileType } from '@app/profile';
+
  import { onMount } from "svelte";
+
  import { link } from "svelte-routing";
+
  import { ethers } from "ethers";
+
  import {
+
    safeLink,
+
    explorerLink,
+
    identifyAddress,
+
    formatAddress,
+
    AddressType,
+
    parseEnsLabel,
+
  } from "@app/utils";
+
  import { Profile, ProfileType } from "@app/profile";
  import Avatar from "@app/Avatar.svelte";
  import Badge from "@app/Badge.svelte";
-
  import type { Config } from '@app/config';
+
  import type { Config } from "@app/config";

  export let address: string;
  export let config: Config;
@@ -25,18 +32,27 @@
  const nameOrAddress = profile?.ens?.name || address;

  onMount(async () => {
-
    if (! profile) {
-
      identifyAddress(address, config).then((t: AddressType) => addressType = t);
+
    if (!profile) {
+
      identifyAddress(address, config).then(
+
        (t: AddressType) => (addressType = t),
+
      );

      if (resolve) {
-
        Profile.get(address, ProfileType.Minimal, config).then(p => profile = p);
+
        Profile.get(address, ProfileType.Minimal, config).then(
+
          p => (profile = p),
+
        );
      }
    } else {
      // If there is a profile we can use the profile.type to avoid identifying it again.
      addressType = profile.type;
    }
  });
-
  $: addressLabel = resolve && profile?.name ? compact ? parseEnsLabel(profile.name, config) : profile.name : checksumAddress;
+
  $: addressLabel =
+
    resolve && profile?.name
+
      ? compact
+
        ? parseEnsLabel(profile.name, config)
+
        : profile.name
+
      : checksumAddress;
  $: checksumAddress = compact
    ? formatAddress(address)
    : ethers.utils.getAddress(address);
@@ -65,15 +81,17 @@
  }
</style>

-
<div class="address" title={address}
+
<div
+
  class="address"
+
  title={address}
  class:text-small={small}
  class:text-xsmall={xsmall}
  class:highlight>
  {#if !noAvatar}
    {#if resolve && profile?.avatar}
-
      <Avatar inline source={profile.avatar} title={address}/>
+
      <Avatar inline source={profile.avatar} title={address} />
    {:else}
-
      <Avatar inline source={address} title={address}/>
+
      <Avatar inline source={address} title={address} />
    {/if}
  {/if}
  <div class="wrapper">
@@ -94,7 +112,8 @@
      {/if}
    {:else if addressType === AddressType.EOA}
      <a use:link href={`/${nameOrAddress}`}>{addressLabel}</a>
-
    {:else} <!-- While we're waiting to find out what address type it is -->
+
    {:else}
+
      <!-- While we're waiting to find out what address type it is -->
      <a href={explorerLink(address, config)} target="_blank">{addressLabel}</a>
    {/if}
  </div>
modified src/App.svelte
@@ -1,21 +1,21 @@
<script lang="ts">
  import { Router, Route } from "svelte-routing";
-
  import { getConfig } from '@app/config';
-
  import { Connection, state, session } from '@app/session';
+
  import { getConfig } from "@app/config";
+
  import { Connection, state, session } from "@app/session";

-
  import Home from '@app/base/home/Index.svelte';
-
  import Vesting from '@app/base/vesting/Index.svelte';
-
  import Registrations from '@app/base/registrations/Routes.svelte';
-
  import Orgs from '@app/base/orgs/Routes.svelte';
-
  import Users from '@app/base/users/Routes.svelte';
-
  import Seeds from '@app/base/seeds/Routes.svelte';
-
  import Faucet from '@app/base/faucet/Routes.svelte';
-
  import Projects from '@app/base/projects/Routes.svelte';
-
  import Profile from '@app/Profile.svelte';
-
  import Resolver from '@app/base/resolver/Routes.svelte';
-
  import Header from '@app/Header.svelte';
-
  import Loading from '@app/Loading.svelte';
-
  import Modal from '@app/Modal.svelte';
+
  import Home from "@app/base/home/Index.svelte";
+
  import Vesting from "@app/base/vesting/Index.svelte";
+
  import Registrations from "@app/base/registrations/Routes.svelte";
+
  import Orgs from "@app/base/orgs/Routes.svelte";
+
  import Users from "@app/base/users/Routes.svelte";
+
  import Seeds from "@app/base/seeds/Routes.svelte";
+
  import Faucet from "@app/base/faucet/Routes.svelte";
+
  import Projects from "@app/base/projects/Routes.svelte";
+
  import Profile from "@app/Profile.svelte";
+
  import Resolver from "@app/base/resolver/Routes.svelte";
+
  import Header from "@app/Header.svelte";
+
  import Loading from "@app/Loading.svelte";
+
  import Modal from "@app/Modal.svelte";
  import LinearGradient from "@app/LinearGradient.svelte";

  const loadConfig = getConfig().then(async cfg => {
@@ -34,9 +34,10 @@
  });

  function handleKeydown(event: KeyboardEvent) {
-
    if (event.key === 'Enter') {
-
      const elems = document.querySelectorAll<HTMLElement>('button.primary');
-
      if (elems.length === 1) { // We only allow this when there's one primary button.
+
    if (event.key === "Enter") {
+
      const elems = document.querySelectorAll<HTMLElement>("button.primary");
+
      if (elems.length === 1) {
+
        // We only allow this when there's one primary button.
        elems[0].click();
      }
    }
@@ -48,11 +49,7 @@
    height: 100%;
    display: flex;
    flex-direction: column;
-
    background: linear-gradient(
-
      180deg,
-
      #181a38 0%,
-
      transparent 100%
-
    );
+
    background: linear-gradient(180deg, #181a38 0%, transparent 100%);
    background-repeat: no-repeat;
    background-size: 100% 6rem;
  }
@@ -68,7 +65,10 @@
<svelte:head>
  <title>Radicle</title>
  {#if import.meta.env.PROD}
-
    <script defer data-domain="app.radicle.xyz" src="https://plausible.io/js/plausible.js"></script>
+
    <script
+
      defer
+
      data-domain="app.radicle.xyz"
+
      src="https://plausible.io/js/plausible.js"></script>
  {/if}
</svelte:head>

modified src/Async.svelte
@@ -12,7 +12,8 @@
  <div class="commit">
    <div class="error error-message text-xsmall">
      <div>
-
        API request to <code class="text-xsmall">{err.url}</code> failed.
+
        API request to <code class="text-xsmall">{err.url}</code>
+
        failed.
      </div>
    </div>
  </div>
modified src/Authorship.svelte
@@ -15,7 +15,11 @@

  onMount(async () => {
    if (author.profile?.ens?.name) {
-
      profile = await Profile.get(author.profile.ens.name, ProfileType.Minimal, config);
+
      profile = await Profile.get(
+
        author.profile.ens.name,
+
        ProfileType.Minimal,
+
        config,
+
      );
    }
  });
</script>
@@ -42,7 +46,14 @@
<span class="authorship text-xsmall">
  {#if profile}
    <Address
-
      xsmall highlight resolve noBadge compact {noAvatar} {config} {profile}
+
      xsmall
+
      highlight
+
      resolve
+
      noBadge
+
      compact
+
      {noAvatar}
+
      {config}
+
      {profile}
      address={profile.address} />
  {:else if author.profile}
    <span class="highlight">
modified src/Avatar.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
-
  import { createIcon } from '@app/blockies';
-
  import { isAddress, isPeerId, isRadicleId } from '@app/utils';
+
  import { createIcon } from "@app/blockies";
+
  import { isAddress, isPeerId, isRadicleId } from "@app/utils";

  export let title: string;
  export let source: string;
@@ -56,5 +56,11 @@
</style>

<!-- svelte-ignore a11y-missing-attribute -->
-
<img {title} src={source} class="avatar" on:error={handleMissingFile}
-
  class:inline class:grayscale class:glowOnHover />
+
<img
+
  {title}
+
  src={source}
+
  class="avatar"
+
  on:error={handleMissingFile}
+
  class:inline
+
  class:grayscale
+
  class:glowOnHover />
modified src/BlockTimer.spec.ts
@@ -3,8 +3,7 @@ import { render } from "@testing-library/svelte";
import "@public/index.css";
import type { EventType, Listener } from "@ethersproject/abstract-provider";

-

-
describe('BlockTimer', () => {
+
describe("BlockTimer", () => {
  it("increases correctly the loading bar", () => {
    let block = 1;
    const props = {
@@ -14,33 +13,44 @@ describe('BlockTimer', () => {
            if (event === "block") {
              listener(block);
            }
-
          }
-
        }
+
          },
+
        },
      },
      startBlock: 1,
-
      duration: 3
+
      duration: 3,
    };

    const { rerender } = render(BlockTimer, props);

-
    cy.get("div.loader").should("have.attr", "style", "width: 0%;").then(() => {
-
      block += 1;
-
      rerender(props);
-
    });
+
    cy.get("div.loader")
+
      .should("have.attr", "style", "width: 0%;")
+
      .then(() => {
+
        block += 1;
+
        rerender(props);
+
      });

-
    cy.get("div.loader").last().should("have.attr", "style", "width: 33%;").then(() => {
-
      block += 1;
-
      rerender(props);
-
    });
+
    cy.get("div.loader")
+
      .last()
+
      .should("have.attr", "style", "width: 33%;")
+
      .then(() => {
+
        block += 1;
+
        rerender(props);
+
      });

-
    cy.get("div.loader").last().should("have.attr", "style", "width: 66%;").then(() => {
-
      block += 1;
-
      rerender(props);
-
    });
+
    cy.get("div.loader")
+
      .last()
+
      .should("have.attr", "style", "width: 66%;")
+
      .then(() => {
+
        block += 1;
+
        rerender(props);
+
      });

-
    cy.get("div.loader").last().should("have.attr", "style", "width: 99%;").then(() => {
-
      block += 1;
-
      rerender(props);
-
    });
+
    cy.get("div.loader")
+
      .last()
+
      .should("have.attr", "style", "width: 99%;")
+
      .then(() => {
+
        block += 1;
+
        rerender(props);
+
      });
  });
});
modified src/BlockTimer.svelte
@@ -29,5 +29,8 @@
</style>

<div class="parent">
-
  <div class="loader" style="width: {(currentBlock - startBlock) * Math.floor(100 / duration)}%" />
+
  <div
+
    class="loader"
+
    style="width: {(currentBlock - startBlock) *
+
      Math.floor(100 / duration)}%" />
</div>
modified src/Card.svelte
@@ -1,18 +1,21 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { Link } from 'svelte-routing';
-
  import Avatar from '@app/Avatar.svelte';
-
  import type { Profile } from '@app/profile';
-
  import type { Config } from '@app/config';
-
  import { formatName, formatAddress } from '@app/utils';
-
  import type { Seed } from '@app/base/seeds/Seed';
-
  import Loading from '@app/Loading.svelte';
+
  import { onMount } from "svelte";
+
  import { Link } from "svelte-routing";
+
  import Avatar from "@app/Avatar.svelte";
+
  import type { Profile } from "@app/profile";
+
  import type { Config } from "@app/config";
+
  import { formatName, formatAddress } from "@app/utils";
+
  import type { Seed } from "@app/base/seeds/Seed";
+
  import Loading from "@app/Loading.svelte";

-
  export let profile: Profile | {
-
    address: string;
-
    avatar?: string;
-
    name?: string;
-
  } | null = null;
+
  export let profile:
+
    | Profile
+
    | {
+
        address: string;
+
        avatar?: string;
+
        name?: string;
+
      }
+
    | null = null;
  export let seed: Seed | null = null;
  export let config: Config;
  export let path: string;
@@ -43,7 +46,7 @@
  .card::last-child {
    margin-right: 0;
  }
-
  .card:hover  {
+
  .card:hover {
    background: var(--color-foreground-background-lighter);
  }
  .card-avatar {
@@ -88,7 +91,9 @@
  <div class="card" class:seed={!!seed}>
    <div class="card-avatar">
      {#if profile}
-
        <Avatar source={profile.avatar ?? profile.address} title={profile.address} />
+
        <Avatar
+
          source={profile.avatar ?? profile.address}
+
          title={profile.address} />
      {:else if seed}
        <span class="seed-emoji">
          {seed.emoji}
modified src/Cards.svelte
@@ -1,10 +1,10 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { Profile, ProfileType } from '@app/profile';
-
  import Card from '@app/Card.svelte';
-
  import type { Org } from '@app/base/orgs/Org';
-
  import type { Config } from '@app/config';
-
  import type { Seed } from '@app/base/seeds/Seed';
+
  import { onMount } from "svelte";
+
  import { Profile, ProfileType } from "@app/profile";
+
  import Card from "@app/Card.svelte";
+
  import type { Org } from "@app/base/orgs/Org";
+
  import type { Config } from "@app/config";
+
  import type { Seed } from "@app/base/seeds/Seed";

  export let config: Config;
  export let orgs: Org[] = [];
@@ -37,28 +37,35 @@
  }
</style>

-
  <div class="list">
-
    {#each orgs as org}
-
      {#await Profile.get(org.name ?? org.address, ProfileType.Minimal, config)}
-
        <Card profile={{ address: org.address }} {config} path={`/${org.address}`} />
-
      {:then profile}
-
        {#if orgMembers[profile.address]?.length}
-
          <Card {profile} {config} path={`/${profile.nameOrAddress}`} members={orgMembers[profile.address]} />
-
        {:else}
-
          <Card {profile} {config} path={`/${profile.nameOrAddress}`} />
-
        {/if}
-
      {/await}
-
    {/each}
+
<div class="list">
+
  {#each orgs as org}
+
    {#await Profile.get(org.name ?? org.address, ProfileType.Minimal, config)}
+
      <Card
+
        profile={{ address: org.address }}
+
        {config}
+
        path={`/${org.address}`} />
+
    {:then profile}
+
      {#if orgMembers[profile.address]?.length}
+
        <Card
+
          {profile}
+
          {config}
+
          path={`/${profile.nameOrAddress}`}
+
          members={orgMembers[profile.address]} />
+
      {:else}
+
        <Card {profile} {config} path={`/${profile.nameOrAddress}`} />
+
      {/if}
+
    {/await}
+
  {/each}

-
    {#each profiles as profile}
-
      <Card {profile} {config} path={`/${profile.nameOrAddress}`} />
-
    {/each}
+
  {#each profiles as profile}
+
    <Card {profile} {config} path={`/${profile.nameOrAddress}`} />
+
  {/each}

-
    {#each seeds as seed}
-
      <Card {seed} {config} path={`/seeds/${seed.host}`} />
-
    {/each}
+
  {#each seeds as seed}
+
    <Card {seed} {config} path={`/seeds/${seed.host}`} />
+
  {/each}

-
    {#if !orgs.length && !profiles.length && !seeds.length}
-
      <slot />
-
    {/if}
-
  </div>
+
  {#if !orgs.length && !profiles.length && !seeds.length}
+
    <slot />
+
  {/if}
+
</div>
modified src/Clipboard.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
-
  import Icon from '@app/Icon.svelte';
-
  import { toClipboard } from '@app/utils';
+
  import Icon from "@app/Icon.svelte";
+
  import { toClipboard } from "@app/utils";
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();
modified src/Comment.svelte
@@ -20,15 +20,18 @@

  onMount(async () => {
    if (comment.author.profile?.ens?.name) {
-
      profile = await Profile.get(comment.author.profile.ens.name, ProfileType.Minimal, config);
+
      profile = await Profile.get(
+
        comment.author.profile.ens.name,
+
        ProfileType.Minimal,
+
        config,
+
      );
    }
  });

  $: source = profile?.avatar || comment.author.urn;
-
  $: title = profile?.name ||
-
    (comment.author.profile
-
      ? comment.author.profile.name
-
      : comment.author.urn);
+
  $: title =
+
    profile?.name ||
+
    (comment.author.profile ? comment.author.profile.name : comment.author.urn);

  const selectReaction = (event: { detail: string }) => {
    // TODO: Once we allow adding reactions through the http-api, we should call it here.
@@ -78,7 +81,11 @@
  </div>
  <div class="card">
    <div class="card-header">
-
      <Authorship noAvatar {config} {caption} {profile}
+
      <Authorship
+
        noAvatar
+
        {config}
+
        {caption}
+
        {profile}
        author={comment.author}
        timestamp={comment.timestamp} />
      <ReactionSelector on:select={selectReaction} />
modified src/Connect.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
  import { get } from "svelte/store";
  import { Connection, state } from "@app/session";
-
  import type { Err } from '@app/error';
-
  import Error from '@app/Error.svelte';
-
  import type { Config } from '@app/config';
+
  import type { Err } from "@app/error";
+
  import Error from "@app/Error.svelte";
+
  import type { Config } from "@app/config";
  import ConnectWallet from "@app/components/Modal/ConnectWallet.svelte";

  export let caption = "Connect";
@@ -39,8 +39,7 @@
  {style}
  class="connect {className}"
  disabled={connecting}
-
  data-waiting={connecting || null}
-
>
+
  data-waiting={connecting || null}>
  {#if connecting}
    Connecting...
  {:else}
@@ -49,8 +48,15 @@
</button>

{#if $walletConnectState.state === "open"}
-
  <ConnectWallet {config} uri={$walletConnectState.uri} on:close={onModalClose} />
+
  <ConnectWallet
+
    {config}
+
    uri={$walletConnectState.uri}
+
    on:close={onModalClose} />
{:else if error}
-
  <Error floating emoji="👛" title="Connection failed" {error} on:close={() => error = null} />
+
  <Error
+
    floating
+
    emoji="👛"
+
    title="Connection failed"
+
    {error}
+
    on:close={() => (error = null)} />
{/if}
-

modified src/Diagram.svelte
@@ -39,32 +39,43 @@
  function createPath() {
    let i = 1;

-
    if (commitCountArray.length < 52) commitCountArray.push(...new Array(52 - commitCountArray.length).fill(0));
+
    if (commitCountArray.length < 52) {
+
      commitCountArray.push(...new Array(52 - commitCountArray.length).fill(0));
+
    }

    const maxValue = Math.max(...commitCountArray);
    const minValue = Math.min(...commitCountArray);

    // Normalizes the values to the viewBox dimensions
-
    const normalizedArray =
-
      commitCountArray.map(c => {
-
        // If we are not crossing the `viewBoxHeight` we want to return the actual value,
-
        // and don't want to normalize <`minimalHeight` commit counts as huge spikes.
-
        if (maxValue < viewBoxHeight && c >= minimalHeight) { return c; }
-
        // If the value is 0..minimalHeight though we don't want to set it to the minimalHeight.
-
        else if (c > 0 && c < minimalHeight) { return minimalHeight; }
-
        // If the count is 0 we have to make sure the normalization is not being run since it would return NaN
-
        else { return c === 0 ? 0 : (viewBoxHeight - 0) * (c - minValue) / (maxValue - minValue); }
-
      });
-

-
    const path = normalizedArray
-
      .slice(1)
-
      .reduce((acc, curr) => {
-
        const s = `${viewBoxWidth - widthIteration * i},${viewBoxHeight - curr}`;
+
    const normalizedArray = commitCountArray.map(c => {
+
      // If we are not crossing the `viewBoxHeight` we want to return the actual value,
+
      // and don't want to normalize <`minimalHeight` commit counts as huge spikes.
+
      if (maxValue < viewBoxHeight && c >= minimalHeight) {
+
        return c;
+
      }
+
      // If the value is 0..minimalHeight though we don't want to set it to the minimalHeight.
+
      else if (c > 0 && c < minimalHeight) {
+
        return minimalHeight;
+
      }
+
      // If the count is 0 we have to make sure the normalization is not being run since it would return NaN
+
      else {
+
        return c === 0
+
          ? 0
+
          : ((viewBoxHeight - 0) * (c - minValue)) / (maxValue - minValue);
+
      }
+
    });
+

+
    const path = normalizedArray.slice(1).reduce(
+
      (acc, curr) => {
+
        const s = `${viewBoxWidth - widthIteration * i},${
+
          viewBoxHeight - curr
+
        }`;
        lastWidthPoint = viewBoxWidth - widthIteration * i;
        i += 1;
        return acc.concat(s);
      },
-
      [`M${viewBoxWidth},${viewBoxHeight - normalizedArray[0]}`]);
+
      [`M${viewBoxWidth},${viewBoxHeight - normalizedArray[0]}`],
+
    );
    return path.join();
  }

@@ -72,11 +83,15 @@
    // Creates the stroke path with the array of points
    path = createPath();
    // Concats a path closing for it to be the area under the stroke
-
    areaPath = path.concat(`L${lastWidthPoint},${viewBoxHeight}L${viewBoxWidth},${viewBoxHeight}Z`);
+
    areaPath = path.concat(
+
      `L${lastWidthPoint},${viewBoxHeight}L${viewBoxWidth},${viewBoxHeight}Z`,
+
    );
  });
</script>

-
<svg viewBox="0 0 {viewBoxWidth} {heightWithPadding}" xmlns="http://www.w3.org/2000/svg">
+
<svg
+
  viewBox="0 0 {viewBoxWidth} {heightWithPadding}"
+
  xmlns="http://www.w3.org/2000/svg">
  <svg style="height: 0; width: 0;" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <linearGradient id="fillGradient" x1="0" y1="1" x2="0" y2="0">
@@ -87,11 +102,22 @@
  </svg>
  {#if points.length > 0}
    <g>
-
      <path fill="transparent" stroke="url(#gradient)" stroke-width={strokeWidth} stroke-linejoin="round" d={path} />
+
      <path
+
        fill="transparent"
+
        stroke="url(#gradient)"
+
        stroke-width={strokeWidth}
+
        stroke-linejoin="round"
+
        d={path} />
      <path fill="url(#fillGradient)" stroke="transparent" d={areaPath} />
    </g>
  {:else}
    <!-- If no commits have been made in a year, we show a straight line -->
-
    <line x1="0" y1={viewBoxHeight} x2="600" y2={viewBoxHeight} stroke="#ff55ff" stroke-width={1} />
+
    <line
+
      x1="0"
+
      y1={viewBoxHeight}
+
      x2="600"
+
      y2={viewBoxHeight}
+
      stroke="#ff55ff"
+
      stroke-width={1} />
  {/if}
</svg>
modified src/Dropdown.svelte
@@ -2,7 +2,12 @@
  import { createEventDispatcher } from "svelte";
  import Badge from "@app/Badge.svelte";

-
  export let items: { key: string; title: string; value: string; badge: string | null }[];
+
  export let items: {
+
    key: string;
+
    title: string;
+
    value: string;
+
    badge: string | null;
+
  }[];
  export let selected: string | null = null;

  const dispatch = createEventDispatcher();
@@ -30,7 +35,8 @@
    align-items: center;
    gap: 0.5rem;
  }
-
  .dropdown-item:hover, .selected {
+
  .dropdown-item:hover,
+
  .selected {
    background-color: var(--color-foreground-background-lighter);
  }
  @media (max-width: 720px) {
@@ -42,9 +48,14 @@
</style>

<div class="dropdown">
-
  {#each items as {key, value, badge, title}}
+
  {#each items as { key, value, badge, title }}
    {#if key && value}
-
      <div class="dropdown-item" class:selected={value === selected} on:click={() => onSelect(value)} {title}>{@html key}
+
      <div
+
        class="dropdown-item"
+
        class:selected={value === selected}
+
        on:click={() => onSelect(value)}
+
        {title}>
+
        {@html key}
        {#if badge}
          <Badge variant="primary">{badge}</Badge>
        {/if}
modified src/Error.spec.ts
@@ -1,18 +1,20 @@
import Error from "./Error.svelte";
import { render } from "@testing-library/svelte";
-
import { Failure } from '@app/error';
+
import { Failure } from "@app/error";
import "@public/index.css";

-
describe('Error', () => {
+
describe("Error", () => {
  it("should show passed in props", () => {
-
    render(Error, { props: {
-
      subtitle: "Subtitle of Modal",
-
      error: {
-
        type: Failure.InsufficientBalance,
-
        txHash: "0x8b678e51f970c5307bf45a8bcea373b597f9acbcea5c5ba784a1d383361a89d1",
-
        message: "Not enough RAD"
-
      }
-
    }
+
    render(Error, {
+
      props: {
+
        subtitle: "Subtitle of Modal",
+
        error: {
+
          type: Failure.InsufficientBalance,
+
          txHash:
+
            "0x8b678e51f970c5307bf45a8bcea373b597f9acbcea5c5ba784a1d383361a89d1",
+
          message: "Not enough RAD",
+
        },
+
      },
    });
    cy.findByText("Error");
    cy.findByText("Subtitle of Modal");
@@ -21,20 +23,24 @@ describe('Error', () => {
  });

  it("should show custom error message", () => {
-
    render(Error, { props: {
-
      subtitle: "Subtitle of Modal",
-
      message: "Error message to check for",
-
    } });
+
    render(Error, {
+
      props: {
+
        subtitle: "Subtitle of Modal",
+
        message: "Error message to check for",
+
      },
+
    });
    cy.findByText("Error message to check for");
  });

  it("should change button label to Close when floating", () => {
-
    render(Error, { props: {
-
      title: "Title of Modal",
-
      subtitle: "Subtitle of Modal",
-
      message: "Error message to check for",
-
      floating: true
-
    } });
+
    render(Error, {
+
      props: {
+
        title: "Title of Modal",
+
        subtitle: "Subtitle of Modal",
+
        message: "Error message to check for",
+
        floating: true,
+
      },
+
    });
    cy.findByText("Close");
  });
});
modified src/Error.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
-
  import { createEventDispatcher } from 'svelte';
-
  import Modal from '@app/Modal.svelte';
-
  import type { Err } from '@app/error';
+
  import { createEventDispatcher } from "svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import type { Err } from "@app/error";

  const dispatch = createEventDispatcher();

@@ -31,13 +31,14 @@

  <span slot="body">
    <slot>
-
      <strong>Error:</strong> {body}
+
      <strong>Error:</strong>
+
      {body}
    </slot>
  </span>

  <span slot="actions">
    <slot name="actions">
-
      <button on:click={() => dispatch('close')}>
+
      <button on:click={() => dispatch("close")}>
        {action}
      </button>
    </slot>
modified src/Floating.svelte
@@ -14,13 +14,13 @@
  let thisComponent: HTMLDivElement;

  function clickOutside(ev: MouseEvent) {
-
    if (! $focused?.contains(ev.target as HTMLDivElement)) {
+
    if (!$focused?.contains(ev.target as HTMLDivElement)) {
      closeFocused();
    }
  }

  function toggle() {
-
    if (! disabled) {
+
    if (!disabled) {
      expanded = !expanded;
      if ($focused === thisComponent) {
        closeFocused();
modified src/Form.svelte
@@ -14,13 +14,13 @@
  }

  const validationExamples: Record<string, string> = {
-
    "URL": "https://acme.xyz/",
-
    "URN": "eip155:1:0xd1bb21bd5a432d2919c82bcefe1bc7f8cc9207d9",
-
    "handle": "acme",
-
    "id": "hydkkcf6k9be5fuszdhpqbctu3q3fuwagj874wx2puia8ti8coygh1",
-
    "identity": "rad:git:hnrkqdpm9ub19oc8dccx44echy75hzfsezyio",
-
    "domain": "seed.acme.xyz",
-
    "address": "0x17a8c096733BD5F87aD43D7A2A4d1C42ab8A2A70",
+
    URL: "https://acme.xyz/",
+
    URN: "eip155:1:0xd1bb21bd5a432d2919c82bcefe1bc7f8cc9207d9",
+
    handle: "acme",
+
    id: "hydkkcf6k9be5fuszdhpqbctu3q3fuwagj874wx2puia8ti8coygh1",
+
    identity: "rad:git:hnrkqdpm9ub19oc8dccx44echy75hzfsezyio",
+
    domain: "seed.acme.xyz",
+
    address: "0x17a8c096733BD5F87aD43D7A2A4d1C42ab8A2A70",
  };

  const validationTypes: { [index: string]: RegExp } = {
@@ -45,11 +45,11 @@

<script lang="ts">
  import { link } from "svelte-routing";
-
  import { createEventDispatcher } from 'svelte';
-
  import { marked } from 'marked';
-
  import { capitalize, isUrl, isAddress, formatSeedId } from '@app/utils';
-
  import Address from '@app/Address.svelte';
-
  import type { Config } from '@app/config';
+
  import { createEventDispatcher } from "svelte";
+
  import { marked } from "marked";
+
  import { capitalize, isUrl, isAddress, formatSeedId } from "@app/utils";
+
  import Address from "@app/Address.svelte";
+
  import type { Config } from "@app/config";

  export let fields: Field[];
  export let editable = false;
@@ -65,12 +65,14 @@

    formFields = formFields.map(field => {
      if (field.name === name && field.validate) {
-
        hasErrors = value.length > 0 ? !validationTypes[field.validate].test(value) : false;
-
        return { ...field,
+
        hasErrors =
+
          value.length > 0
+
            ? !validationTypes[field.validate].test(value)
+
            : false;
+
        return {
+
          ...field,
          value: value,
-
          error: hasErrors
-
            ? `Must be a valid ${field.validate}`
-
            : undefined,
+
          error: hasErrors ? `Must be a valid ${field.validate}` : undefined,
          example: validationExamples[field.validate],
        };
      }
@@ -79,20 +81,22 @@
  };

  const cleanup = (fields: Field[]): { name: string; value?: string }[] => {
-
    return fields.filter(field => field.editable).map(field => {
-
      return {
-
        name: field.name,
-
        // We only allow to have a trueish value or an empty string.
-
        value: field.value ? field.value.trim() : "",
-
      };
-
    });
+
    return fields
+
      .filter(field => field.editable)
+
      .map(field => {
+
        return {
+
          name: field.name,
+
          // We only allow to have a trueish value or an empty string.
+
          value: field.value ? field.value.trim() : "",
+
        };
+
      });
  };
  const dispatch = createEventDispatcher();
-
  const save = () => dispatch('save', cleanup(formFields));
-
  const validate = (event: Event) => dispatch('validate', check(event));
+
  const save = () => dispatch("save", cleanup(formFields));
+
  const validate = (event: Event) => dispatch("validate", check(event));
  const cancel = () => {
    formFields = fields;
-
    dispatch('cancel');
+
    dispatch("cancel");
  };
</script>

@@ -184,28 +188,43 @@
    </div>
    <div>
      {#if field.editable && editable}
-
        <input name={field.name} class="field" placeholder={field.placeholder}
-
          on:change={validate} on:input={() => field.error = null}
-
          value={field.value || ""} type="text" {disabled} />
+
        <input
+
          name={field.name}
+
          class="field"
+
          placeholder={field.placeholder}
+
          on:change={validate}
+
          on:input={() => (field.error = null)}
+
          value={field.value || ""}
+
          type="text"
+
          {disabled} />
      {:else}
        <span class="field">
          {#if field.value}
            {#if isUrl(field.value)}
              <span class="ellipsis">
-
                <a class="link" href="{field.value}" target="_blank">{field.value}</a>
+
                <a class="link" href={field.value} target="_blank">
+
                  {field.value}
+
                </a>
              </span>
            {:else if isAddress(field.value)}
              <div class="desktop-inline">
-
                <Address resolve={field.resolve ?? false} address={field.value} {config} />
+
                <Address
+
                  resolve={field.resolve ?? false}
+
                  address={field.value}
+
                  {config} />
              </div>
              <div class="mobile-inline">
-
                <Address compact resolve={field.resolve ?? false} address={field.value} {config} />
+
                <Address
+
                  compact
+
                  resolve={field.resolve ?? false}
+
                  address={field.value}
+
                  {config} />
              </div>
-
            {:else if (field.url)}
+
            {:else if field.url}
              <div>
-
                <a class="link" use:link href="{field.url}">{field.value}</a>
+
                <a class="link" use:link href={field.url}>{field.value}</a>
              </div>
-
            {:else if (field.validate === "id")}
+
            {:else if field.validate === "id"}
              <div class="mobile">
                {formatSeedId(field.value)}
              </div>
@@ -223,7 +242,8 @@
      {#if field.error}
        <div class="description invalid text-small faded">
          {#if field.example}
-
            {field.error}, eg. <em>{field.example}</em>
+
            {field.error}, eg.
+
            <em>{field.example}</em>
          {:else}
            {field.error}
          {/if}
@@ -238,10 +258,11 @@
</div>

<div class="actions" class:editable>
-
  <button on:click={cancel} {disabled} class="regular">
-
    Cancel
-
  </button>
-
  <button on:click={save} disabled={hasErrors || disabled} class="regular primary">
+
  <button on:click={cancel} {disabled} class="regular">Cancel</button>
+
  <button
+
    on:click={save}
+
    disabled={hasErrors || disabled}
+
    class="regular primary">
    Save
  </button>
</div>
modified src/Header.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
  import { link } from "svelte-routing";
  import { formatAddress, formatBalance } from "@app/utils";
-
  import { error, Failure } from '@app/error';
+
  import { error, Failure } from "@app/error";
  import { disconnectWallet } from "@app/session";
-
  import type { Session } from '@app/session';
-
  import Loading from '@app/Loading.svelte';
-
  import Logo from '@app/Logo.svelte';
-
  import Connect from '@app/Connect.svelte';
-
  import type { Config } from '@app/config';
+
  import type { Session } from "@app/session";
+
  import Loading from "@app/Loading.svelte";
+
  import Logo from "@app/Logo.svelte";
+
  import Connect from "@app/Connect.svelte";
+
  import type { Config } from "@app/config";
  import { Profile, ProfileType } from "@app/profile";
-
  import Avatar from '@app/Avatar.svelte';
-
  import Search from '@app/Search.svelte';
+
  import Avatar from "@app/Avatar.svelte";
+
  import Search from "@app/Search.svelte";
  import Floating from "@app/Floating.svelte";
  import Icon from "./Icon.svelte";
  import MobileNavbar from "./MobileNavbar.svelte";
@@ -37,12 +37,13 @@
    align-items: center;
    margin: 0;
    padding: 1.5rem;
-
	height: 5.5rem;
+
    height: 5.5rem;
  }
-
  header .left, header .right {
+
  header .left,
+
  header .right {
    display: flex;
    align-items: center;
-
	height: var(--button-regular-height);
+
    height: var(--button-regular-height);
  }
  header .nav {
    display: inline-block;
@@ -68,8 +69,8 @@

  .logo {
    display: flex;
-
	height: var(--button-regular-height);
-
	align-items: center;
+
    height: var(--button-regular-height);
+
    align-items: center;
  }
  .error {
    text-align: center;
@@ -131,8 +132,11 @@
  div.toggle {
    display: none;
  }
-
  @media(max-width: 720px) {
-
    .network, .search, header .nav, .balance {
+
  @media (max-width: 720px) {
+
    .network,
+
    .search,
+
    header .nav,
+
    .balance {
      display: none;
    }
    div.toggle {
@@ -152,9 +156,13 @@
  {#if $error.type === Failure.TransactionFailed}
    <div class="error">
      {#if $error.message}
-
        <strong>Error:</strong> {$error.message}
+
        <strong>Error:</strong>
+
        {$error.message}
      {:else if $error.txHash}
-
        <strong>Error:</strong> Transaction <a href="https://etherscan.io/tx/{$error.txHash}">{$error.txHash}</a> failed.
+
        <strong>Error:</strong>
+
        Transaction
+
        <a href="https://etherscan.io/tx/{$error.txHash}">{$error.txHash}</a>
+
        failed.
      {/if}
    </div>
  {/if}
@@ -172,9 +180,7 @@
      {#if session && Object.keys(session.siwe).length > 0}
        <span class="seeds-container">
          <Floating>
-
            <span slot="toggle" class="nav-link">
-
              Seeds
-
            </span>
+
            <span slot="toggle" class="nav-link">Seeds</span>
            <svelte:fragment slot="modal">
              <SeedDropdown seeds={session.siwe} {config} />
            </svelte:fragment>
@@ -185,9 +191,9 @@
  </div>

  <div class="right">
-
    {#if config && config.network.name === 'rinkeby'}
+
    {#if config && config.network.name === "rinkeby"}
      <span class="network">Rinkeby</span>
-
    {:else if config && config.network.name === 'homestead'}
+
    {:else if config && config.network.name === "homestead"}
      <!-- Don't show anything -->
    {:else}
      <span class="network unavailable">No Network</span>
@@ -196,26 +202,30 @@
    {#if address}
      <span class="balance">
        {#if tokenBalance}
-
          {formatBalance(tokenBalance)} <strong>RAD</strong>
+
          {formatBalance(tokenBalance)}
+
          <strong>RAD</strong>
        {:else}
          <Loading small />
        {/if}
      </span>

-
      <button class="address outline regular"
+
      <button
+
        class="address outline regular"
        on:click={() => disconnectWallet(config)}
-
        on:mouseover={() => sessionButtonHover = true}
-
        on:focus={() => sessionButtonHover = true}
-
        on:mouseout={() => sessionButtonHover = false}
-
        on:blur={() => sessionButtonHover = false}
-
      >
+
        on:mouseover={() => (sessionButtonHover = true)}
+
        on:focus={() => (sessionButtonHover = true)}
+
        on:mouseout={() => (sessionButtonHover = false)}
+
        on:blur={() => (sessionButtonHover = false)}>
        {#await Profile.get(address, ProfileType.Minimal, config)}
          <Loading small center />
        {:then profile}
          {#if sessionButtonHover}
            Disconnect
          {:else}
-
            <Avatar source={profile.avatar ?? address} title={address} inline />{formatAddress(address)}
+
            <Avatar
+
              source={profile.avatar ?? address}
+
              title={address}
+
              inline />{formatAddress(address)}
          {/if}
        {/await}
      </button>
modified src/Icon.svelte
@@ -27,7 +27,8 @@
  fill="currentColor"
  viewBox="0 0 24 24">
  {#if name === "browse"}
-
    <path d="M8.46934 7.23871C8.61151 7.10623 8.79956 7.03411 8.99386
+
    <path
+
      d="M8.46934 7.23871C8.61151 7.10623 8.79956 7.03411 8.99386
    7.03753C9.18816 7.04096 9.37355 7.11967 9.51096 7.25709C9.64838 7.3945
    9.72709 7.57988 9.73052 7.77419C9.73394 7.96849 9.66182 8.15653 9.52934
    8.29871L5.80934 12.0187L9.52934 15.7387C9.60303 15.8074 9.66213 15.8902
@@ -52,17 +53,25 @@
    12.5487C19.4198 12.4081 19.4987 12.2175 19.4987 12.0187C19.4987 11.82
    19.4198 11.6293 19.2793 11.4887L15.0293 7.23871V7.23871Z" />
  {:else if name === "clipboard"}
-
    <path d="M9 5H14.7071L18 8.29289V17H9V5ZM10 6V16H17V9H14V6H10ZM15
+
    <path
+
      d="M9 5H14.7071L18 8.29289V17H9V5ZM10 6V16H17V9H14V6H10ZM15
    6.70711L16.2929 8H15V6.70711ZM7 8H8V18H15V19H7V8Z" />
  {:else if name === "clipboard-small"}
-
    <path fill-rule="evenodd" clip-rule="evenodd" d="M14 7L17 10V16H10V7H14ZM13
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M14 7L17 10V16H10V7H14ZM13
    8H11V15H16V11H13V8ZM14 8.41421V10H15.5858L14 8.41421ZM8
    10H9V17H14V18H8V10Z" />
  {:else if name === "ellipsis"}
-
    <path d="M7 12a2 2 0 1 1-4.001-.001A2 2 0 0 1 7 12zm12-2a2 2 0 1 0 .001
-
    4.001A2 2 0 0 0 19 10zm-7 0a2 2 0 1 0 .001 4.001A2 2 0 0 0 12 10z"/>
+
    <path
+
      d="M7 12a2 2 0 1 1-4.001-.001A2 2 0 0 1 7 12zm12-2a2 2 0 1 0 .001
+
    4.001A2 2 0 0 0 19 10zm-7 0a2 2 0 1 0 .001 4.001A2 2 0 0 0 12 10z" />
  {:else if name === "fork"}
-
    <path fill-rule="evenodd" clip-rule="evenodd" d="M8.34375 6.9375C7.5671
+
    <path
+
      fill-rule="evenodd"
+
      clip-rule="evenodd"
+
      d="M8.34375 6.9375C7.5671
    6.9375 6.9375 7.5671 6.9375 8.34375C6.9375 9.1204 7.5671 9.75 8.34375
    9.75C9.1204 9.75 9.75 9.1204 9.75 8.34375C9.75 7.5671 9.1204 6.9375 8.34375
    6.9375ZM8.8125 10.6406C9.8823 10.4235 10.6875 9.47764 10.6875
@@ -96,7 +105,8 @@
    20.0625 13.5 19.4329 13.5 18.6562C13.5 17.8796 12.8704 17.25 12.0938
    17.25Z" />
  {:else if name === "github"}
-
    <path d="M12 4C7.58 4 4 7.67295 4 12.2031C4 15.8282 6.292 18.9023 9.47
+
    <path
+
      d="M12 4C7.58 4 4 7.67295 4 12.2031C4 15.8282 6.292 18.9023 9.47
    19.9858C9.87 20.0631 10.0167 19.8095 10.0167 19.5914C10.0167 19.3966 10.01
    18.8805 10.0067 18.1969C7.78133 18.6918 7.312 17.0963 7.312 17.0963C6.948
    16.1495 6.422 15.8966 6.422 15.8966C5.69733 15.388 6.478 15.3982 6.478
@@ -113,7 +123,8 @@
    19.5852C13.9593 19.8006 14.0993 20.0569 14.5093 19.9749C17.71 18.8989 20
    15.8227 20 12.2031C20 7.67295 16.418 4 12 4" />
  {:else if name === "url"}
-
    <path d="M18.7566 11.2493L15.7531 14.2518C14.0953 15.9107 11.4059 15.9107
+
    <path
+
      d="M18.7566 11.2493L15.7531 14.2518C14.0953 15.9107 11.4059 15.9107
    9.74803 14.2518C9.48676 13.9916 9.28252 13.6982 9.10313 13.3954L10.4987
    11.9999C10.565 11.933 10.6469 11.8947 10.7252 11.8496C10.8216 12.1793
    10.9901 12.4914 11.2493 12.7505C12.0772 13.5789 13.4245 13.5779 14.2518
@@ -129,9 +140,10 @@
    14.2518 9.74809C12.594 8.08978 9.90462 8.08978 8.24627 9.74809L5.24374
    12.7506C3.58542 14.4094 3.58542 17.0978 5.24374 18.7566C6.90207 20.4145
    9.59097 20.4145 11.2493 18.7566L13.5251 16.4809C12.6158 16.6146 11.6822
-
    16.5234 10.8164 16.1865Z"/>
+
    16.5234 10.8164 16.1865Z" />
  {:else if name === "twitter"}
-
    <path d="M19.9687 7.54849C19.3697 7.81214 18.7351 7.98617 18.0853
+
    <path
+
      d="M19.9687 7.54849C19.3697 7.81214 18.7351 7.98617 18.0853
    8.06498C18.7694 7.65395 19.2816 7.00936 19.5273 6.25025C18.8933 6.62013
    18.1907 6.88937 17.4427 7.03932C16.9492 6.51179 16.2952 6.1619 15.5824
    6.04399C14.8696 5.92608 14.1378 6.04675 13.5006 6.38727C12.8635 6.72778
@@ -145,7 +157,7 @@
    8.848 16.1696C7.68769 17.0799 6.25498 17.574 4.78 17.5725C4.52 17.5725
    4.26067 17.5571 4 17.5278C5.50381 18.4904 7.25234 19.0013 9.038 19C15.0733
    19 18.37 14.0043 18.37 9.67978C18.37 9.53982 18.37 9.39987 18.36
-
    9.25992C19.004 8.79665 19.5595 8.22147 20 7.56182L19.9687 7.54849Z"/>
+
    9.25992C19.004 8.79665 19.5595 8.22147 20 7.56182L19.9687 7.54849Z" />
  {:else}
    {unreachable(name)}
  {/if}
modified src/Input.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
-
  import Clipboard from '@app/Clipboard.svelte';
-
  import { closeFocused } from '@app/Floating.svelte';
+
  import Clipboard from "@app/Clipboard.svelte";
+
  import { closeFocused } from "@app/Floating.svelte";

  export let name: string;
  export let value: string;
modified src/LinearGradient.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
  export let id: string;
-
  export let fillColor="#ff55ff";
+
  export let fillColor = "#ff55ff";
</script>

<svg style="height: 0; width: 0;" xmlns="http://www.w3.org/2000/svg">
modified src/Link.svelte
@@ -6,12 +6,12 @@

  function getProps(/* props: any */) {
    if (primary) {
-
      return { "class": "primary link" };
+
      return { class: "primary link" };
    }
-
    return { "class": "link" };
+
    return { class: "link" };
  }
</script>

-
<Link to="{to}" getProps="{getProps}">
+
<Link {to} {getProps}>
  <slot />
</Link>
modified src/Loading.svelte
@@ -66,32 +66,48 @@
  }

  @-webkit-keyframes sk-bouncedelay-condensed {
-
    0%, 100% { -webkit-transform: scale(0.2) }
-
    50% { -webkit-transform: scale(1.0) }
+
    0%,
+
    100% {
+
      -webkit-transform: scale(0.2);
+
    }
+
    50% {
+
      -webkit-transform: scale(1);
+
    }
  }

  @keyframes sk-bouncedelay-condensed {
-
    0%, 100% {
+
    0%,
+
    100% {
      -webkit-transform: scale(0.2);
      transform: scale(0.2);
-
    } 50% {
-
      -webkit-transform: scale(1.0);
-
      transform: scale(1.0);
+
    }
+
    50% {
+
      -webkit-transform: scale(1);
+
      transform: scale(1);
    }
  }

  @-webkit-keyframes sk-bouncedelay {
-
    0%, 80%, 100% { -webkit-transform: scale(0) }
-
    40% { -webkit-transform: scale(1.0) }
+
    0%,
+
    80%,
+
    100% {
+
      -webkit-transform: scale(0);
+
    }
+
    40% {
+
      -webkit-transform: scale(1);
+
    }
  }

  @keyframes sk-bouncedelay {
-
    0%, 80%, 100% {
+
    0%,
+
    80%,
+
    100% {
      -webkit-transform: scale(0);
      transform: scale(0);
-
    } 40% {
-
      -webkit-transform: scale(1.0);
-
      transform: scale(1.0);
+
    }
+
    40% {
+
      -webkit-transform: scale(1);
+
      transform: scale(1);
    }
  }

@@ -103,21 +119,35 @@
  }

  @keyframes fadeIn {
-
    0% { opacity: 0 }
-
    100% { opacity: 1 }
+
    0% {
+
      opacity: 0;
+
    }
+
    100% {
+
      opacity: 1;
+
    }
  }
  @-webkit-keyframes fadeIn {
-
    0% { opacity: 0 }
-
    100% { opacity: 1 }
+
    0% {
+
      opacity: 0;
+
    }
+
    100% {
+
      opacity: 1;
+
    }
  }
</style>

<div class="container">
-
  <div class="spinner" class:fade-in={fadeIn} class:small class:center class:margins class:condensed>
-
    <div class="bounce1" style="background-color: var(--color-{color})"></div>
+
  <div
+
    class="spinner"
+
    class:fade-in={fadeIn}
+
    class:small
+
    class:center
+
    class:margins
+
    class:condensed>
+
    <div class="bounce1" style="background-color: var(--color-{color})" />
    {#if !condensed}
-
      <div class="bounce2" style="background-color: var(--color-{color})"></div>
-
      <div class="bounce3" style="background-color: var(--color-{color})"></div>
+
      <div class="bounce2" style="background-color: var(--color-{color})" />
+
      <div class="bounce3" style="background-color: var(--color-{color})" />
    {/if}
  </div>
</div>
modified src/Logo.svelte
@@ -5,12 +5,29 @@
<style>
</style>

-
<svg {style} width="36" height="34" viewBox="0 0 36 34" fill="none" xmlns="http://www.w3.org/2000/svg">
-
  <path fill-rule="evenodd" clip-rule="evenodd" d="M18.5687 19.0417C18.8572 18.2253 19.1389 17.3905 19.4228 16.5491C21.8513 9.35112 24.4399 1.67842 32.8537 1.04244C33.2252 1.01436 33.608 0.999997 34.0027 1C33.8296 1.23078 33.6991 1.57621 33.5907 2.00565C33.4423 2.59325 33.3351 3.33814 33.2167 4.16182C32.9561 5.97456 32.6405 8.16891 31.7092 9.90809C29.8743 13.3345 24.8285 15.024 22.3056 14.4765C19.91 17.0735 19.3178 22.4338 19.6913 26.411C19.7597 27.1398 19.9076 27.8537 20.0495 28.5387C20.2456 29.4852 20.4302 30.3765 20.3779 31.1756C20.2485 33.6081 15.8912 33.6081 15.7618 31.1756C15.7095 30.3765 15.8941 29.4852 16.0902 28.5387C16.2321 27.8537 16.38 27.1398 16.4484 26.411C16.4675 26.2077 16.4867 26.0074 16.5057 25.8098C16.851 22.2059 17.1059 19.5453 14.9571 17.3278C14.7314 17.095 14.4792 16.867 14.1979 16.6433C14.0258 16.9617 13.8419 17.2404 13.65 17.4841C12.3774 19.0998 10.747 19.1773 9.79299 19.0722C7.61437 18.8321 6.17 17.0674 4.7655 15.3513C4.19335 14.6522 3.62781 13.9612 3.02196 13.3847C2.73293 13.1097 2.43473 12.8607 2.12225 12.6493C2.50137 12.4981 2.88393 12.3708 3.26863 12.2664C8.84538 10.7528 14.8744 14.0475 17.488 19.1056C17.7066 19.5286 17.9013 19.964 18.0699 20.4099C18.2394 19.9612 18.4052 19.5044 18.5687 19.0417ZM17.9276 17.8531C18.1082 17.3281 18.2871 16.7974 18.4681 16.2608C18.7688 15.3688 19.0751 14.4605 19.4034 13.5352C20.1931 11.3094 21.0827 9.07249 22.24 7.08373C23.3997 5.09089 24.849 3.31009 26.777 2.02993C28.7159 0.742458 31.0812 -2.21724e-05 34.0028 4.96602e-10L36 1.51401e-05L34.7986 1.60152C34.7172 1.71 34.5923 2.01783 34.4627 2.67346C34.3703 3.14034 34.2933 3.67532 34.2058 4.28372C34.1774 4.48175 34.1477 4.68756 34.1161 4.90133C33.8683 6.5769 33.5108 8.656 32.5867 10.3815C31.5278 12.359 29.5921 13.7639 27.6601 14.6007C25.9891 15.3244 24.1674 15.6886 22.7093 15.5483C21.8724 16.6529 21.2826 18.2584 20.9395 20.1091C20.5575 22.1692 20.504 24.4096 20.6831 26.3172C20.7459 26.9858 20.8806 27.6368 21.0222 28.3216C21.05 28.4558 21.078 28.5913 21.1058 28.7285C21.2666 29.5219 21.4262 30.3985 21.3724 31.2354C21.3203 32.1765 20.8453 32.8995 20.1952 33.3606C19.5677 33.8056 18.7957 34 18.0699 34C17.344 34 16.572 33.8056 15.9446 33.3606C15.2944 32.8995 14.8193 32.1764 14.7673 31.2353C14.7135 30.3984 14.8731 29.5219 15.0339 28.7285C15.0617 28.5913 15.0897 28.4558 15.1175 28.3216C15.2591 27.6368 15.3938 26.9858 15.4566 26.3172C15.475 26.1207 15.4933 25.9286 15.5113 25.7404C15.6955 23.8092 15.8394 22.3004 15.6535 20.9804C15.5062 19.9343 15.1504 19.0223 14.3789 18.1701C13.7456 18.9517 13.02 19.4494 12.2823 19.7434C11.2786 20.1434 10.3271 20.1371 9.68428 20.0662C7.5339 19.8293 6.04107 18.3873 4.8627 17.0257C4.56533 16.6821 4.2767 16.3294 3.9997 15.991L3.99119 15.9806C3.70843 15.6351 3.4379 15.3048 3.16528 14.9905C2.61589 14.3573 2.10048 13.8405 1.56544 13.4785L0 12.4195L1.75443 11.7199C7.89421 9.27172 14.7119 12.5328 17.9276 17.8531Z" fill="url(#paint0_radial)" fill-opacity="0.84"/>
+
<svg
+
  {style}
+
  width="36"
+
  height="34"
+
  viewBox="0 0 36 34"
+
  fill="none"
+
  xmlns="http://www.w3.org/2000/svg">
+
  <path
+
    fill-rule="evenodd"
+
    clip-rule="evenodd"
+
    d="M18.5687 19.0417C18.8572 18.2253 19.1389 17.3905 19.4228 16.5491C21.8513 9.35112 24.4399 1.67842 32.8537 1.04244C33.2252 1.01436 33.608 0.999997 34.0027 1C33.8296 1.23078 33.6991 1.57621 33.5907 2.00565C33.4423 2.59325 33.3351 3.33814 33.2167 4.16182C32.9561 5.97456 32.6405 8.16891 31.7092 9.90809C29.8743 13.3345 24.8285 15.024 22.3056 14.4765C19.91 17.0735 19.3178 22.4338 19.6913 26.411C19.7597 27.1398 19.9076 27.8537 20.0495 28.5387C20.2456 29.4852 20.4302 30.3765 20.3779 31.1756C20.2485 33.6081 15.8912 33.6081 15.7618 31.1756C15.7095 30.3765 15.8941 29.4852 16.0902 28.5387C16.2321 27.8537 16.38 27.1398 16.4484 26.411C16.4675 26.2077 16.4867 26.0074 16.5057 25.8098C16.851 22.2059 17.1059 19.5453 14.9571 17.3278C14.7314 17.095 14.4792 16.867 14.1979 16.6433C14.0258 16.9617 13.8419 17.2404 13.65 17.4841C12.3774 19.0998 10.747 19.1773 9.79299 19.0722C7.61437 18.8321 6.17 17.0674 4.7655 15.3513C4.19335 14.6522 3.62781 13.9612 3.02196 13.3847C2.73293 13.1097 2.43473 12.8607 2.12225 12.6493C2.50137 12.4981 2.88393 12.3708 3.26863 12.2664C8.84538 10.7528 14.8744 14.0475 17.488 19.1056C17.7066 19.5286 17.9013 19.964 18.0699 20.4099C18.2394 19.9612 18.4052 19.5044 18.5687 19.0417ZM17.9276 17.8531C18.1082 17.3281 18.2871 16.7974 18.4681 16.2608C18.7688 15.3688 19.0751 14.4605 19.4034 13.5352C20.1931 11.3094 21.0827 9.07249 22.24 7.08373C23.3997 5.09089 24.849 3.31009 26.777 2.02993C28.7159 0.742458 31.0812 -2.21724e-05 34.0028 4.96602e-10L36 1.51401e-05L34.7986 1.60152C34.7172 1.71 34.5923 2.01783 34.4627 2.67346C34.3703 3.14034 34.2933 3.67532 34.2058 4.28372C34.1774 4.48175 34.1477 4.68756 34.1161 4.90133C33.8683 6.5769 33.5108 8.656 32.5867 10.3815C31.5278 12.359 29.5921 13.7639 27.6601 14.6007C25.9891 15.3244 24.1674 15.6886 22.7093 15.5483C21.8724 16.6529 21.2826 18.2584 20.9395 20.1091C20.5575 22.1692 20.504 24.4096 20.6831 26.3172C20.7459 26.9858 20.8806 27.6368 21.0222 28.3216C21.05 28.4558 21.078 28.5913 21.1058 28.7285C21.2666 29.5219 21.4262 30.3985 21.3724 31.2354C21.3203 32.1765 20.8453 32.8995 20.1952 33.3606C19.5677 33.8056 18.7957 34 18.0699 34C17.344 34 16.572 33.8056 15.9446 33.3606C15.2944 32.8995 14.8193 32.1764 14.7673 31.2353C14.7135 30.3984 14.8731 29.5219 15.0339 28.7285C15.0617 28.5913 15.0897 28.4558 15.1175 28.3216C15.2591 27.6368 15.3938 26.9858 15.4566 26.3172C15.475 26.1207 15.4933 25.9286 15.5113 25.7404C15.6955 23.8092 15.8394 22.3004 15.6535 20.9804C15.5062 19.9343 15.1504 19.0223 14.3789 18.1701C13.7456 18.9517 13.02 19.4494 12.2823 19.7434C11.2786 20.1434 10.3271 20.1371 9.68428 20.0662C7.5339 19.8293 6.04107 18.3873 4.8627 17.0257C4.56533 16.6821 4.2767 16.3294 3.9997 15.991L3.99119 15.9806C3.70843 15.6351 3.4379 15.3048 3.16528 14.9905C2.61589 14.3573 2.10048 13.8405 1.56544 13.4785L0 12.4195L1.75443 11.7199C7.89421 9.27172 14.7119 12.5328 17.9276 17.8531Z"
+
    fill="url(#paint0_radial)"
+
    fill-opacity="0.84" />
  <defs>
-
    <radialGradient id="paint0_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(14.6116 21.5361) rotate(67.3618) scale(26.8971 26.8264)">
-
      <stop stop-color="#5555FF"/>
-
      <stop offset="1" stop-color="#FF55FF"/>
+
    <radialGradient
+
      id="paint0_radial"
+
      cx="0"
+
      cy="0"
+
      r="1"
+
      gradientUnits="userSpaceOnUse"
+
      gradientTransform="translate(14.6116 21.5361) rotate(67.3618) scale(26.8971 26.8264)">
+
      <stop stop-color="#5555FF" />
+
      <stop offset="1" stop-color="#FF55FF" />
    </radialGradient>
  </defs>
</svg>
modified src/Markdown.svelte
@@ -20,7 +20,7 @@
        ...getDefaultWhiteList(),
        img: ["src"],
        audio: ["src"],
-
        video: ["src"]
+
        video: ["src"],
      },
      stripIgnoreTag: false,
    });
@@ -76,8 +76,12 @@
    padding-left: 0.5rem;
  }

-
  .markdown :global(h1), .markdown :global(h2), .markdown :global(h3),
-
  .markdown :global(h4), .markdown :global(h5), .markdown :global(h6) {
+
  .markdown :global(h1),
+
  .markdown :global(h2),
+
  .markdown :global(h3),
+
  .markdown :global(h4),
+
  .markdown :global(h5),
+
  .markdown :global(h6) {
    color: var(--color-foreground);
  }

@@ -182,7 +186,8 @@
    display: none;
  }

-
  .markdown :global(a), .markdown :global(a > code) {
+
  .markdown :global(a),
+
  .markdown :global(a > code) {
    background: none;
    padding: 0;
    color: var(--color-foreground);
modified src/Message.svelte
@@ -14,5 +14,5 @@
</style>

<div class="message" class:message-error={error}>
-
  <slot></slot>
+
  <slot />
</div>
modified src/MobileNavbar.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
-
  import { link } from 'svelte-routing';
-
  import { createEventDispatcher } from 'svelte';
-
  import Search from './Search.svelte';
+
  import { link } from "svelte-routing";
+
  import { createEventDispatcher } from "svelte";
+
  import Search from "./Search.svelte";
  import { clickOutside } from "@app/utils";

  const dispatch = createEventDispatcher();
@@ -25,10 +25,10 @@
    display: flex;
    align-items: center;
    justify-content: center;
-
    background-color: #000000BF;
+
    background-color: #000000bf;
  }
  .modal {
-
    position:absolute;
+
    position: absolute;
    top: 90px;
    right: 1.5rem;
    padding: 1.5rem;
@@ -66,9 +66,7 @@
        <Search size={20} on:search={() => dispatch("select")} />
      </div>
      <div>
-
        <a use:link on:click={() => dispatch("select")} href="/orgs">
-
          Orgs
-
        </a>
+
        <a use:link on:click={() => dispatch("select")} href="/orgs">Orgs</a>
        <a use:link on:click={() => dispatch("select")} href="/registrations">
          Register
        </a>
modified src/Modal.svelte
@@ -8,7 +8,8 @@
</script>

<style>
-
  .modal-floating, .modal-overlay {
+
  .modal-floating,
+
  .modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
@@ -24,7 +25,7 @@
  }
  .modal-overlay {
    z-index: 200;
-
    background-color: rgba(0, 0, 0, .75);
+
    background-color: rgba(0, 0, 0, 0.75);
  }
  .modal {
    padding: 2rem 3rem;
@@ -48,16 +49,10 @@
  .modal.modal-subtle {
    border: none;
    box-shadow: none;
-
    background: radial-gradient(
-
      var(--color-glow) 0%,
-
      transparent 70%
-
    );
+
    background: radial-gradient(var(--color-glow) 0%, transparent 70%);
  }
  .modal.modal-subtle.error {
-
    background: radial-gradient(
-
      var(--color-glow-error) 0%,
-
      transparent 70%
-
    );
+
    background: radial-gradient(var(--color-glow-error) 0%, transparent 70%);
  }
  .modal-title {
    color: var(--color-foreground);
@@ -100,27 +95,29 @@
</style>

{#if floating}
-
  <div class="modal-overlay"></div>
+
  <div class="modal-overlay" />
{/if}

<div class:modal-floating={floating} class:off-centered={!center}>
-
  <div class="modal" class:error
-
       class:modal-subtle={subtle}
-
       class:modal-narrow={narrow}
-
       class:modal-small={small}>
+
  <div
+
    class="modal"
+
    class:error
+
    class:modal-subtle={subtle}
+
    class:modal-narrow={narrow}
+
    class:modal-small={small}>
    <div class="modal-title">
-
      <slot name="title"></slot>
+
      <slot name="title" />
    </div>
    <div class="modal-subtitle">
-
      <slot name="subtitle"></slot>
+
      <slot name="subtitle" />
    </div>
    {#if $$slots.body && !small}
      <div class="modal-body">
-
        <slot name="body"></slot>
+
        <slot name="body" />
      </div>
    {/if}
    <div class="modal-actions">
-
      <slot name="actions"></slot>
+
      <slot name="actions" />
    </div>
  </div>
</div>
modified src/NotFound.spec.ts
@@ -2,13 +2,13 @@ import NotFound from "./NotFound.svelte";
import { render } from "@testing-library/svelte";
import "@public/index.css";

-
describe('NotFound', () => {
+
describe("NotFound", () => {
  it("shows passed props correctly", () => {
    render(NotFound, {
      props: {
        title: "nakamoto",
-
        subtitle: "Sorry, the requested project was not found."
-
      }
+
        subtitle: "Sorry, the requested project was not found.",
+
      },
    });
    cy.findByText("nakamoto");
    cy.findByText("Sorry, the requested project was not found.");
modified src/NotFound.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import Modal from '@app/Modal.svelte';
+
  import Modal from "@app/Modal.svelte";

  export let title = "";
  export let subtitle = "";
@@ -14,8 +14,6 @@
    <p>{subtitle}</p>
  </span>
  <span slot="actions">
-
    <button on:click={back}>
-
      Back
-
    </button>
+
    <button on:click={back}>Back</button>
  </span>
</Modal>
modified src/Options.svelte
@@ -1,8 +1,12 @@
<script lang="ts">
  import { marked } from "marked";
-
  import { createEventDispatcher } from 'svelte';
+
  import { createEventDispatcher } from "svelte";

-
  export let options: { label: string; value: string; description?: string[] }[];
+
  export let options: {
+
    label: string;
+
    value: string;
+
    description?: string[];
+
  }[];
  export let name: string;
  export let selected = "";
  export let disabled = false;
@@ -27,7 +31,8 @@
  .options label:last-child {
    margin-bottom: 0.5rem;
  }
-
  .options label, .options input {
+
  .options label,
+
  .options input {
    cursor: pointer;
  }
  .options input {
@@ -64,10 +69,15 @@
<main>
  <div class="options">
    {#each options as option}
-
      <label for="{option.value}">
-
        <input type="radio" {disabled} checked={selected === option.value} {name}
-
               id="{option.value}" value="{option.value}"
-
               on:click={() => dispatch('changed', option.value)}>
+
      <label for={option.value}>
+
        <input
+
          type="radio"
+
          {disabled}
+
          checked={selected === option.value}
+
          {name}
+
          id={option.value}
+
          value={option.value}
+
          on:click={() => dispatch("changed", option.value)} />
        {option.label}
      </label>
      {#if option.description}
modified src/Placeholder.svelte
@@ -23,7 +23,7 @@
<div class="error error-message placeholder">
  <header>
    <div class="icon">{icon}</div>
-
    <slot name="title"></slot>
+
    <slot name="title" />
  </header>
-
  <slot name="body"></slot>
+
  <slot name="body" />
</div>
modified src/Profile.svelte
@@ -1,26 +1,26 @@
<script lang="ts">
-
  import type { SvelteComponent } from 'svelte';
-
  import type { Config } from '@app/config';
-
  import Address from '@app/Address.svelte';
-
  import Avatar from '@app/Avatar.svelte';
-
  import Icon from '@app/Icon.svelte';
-
  import SetName from '@app/ens/SetName.svelte';
-
  import SeedAddress from '@app/SeedAddress.svelte';
-
  import TransferOwnership from '@app/base/orgs/TransferOwnership.svelte';
-
  import Link from '@app/Link.svelte';
-
  import { getBalance, Profile, ProfileType } from '@app/profile';
-
  import Loading from '@app/Loading.svelte';
-
  import * as utils from '@app/utils';
-
  import { session } from '@app/session';
-
  import { Org } from '@app/base/orgs/Org';
-
  import Message from '@app/Message.svelte';
-
  import Error from '@app/Error.svelte';
-
  import { User } from '@app/base/users/User';
-
  import Projects from '@app/base/orgs/View/Projects.svelte';
-
  import { MissingReverseRecord, NotFoundError } from '@app/error';
-
  import NotFound from '@app/NotFound.svelte';
-
  import RadicleUrn from '@app/RadicleUrn.svelte';
-
  import Badge from '@app/Badge.svelte';
+
  import type { SvelteComponent } from "svelte";
+
  import type { Config } from "@app/config";
+
  import Address from "@app/Address.svelte";
+
  import Avatar from "@app/Avatar.svelte";
+
  import Icon from "@app/Icon.svelte";
+
  import SetName from "@app/ens/SetName.svelte";
+
  import SeedAddress from "@app/SeedAddress.svelte";
+
  import TransferOwnership from "@app/base/orgs/TransferOwnership.svelte";
+
  import Link from "@app/Link.svelte";
+
  import { getBalance, Profile, ProfileType } from "@app/profile";
+
  import Loading from "@app/Loading.svelte";
+
  import * as utils from "@app/utils";
+
  import { session } from "@app/session";
+
  import { Org } from "@app/base/orgs/Org";
+
  import Message from "@app/Message.svelte";
+
  import Error from "@app/Error.svelte";
+
  import { User } from "@app/base/users/User";
+
  import Projects from "@app/base/orgs/View/Projects.svelte";
+
  import { MissingReverseRecord, NotFoundError } from "@app/error";
+
  import NotFound from "@app/NotFound.svelte";
+
  import RadicleUrn from "@app/RadicleUrn.svelte";
+
  import Badge from "@app/Badge.svelte";

  export let config: Config;
  export let addressOrName: string;
@@ -38,10 +38,11 @@
  };

  $: account = $session && $session.address;
-
  $: isOwner = (org: Org): boolean => $session
-
    ? utils.isAddressEqual(org.owner, $session.address)
-
    : false;
-
  $: getOrgTreasury = async (org: Org): Promise<Array<utils.Token>| undefined> => {
+
  $: isOwner = (org: Org): boolean =>
+
    $session ? utils.isAddressEqual(org.owner, $session.address) : false;
+
  $: getOrgTreasury = async (
+
    org: Org,
+
  ): Promise<Array<utils.Token> | undefined> => {
    const addressType = await utils.identifyAddress(org.owner, config);
    // We query the org treasury only for Gnosis Safes, to maintain some privacy for EOA org owners.
    if (addressType === utils.AddressType.Safe) {
@@ -49,9 +50,18 @@
        const tokens = await utils.getTokens(org.owner, config);
        const balance = await getBalance(org.owner, config);

-
        if (! balance.isZero()) {
+
        if (!balance.isZero()) {
          // To maintain the format we hardcode the ETH specs.
-
          return [{ balance, decimals: 18, logo: "", name: "Ethereum", symbol: "ETH" }, ...tokens];
+
          return [
+
            {
+
              balance,
+
              decimals: 18,
+
              logo: "",
+
              name: "Ethereum",
+
              symbol: "ETH",
+
            },
+
            ...tokens,
+
          ];
        } else {
          return tokens;
        }
@@ -184,15 +194,21 @@
  <main>
    <header>
      <div class="avatar">
-
        <Avatar source={profile.avatar ?? profile.address} title={profile.address} />
+
        <Avatar
+
          source={profile.avatar ?? profile.address}
+
          title={profile.address} />
      </div>
      <div class="info">
        <span class="title">
          <span class="bold desktop">
-
            {profile.name ? utils.formatName(profile.name, config) : profile.address}
+
            {profile.name
+
              ? utils.formatName(profile.name, config)
+
              : profile.address}
          </span>
          <span class="bold mobile">
-
            {profile.name ? utils.formatName(profile.name, config) : utils.formatAddress(profile.address)}
+
            {profile.name
+
              ? utils.formatName(profile.name, config)
+
              : utils.formatAddress(profile.address)}
          </span>
          {#if profile.name && profile.org}
            <Badge variant="foreground">org</Badge>
@@ -236,14 +252,22 @@
      {/if}
      <!-- Address -->
      <div class="label">Address</div>
-
      <div class="desktop"><Address {config} {profile} address={profile.address} /></div>
-
      <div class="mobile"><Address compact {config} {profile} address={profile.address} /></div>
+
      <div class="desktop">
+
        <Address {config} {profile} address={profile.address} />
+
      </div>
+
      <div class="mobile">
+
        <Address compact {config} {profile} address={profile.address} />
+
      </div>
      <div class="desktop" />
      <!-- Owner -->
      {#if profile.org}
        <div class="label">Owner</div>
-
        <div class="desktop"><Address resolve {config} address={profile.org.owner} /></div>
-
        <div class="mobile"><Address compact resolve {config} address={profile.org.owner} /></div>
+
        <div class="desktop">
+
          <Address resolve {config} address={profile.org.owner} />
+
        </div>
+
        <div class="mobile">
+
          <Address compact resolve {config} address={profile.org.owner} />
+
        </div>
        <div class="desktop">
          {#await account && profile.org.isMember(account, config) then isMember}
            {#if isOwner(profile.org) || isMember}
@@ -259,7 +283,9 @@
            <div class="label">Treasury</div>
            <div>
              {#each tokens as token}
-
                {` ${utils.formatBalance(token.balance, token.decimals)} ${token.symbol} `}
+
                {` ${utils.formatBalance(token.balance, token.decimals)} ${
+
                  token.symbol
+
                } `}
              {/each}
            </div>
            <div class="desktop" />
@@ -269,8 +295,12 @@
        <!-- Project anchors -->
        {#if profile.anchorsAccount}
          <div class="label">Anchors</div>
-
          <div class="desktop"><Address {config} address={profile.anchorsAccount} /></div>
-
          <div class="mobile"><Address compact {config} address={profile.anchorsAccount} /></div>
+
          <div class="desktop">
+
            <Address {config} address={profile.anchorsAccount} />
+
          </div>
+
          <div class="mobile">
+
            <Address compact {config} address={profile.anchorsAccount} />
+
          </div>
          <div class="desktop" />
        {/if}
      {/if}
@@ -290,9 +320,7 @@
              <!-- Loading -->
            {:then authorized}
              {#if authorized}
-
                <button class="small secondary" on:click={setName}>
-
                  Set
-
                </button>
+
                <button class="small secondary" on:click={setName}>Set</button>
              {/if}
            {/await}
          </div>
@@ -302,9 +330,11 @@
          {#if safe}
            <div class="label">Quorum</div>
            <div>
-
              {safe.threshold} <span class="faded">of</span> {safe.owners.length}
+
              {safe.threshold}
+
              <span class="faded">of</span>
+
              {safe.owners.length}
            </div>
-
            <div class="desktop"/>
+
            <div class="desktop" />
          {/if}
        {/await}
      {:else}
@@ -318,9 +348,7 @@
        </div>
        <div class="desktop">
          {#if isUserAuthorized(profile.address)}
-
            <button class="small secondary" on:click={setName}>
-
              Set
-
            </button>
+
            <button class="small secondary" on:click={setName}>Set</button>
          {/if}
        </div>
      {/if}
@@ -331,7 +359,7 @@
        <Loading center />
      {:then members}
        {#if members.length > 0}
-
            <!-- We don't need to catch errors here, since it's not defined by user input and defaults to ETH addresses -->
+
          <!-- We don't need to catch errors here, since it's not defined by user input and defaults to ETH addresses -->
          {#await Profile.getMulti(members, config)}
            <div class="members loading">
              <Loading small />
@@ -342,12 +370,20 @@
                <div class="member">
                  <div class="member-icon">
                    <Link to="/{profile.address}">
-
                      <Avatar source={profile.avatar ?? profile.address} title={profile.address} />
+
                      <Avatar
+
                        source={profile.avatar ?? profile.address}
+
                        title={profile.address} />
                    </Link>
                  </div>
                  <div class="desktop">
-
                    <Address address={profile.address} compact
-
                      resolve noBadge noAvatar {profile} {config} />
+
                    <Address
+
                      address={profile.address}
+
                      compact
+
                      resolve
+
                      noBadge
+
                      noAvatar
+
                      {profile}
+
                      {config} />
                  </div>
                </div>
              {/each}
@@ -356,7 +392,8 @@
        {/if}
      {:catch err}
        <Message error>
-
          <strong>Error: </strong> failed to load org members: {err.message}.
+
          <strong>Error:</strong>
+
          failed to load org members: {err.message}.
        </Message>
      {/await}
    {:else}
@@ -373,12 +410,20 @@
                {:then profile}
                  <div class="member-icon">
                    <Link to="/{profile.address}">
-
                      <Avatar source={profile.avatar ?? profile.address} title={profile.address} />
+
                      <Avatar
+
                        source={profile.avatar ?? profile.address}
+
                        title={profile.address} />
                    </Link>
                  </div>
                  <div class="desktop">
-
                    <Address address={profile.address} compact
-
                      resolve noBadge noAvatar {profile} {config} />
+
                    <Address
+
                      address={profile.address}
+
                      compact
+
                      resolve
+
                      noBadge
+
                      noAvatar
+
                      {profile}
+
                      {config} />
                  </div>
                {/await}
              </div>
@@ -387,7 +432,8 @@
        {/if}
      {:catch err}
        <Message error>
-
          <strong>Error: </strong> failed to load orgs: {err.message}.
+
          <strong>Error:</strong>
+
          failed to load orgs: {err.message}.
        </Message>
      {/await}
    {/if}
@@ -396,13 +442,25 @@
    {/if}
  </main>

-
  <svelte:component this={setNameForm} entity={profile.org ?? new User(profile.address)} {config} on:close={() => setNameForm = null} />
-
  <svelte:component this={transferOwnerForm} org={profile.org} {config} on:close={() => transferOwnerForm = null} />
+
  <svelte:component
+
    this={setNameForm}
+
    entity={profile.org ?? new User(profile.address)}
+
    {config}
+
    on:close={() => (setNameForm = null)} />
+
  <svelte:component
+
    this={transferOwnerForm}
+
    org={profile.org}
+
    {config}
+
    on:close={() => (transferOwnerForm = null)} />
{:catch err}
  {#if err instanceof NotFoundError}
-
    <NotFound title={addressOrName} subtitle="Sorry, the requested address or domain was not found." />
+
    <NotFound
+
      title={addressOrName}
+
      subtitle="Sorry, the requested address or domain was not found." />
  {:else if err instanceof MissingReverseRecord}
-
    <NotFound title={addressOrName} subtitle="Sorry, the requested name has no reverse record set." />
+
    <NotFound
+
      title={addressOrName}
+
      subtitle="Sorry, the requested name has no reverse record set." />
  {:else}
    <Error error={err} />
  {/if}
modified src/RadicleUrn.svelte
@@ -6,12 +6,13 @@
  let copied = false;

  const copy = () => {
-
    return () => toClipboard(urn).then(() => {
-
      copied = true;
-
      setTimeout(() => {
-
        copied = false;
-
      }, 3000);
-
    });
+
    return () =>
+
      toClipboard(urn).then(() => {
+
        copied = true;
+
        setTimeout(() => {
+
          copied = false;
+
        }, 3000);
+
      });
  };
</script>

@@ -32,7 +33,6 @@
  }
</style>

-

<div class="desktop">
  <div class="urn">
    <span class="icon">🌱</span>
modified src/Reactions.svelte
@@ -19,8 +19,12 @@
  <div class="reactions">
    {#each Object.entries(reactions) as [reaction, count]}
      <!-- TODO: Remove the disabled attribute once we are able to increment reactions -->
-
      <button disabled class="reaction text-xsmall" on:click={() => dispatch("click", reaction)}>
-
        {reaction} {count}
+
      <button
+
        disabled
+
        class="reaction text-xsmall"
+
        on:click={() => dispatch("click", reaction)}>
+
        {reaction}
+
        {count}
      </button>
    {/each}
  </div>
modified src/Review.svelte
@@ -19,7 +19,7 @@
      profile = await Profile.get(
        review.author.profile.ens.name,
        ProfileType.Minimal,
-
        config
+
        config,
      );
    }
  });
@@ -32,11 +32,16 @@
</style>

{#if review.comment.body}
-
  <Comment {config} {getImage}
-
    comment={review.comment} caption={formatVerdict(review.verdict)} />
+
  <Comment
+
    {config}
+
    {getImage}
+
    comment={review.comment}
+
    caption={formatVerdict(review.verdict)} />
{:else}
  <div>
-
    <Authorship {config} {profile}
+
    <Authorship
+
      {config}
+
      {profile}
      author={review.author}
      timestamp={review.timestamp}
      caption={formatVerdict(review.verdict)} />
modified src/Search.spec.ts
@@ -2,10 +2,14 @@ import Search from "./Search.svelte";
import { fireEvent, render } from "@testing-library/svelte";
import "@public/index.css";

-
describe('Logic', () => {
+
describe("Logic", () => {
  it("show a appropiate placeholder", () => {
    render(Search);
-
    cy.get("input").should("have.attr", "placeholder", "Search a name or address...");
+
    cy.get("input").should(
+
      "have.attr",
+
      "placeholder",
+
      "Search a name or address...",
+
    );
  });

  it("allow input a query and navigates accordingly", () => {
modified src/Search.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
-
  import { navigate } from 'svelte-routing';
-
  import { createEventDispatcher } from 'svelte';
+
  import { navigate } from "svelte-routing";
+
  import { createEventDispatcher } from "svelte";

  export let size = 40;

@@ -8,11 +8,9 @@

  const dispatch = createEventDispatcher();
  const handleKeydown = (event: KeyboardEvent) => {
-
    if (event.key === 'Enter') {
+
    if (event.key === "Enter") {
      dispatch("search");
-
      navigate(`/resolver/query?${
-
        new URLSearchParams({ q: input })
-
      }`);
+
      navigate(`/resolver/query?${new URLSearchParams({ q: input })}`);
    }
  };
</script>
@@ -24,15 +22,15 @@
    font-size: 0.875rem;
    text-overflow: ellipsis;
    margin: 0;
-
    padding:  0.5rem 1.25rem;
+
    padding: 0.5rem 1.25rem;
    border-style: dashed;
-
	height: var(--button-regular-height);
+
    height: var(--button-regular-height);
  }
</style>

<input
-
    size="{size}"
-
    type="text"
-
    bind:value={input}
-
    on:keydown={handleKeydown}
-
    placeholder="Search a name or address..." />
+
  {size}
+
  type="text"
+
  bind:value={input}
+
  on:keydown={handleKeydown}
+
  placeholder="Search a name or address..." />
modified src/SeedAddress.spec.ts
@@ -2,7 +2,7 @@ import SeedAddress from "./SeedAddress.svelte";
import { render } from "@testing-library/svelte";
import "@public/index.css";

-
describe('SeedAddress', () => {
+
describe("SeedAddress", () => {
  it("shows the seed emoji and seed host", () => {
    render(SeedAddress, {
      props: {
@@ -11,11 +11,13 @@ describe('SeedAddress', () => {
          emoji: "🐱",
          host: "seed.cloudhead.io",
        },
-
        port: 8776
-
      }
+
        port: 8776,
+
      },
    });
    cy.get("span.seed-icon").should("have.text", "🐱");
-
    cy.findByText("seed.cloudhead.io").should("have.attr", "href", "/seeds/seed.cloudhead.io").should("be.visible");
+
    cy.findByText("seed.cloudhead.io")
+
      .should("have.attr", "href", "/seeds/seed.cloudhead.io")
+
      .should("be.visible");
  });

  it("shows the full seed id", () => {
@@ -27,8 +29,8 @@ describe('SeedAddress', () => {
          host: "seed.cloudhead.io",
        },
        port: 8776,
-
        full: true
-
      }
+
        full: true,
+
      },
    });
    cy.get("span.seed-icon").should("have.text", "🐱");
    cy.findByText("hydkkk…coygh1@seed.cloudhead.io").should("be.visible");
modified src/SeedAddress.svelte
@@ -34,12 +34,22 @@
  <div class="seed-address">
    <span class="seed-icon">{seed.emoji}</span>
    {#if full}
-
      <span><a href="/seeds/{formatSeedHost(seed.host)}" class="link">{formatSeedId(seed.id)}@{seed.host}</a></span>
+
      <span>
+
        <a href="/seeds/{formatSeedHost(seed.host)}" class="link">
+
          {formatSeedId(seed.id)}@{seed.host}
+
        </a>
+
      </span>
      <span class="faded">:{port}</span>
    {:else}
-
      <span><a href="/seeds/{formatSeedHost(seed.host)}" class="link">{formatSeedHost(seed.host)}</a></span>
+
      <span>
+
        <a href="/seeds/{formatSeedHost(seed.host)}" class="link">
+
          {formatSeedHost(seed.host)}
+
        </a>
+
      </span>
    {/if}
  </div>
-
  <Clipboard small text={full ? formatSeedAddress(seed.id, seed.host, port) : seed.host} />
+
  <Clipboard
+
    small
+
    text={full ? formatSeedAddress(seed.id, seed.host, port) : seed.host} />
</div>
-
<div class="desktop"/>
+
<div class="desktop" />
modified src/SeedDropdown.svelte
@@ -11,12 +11,19 @@

  // When a user signs into a new seed we want to update the seed listing
  $: formatSeeds = async () => {
-
    return await Promise.all(Object.values(seeds).map(async session => {
-
      const seed = await Seed.lookup(session.domain, config);
-
      const key = `${seed.emoji} ${seed.host}`;
+
    return await Promise.all(
+
      Object.values(seeds).map(async session => {
+
        const seed = await Seed.lookup(session.domain, config);
+
        const key = `${seed.emoji} ${seed.host}`;

-
      return { key, value: seed.host, title: `Go to ${seed.host}`, badge: null };
-
    }));
+
        return {
+
          key,
+
          value: seed.host,
+
          title: `Go to ${seed.host}`,
+
          badge: null,
+
        };
+
      }),
+
    );
  };
</script>

@@ -24,9 +31,8 @@
  <Dropdown
    {items}
    selected={null}
-
    on:select={(item) => {
+
    on:select={item => {
      closeFocused();
      navigate(`/seeds/${item.detail}`);
-
    }}
-
  />
+
    }} />
{/await}
modified src/SiweConnect.svelte
@@ -25,7 +25,7 @@
<button
  class="small secondary"
  title={tooltip}
-
  disabled={disabled || (connection === Connection.Connecting)}
+
  disabled={disabled || connection === Connection.Connecting}
  on:click={async () => {
    connection = Connection.Connecting;
    try {
@@ -34,8 +34,7 @@
      console.error("Sign in", e);
      connection = Connection.Disconnected;
    }
-
  }}
-
>
+
  }}>
  <span class="align">
    {#if address}
      <Avatar title={address} source={address} inline />
modified src/ToggleButton.svelte
@@ -41,12 +41,14 @@
    border: none;
    min-width: 0;
  }
-
  button:hover, button.active {
+
  button:hover,
+
  button.active {
    cursor: pointer;
    color: var(--color-foreground);
    background-color: var(--color-foreground-background);
  }
-
  button[disabled], button[disabled]:hover {
+
  button[disabled],
+
  button[disabled]:hover {
    cursor: not-allowed;
    color: var(--color-foreground-80);
  }
modified src/WalletConnectSigner.ts
@@ -1,11 +1,17 @@
import type WalletConnect from "@walletconnect/client";
import * as ethers from "ethers";
import * as ethersBytes from "@ethersproject/bytes";
-
import type { TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
+
import type {
+
  TransactionRequest,
+
  TransactionResponse,
+
} from "@ethersproject/abstract-provider";
import { resolveProperties } from "@ethersproject/properties";
import type { Deferrable } from "@ethersproject/properties";
import { _TypedDataEncoder } from "ethers/lib/utils";
-
import type { TypedDataDomain, TypedDataField } from "@ethersproject/abstract-signer";
+
import type {
+
  TypedDataDomain,
+
  TypedDataField,
+
} from "@ethersproject/abstract-signer";

export class WalletConnectSigner extends ethers.Signer {
  public walletConnect: WalletConnect;
@@ -23,35 +29,49 @@ export class WalletConnectSigner extends ethers.Signer {

  async getAddress(): Promise<string> {
    const accountAddress = this.walletConnect.accounts[0];
-
    if (! accountAddress) {
+
    if (!accountAddress) {
      throw new Error(
-
        "The connected wallet has no accounts or there is a connection problem"
+
        "The connected wallet has no accounts or there is a connection problem",
      );
    }
    return ethers.utils.getAddress(accountAddress);
  }

-
  async _signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
+
  async _signTypedData(
+
    domain: TypedDataDomain,
+
    types: Record<string, Array<TypedDataField>>,
+
    value: Record<string, any>,
+
  ): Promise<string> {
    // Populate any ENS names (in-place)
-
    const populated = await _TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => {
-
      const address = await this.provider.resolveName(name);
-
      if (address === null) throw Error("resolver or addr is not configured for ENS name");
-
      return address;
-
    });
+
    const populated = await _TypedDataEncoder.resolveNames(
+
      domain,
+
      types,
+
      value,
+
      async (name: string) => {
+
        const address = await this.provider.resolveName(name);
+
        if (address === null) {
+
          throw Error("resolver or addr is not configured for ENS name");
+
        }
+
        return address;
+
      },
+
    );

    const address = await this.getAddress();
    const signature = await this.walletConnect.signTypedData([
      address.toLowerCase(),
-
      JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value)),
+
      JSON.stringify(
+
        _TypedDataEncoder.getPayload(populated.domain, types, populated.value),
+
      ),
    ]);
    return signature;
  }

  async signMessage(message: ethers.Bytes | string): Promise<string> {
    const prefix = ethers.utils.toUtf8Bytes(
-
      `\x19Ethereum Signed Message:\n${message.length}`
+
      `\x19Ethereum Signed Message:\n${message.length}`,
    );
-
    const data = ((typeof (message) === "string") ? ethers.utils.toUtf8Bytes(message) : message);
+
    const data =
+
      typeof message === "string" ? ethers.utils.toUtf8Bytes(message) : message;

    const msg = ethers.utils.concat([prefix, data]);
    const address = await this.getAddress();
@@ -65,7 +85,7 @@ export class WalletConnectSigner extends ethers.Signer {
  }

  async sendTransaction(
-
    transaction: Deferrable<TransactionRequest>
+
    transaction: Deferrable<TransactionRequest>,
  ): Promise<TransactionResponse> {
    const tx = await resolveProperties(transaction);
    const from = tx.from || (await this.getAddress());
@@ -88,12 +108,12 @@ export class WalletConnectSigner extends ethers.Signer {
      from: from,
      wait: (confirmations?: number) => {
        return this.provider?.waitForTransaction(txHash, confirmations);
-
      }
+
      },
    };
  }

  async signTransaction(
-
    transaction: Deferrable<TransactionRequest>
+
    transaction: Deferrable<TransactionRequest>,
  ): Promise<string> {
    const tx = await resolveProperties(transaction);
    const from = tx.from || (await this.getAddress());
@@ -117,7 +137,7 @@ export class WalletConnectSigner extends ethers.Signer {
}

function maybeBigNumberToString(
-
  bn: ethers.BigNumberish | undefined
+
  bn: ethers.BigNumberish | undefined,
): string | undefined {
  if (bn === undefined) {
    return undefined;
@@ -127,7 +147,7 @@ function maybeBigNumberToString(
}

function bytesLikeToString(
-
  bytes: ethersBytes.BytesLike | undefined
+
  bytes: ethersBytes.BytesLike | undefined,
): string | undefined {
  if (bytes === undefined) {
    return undefined;
modified src/anchors.ts
@@ -16,11 +16,19 @@ interface AnchorObject {
  multihash: string;
}

-
export async function getProjectAnchors(urn: string, anchorsStorage: string, config: Config): Promise<string[]> {
+
export async function getProjectAnchors(
+
  urn: string,
+
  anchorsStorage: string,
+
  config: Config,
+
): Promise<string[]> {
  const unpadded = decodeRadicleId(urn);
  const id = ethers.utils.hexZeroPad(unpadded, 32);
-
  const allAnchors = await querySubgraph(config.orgs.subgraph, GetAllAnchors, { project: id, org: anchorsStorage });
+
  const allAnchors = await querySubgraph(config.orgs.subgraph, GetAllAnchors, {
+
    project: id,
+
    org: anchorsStorage,
+
  });

-
  return allAnchors.anchors
-
    .map((anchor: AnchorObject) => formatProjectHash(ethers.utils.arrayify(anchor.multihash)));
+
  return allAnchors.anchors.map((anchor: AnchorObject) =>
+
    formatProjectHash(ethers.utils.arrayify(anchor.multihash)),
+
  );
}
modified src/api.ts
@@ -25,8 +25,8 @@ export class Request {
    const urlString = this.createUrl(search);

    return await Request.exec(urlString, {
-
      method: 'GET',
-
      headers: { ...headers, 'Accept': 'application/json' }
+
      method: "GET",
+
      headers: { ...headers, Accept: "application/json" },
    });
  }

@@ -38,9 +38,9 @@ export class Request {
    const urlString = this.createUrl();

    return await Request.exec(urlString, {
-
      method: 'POST',
+
      method: "POST",
      body: JSON.stringify(body),
-
      headers: { ...headers, 'Content-Type': 'application/json' }
+
      headers: { ...headers, "Content-Type": "application/json" },
    });
  }

@@ -52,14 +52,17 @@ export class Request {
    const urlString = this.createUrl();

    return await Request.exec(urlString, {
-
      method: 'PUT',
+
      method: "PUT",
      body: JSON.stringify(body),
-
      headers: { ...headers, 'Content-Type': 'application/json' }
+
      headers: { ...headers, "Content-Type": "application/json" },
    });
  }

  // Executes a request and returns the response.
-
  static async exec(urlString: string, props: Record<string, any>): Promise<any> {
+
  static async exec(
+
    urlString: string,
+
    props: Record<string, any>,
+
  ): Promise<any> {
    let response = null;
    try {
      response = await fetch(urlString, props);
@@ -67,7 +70,7 @@ export class Request {
      throw new ApiError("API request failed", urlString);
    }

-
    if (! response.ok) {
+
    if (!response.ok) {
      throw new ApiError(response.statusText, urlString);
    }
    return response.json();
modified src/base/faucet/Index.svelte
@@ -5,7 +5,11 @@
  import { setOpenGraphMetaTag, toWei } from "@app/utils";
  import { formatEther } from "@ethersproject/units";
  import { navigate } from "svelte-routing";
-
  import { getMaxWithdrawAmount, lastWithdrawalByUser, calculateTimeLock } from "./lib";
+
  import {
+
    getMaxWithdrawAmount,
+
    lastWithdrawalByUser,
+
    calculateTimeLock,
+
  } from "./lib";

  export let config: Config;

@@ -17,7 +21,7 @@
  setOpenGraphMetaTag([
    { prop: "og:title", content: "Radicle Faucet" },
    { prop: "og:description", content: "Rinkeby Testnet Faucet" },
-
    { prop: "og:url", content: window.location.href }
+
    { prop: "og:url", content: window.location.href },
  ]);

  async function withdraw() {
@@ -28,15 +32,31 @@

  async function isAbleToWithdraw(amount: string): Promise<[boolean, string?]> {
    try {
-
      if (! $session) { return [false]; }
-
      if (!amount || amount === "0") { return [false, "Not able to withdraw zero tokens"]; }
-
      if (toWei(amount).gt(maxWithdrawAmount)) return [false, `Reduce amount, max withdrawal is ${formatEther(maxWithdrawAmount)}`];
+
      if (!$session) {
+
        return [false];
+
      }
+
      if (!amount || amount === "0") {
+
        return [false, "Not able to withdraw zero tokens"];
+
      }
+
      if (toWei(amount).gt(maxWithdrawAmount)) {
+
        return [
+
          false,
+
          `Reduce amount, max withdrawal is ${formatEther(maxWithdrawAmount)}`,
+
        ];
+
      }
      const currentTime = new Date().getTime();
      const timelock = await calculateTimeLock(amount, $session.signer, config);
      // Converting a 10 digit to 13 digit timestamp by multiplying by 1000
      // since JS doesn't display a correct Date string when passing a 10 digit timestamp.
      const nextAvailableWithdraw = lastWithdrawal.add(timelock).mul(1000);
-
      if (nextAvailableWithdraw.gt(currentTime)) return [false, `Not ready to withdraw, return after ${new Date(nextAvailableWithdraw.toNumber()).toLocaleString('en-GB')}`];
+
      if (nextAvailableWithdraw.gt(currentTime)) {
+
        return [
+
          false,
+
          `Not ready to withdraw, return after ${new Date(
+
            nextAvailableWithdraw.toNumber(),
+
          ).toLocaleString("en-GB")}`,
+
        ];
+
      }

      return [true];
    } catch (e: any) {
@@ -48,8 +68,12 @@
  }

  $: if ($session) {
-
    getMaxWithdrawAmount($session.signer, config).then(x => maxWithdrawAmount = x);
-
    lastWithdrawalByUser($session.signer, config).then(x => lastWithdrawal = x);
+
    getMaxWithdrawAmount($session.signer, config).then(
+
      x => (maxWithdrawAmount = x),
+
    );
+
    lastWithdrawalByUser($session.signer, config).then(
+
      x => (lastWithdrawal = x),
+
    );
  }
</script>

@@ -89,7 +113,6 @@
  .description.invalid {
    color: var(--color-negative) !important;
  }
-

</style>

<svelte:head>
@@ -100,13 +123,13 @@
  <div>
    {#if config.network.name === "homestead"}
      <div class="input-caption">
-
        To get RAD tokens on <strong>{config.network.name}</strong>, please
-
        check the known exchanges.
+
        To get RAD tokens on <strong>{config.network.name}</strong>
+
        , please check the known exchanges.
      </div>
    {:else if !$session}
      <div class="input-caption">
-
        To get RAD tokens on <strong>{config.network.name}</strong>, please
-
        connect your wallet.
+
        To get RAD tokens on <strong>{config.network.name}</strong>
+
        , please connect your wallet.
      </div>
    {:else}
      <div class="input-caption">
@@ -118,16 +141,15 @@
            type="text"
            placeholder="Set amount to withdraw"
            bind:value={amount}
-
            on:input={() => error = ""}
-
          />
-
        <button disabled={false} class="primary" on:click={withdraw}>
+
            on:input={() => (error = "")} />
+
          <button disabled={false} class="primary" on:click={withdraw}>
            Withdraw
-
        </button>
+
          </button>
        </div>
        {#if error}
-
        <div class="error description invalid text-small faded">
-
          {error}
-
        </div>
+
          <div class="error description invalid text-small faded">
+
            {error}
+
          </div>
        {/if}
      </div>
    {/if}
modified src/base/faucet/Withdraw.svelte
@@ -7,7 +7,7 @@
  import Err from "@app/Error.svelte";
  import { Status, State } from "@app/utils";
  import { withdraw } from "./lib";
-
  import { session } from '@app/session';
+
  import { session } from "@app/session";

  export let config: Config;

@@ -17,7 +17,7 @@
    status: Status.Failed,
    error: "Error withdrawing, something happened.",
  };
-
  $: requester = ($session && $session.address);
+
  $: requester = $session && $session.address;

  const back = () => navigate(`/faucet`);

@@ -49,11 +49,7 @@
</style>

{#if error}
-
  <Err
-
    title="Transaction failed"
-
    message={error.message}
-
    on:close={back}
-
  />
+
  <Err title="Transaction failed" message={error.message} on:close={back} />
{:else}
  <Modal>
    <span slot="title">
@@ -84,7 +80,7 @@

    <span slot="actions">
      {#if state.status === Status.Success}
-
        <button on:click={back}> Back </button>
+
        <button on:click={back}>Back</button>
      {/if}
    </span>
  </Modal>
modified src/base/faucet/lib.ts
@@ -1,51 +1,65 @@
-
import * as ethers from 'ethers';
+
import * as ethers from "ethers";

-
import type { Config } from '@app/config';
-
import { assert } from '@app/error';
-
import type { TransactionResponse } from '@ethersproject/providers';
-
import { toWei } from '@app/utils';
+
import type { Config } from "@app/config";
+
import { assert } from "@app/error";
+
import type { TransactionResponse } from "@ethersproject/providers";
+
import { toWei } from "@app/utils";
import type { WalletConnectSigner } from "@app/WalletConnectSigner";
-
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
+
import type { TypedDataSigner } from "@ethersproject/abstract-signer";

-
type Signer = ethers.Signer & TypedDataSigner | WalletConnectSigner | null;
+
type Signer = (ethers.Signer & TypedDataSigner) | WalletConnectSigner | null;

-
export async function withdraw(amount: string, signer: Signer, config: Config): Promise<TransactionResponse> {
+
export async function withdraw(
+
  amount: string,
+
  signer: Signer,
+
  config: Config,
+
): Promise<TransactionResponse> {
  assert(signer);

  const faucet = new ethers.Contract(
    config.radToken.faucet,
    config.abi.faucet,
-
    signer
+
    signer,
  );

  return faucet.withdraw(config.radToken.address, toWei(amount));
}

-
export async function getMaxWithdrawAmount(signer: Signer, config: Config): Promise<ethers.BigNumber> {
+
export async function getMaxWithdrawAmount(
+
  signer: Signer,
+
  config: Config,
+
): Promise<ethers.BigNumber> {
  assert(signer);

  const faucet = new ethers.Contract(
    config.radToken.faucet,
    config.abi.faucet,
-
    signer
+
    signer,
  );

  return faucet.maxWithdrawAmount();
}

-
export async function calculateTimeLock(amount: string, signer: Signer, config: Config): Promise<ethers.BigNumber> {
+
export async function calculateTimeLock(
+
  amount: string,
+
  signer: Signer,
+
  config: Config,
+
): Promise<ethers.BigNumber> {
  assert(signer);

  const faucet = new ethers.Contract(
    config.radToken.faucet,
    config.abi.faucet,
-
    signer
+
    signer,
  );

  return faucet.calculateTimeLock(toWei(amount));
}

-
export async function lastWithdrawalByUser(signer: Signer, config: Config): Promise<ethers.BigNumber> {
+
export async function lastWithdrawalByUser(
+
  signer: Signer,
+
  config: Config,
+
): Promise<ethers.BigNumber> {
  assert(signer);

  const address = signer.getAddress();
@@ -53,7 +67,7 @@ export async function lastWithdrawalByUser(signer: Signer, config: Config): Prom
  const faucet = new ethers.Contract(
    config.radToken.faucet,
    config.abi.faucet,
-
    signer
+
    signer,
  );

  return faucet.lastWithdrawalByUser(address);
modified src/base/home/Index.svelte
@@ -1,33 +1,36 @@
<script lang="ts">
-
  import { navigate } from 'svelte-routing';
-
  import type { Config } from '@app/config';
-
  import Loading from '@app/Loading.svelte';
-
  import Widget from '@app/base/projects/Widget.svelte';
-
  import { Project, ProjectInfo } from '@app/project';
-
  import type { Host } from '@app/api';
+
  import { navigate } from "svelte-routing";
+
  import type { Config } from "@app/config";
+
  import Loading from "@app/Loading.svelte";
+
  import Widget from "@app/base/projects/Widget.svelte";
+
  import { Project, ProjectInfo } from "@app/project";
+
  import type { Host } from "@app/api";
  import * as proj from "@app/project";
-
  import Message from '@app/Message.svelte';
-
  import { setOpenGraphMetaTag } from '@app/utils';
+
  import Message from "@app/Message.svelte";
+
  import { setOpenGraphMetaTag } from "@app/utils";

  export let config: Config;

  setOpenGraphMetaTag([
    { prop: "og:title", content: "Radicle Interface" },
    { prop: "og:description", content: "Interact with Radicle" },
-
    { prop: "og:url", content: window.location.href }
+
    { prop: "og:url", content: window.location.href },
  ]);

-
  const getProjects = config.projects.pinned.length > 0
-
    ? Project.getMulti(config.projects.pinned)
-
    : Promise.resolve([]);
+
  const getProjects =
+
    config.projects.pinned.length > 0
+
      ? Project.getMulti(config.projects.pinned)
+
      : Promise.resolve([]);

  const onClick = (project: ProjectInfo, seed: Host) => {
-
    navigate(proj.path({
-
      urn: project.urn,
-
      seed: seed.host,
-
      profile: null,
-
      revision: project.head,
-
    }));
+
    navigate(
+
      proj.path({
+
        urn: project.urn,
+
        seed: seed.host,
+
        profile: null,
+
        revision: project.head,
+
      }),
+
    );
  };
</script>

@@ -82,8 +85,10 @@

<main>
  <div class="blurb">
-
    <p>Radicle 🌱 enables developers 🧙 to securely collaborate 🔐 on software over a
-
    peer-to-peer network 🌐 built on Git.</p>
+
    <p>
+
      Radicle 🌱 enables developers 🧙 to securely collaborate 🔐 on software
+
      over a peer-to-peer network 🌐 built on Git.
+
    </p>
  </div>

  {#await getProjects}
@@ -93,13 +98,17 @@
  {:then results}
    {#if results.length}
      <div class="heading">
-
        Explore <strong>projects</strong> on the Radicle network.
+
        Explore <strong>projects</strong>
+
        on the Radicle network.
      </div>

      <div class="projects">
        {#each results as result}
          <div class="project">
-
            <Widget compact project={result.info} seed={{ api: result.seed }}
+
            <Widget
+
              compact
+
              project={result.info}
+
              seed={{ api: result.seed }}
              on:click={() => onClick(result.info, result.seed)} />
          </div>
        {/each}
@@ -108,7 +117,8 @@
  {:catch}
    <div class="padding">
      <Message error>
-
        <strong>Error: </strong> failed to load projects.
+
        <strong>Error:</strong>
+
        failed to load projects.
      </Message>
    </div>
  {/await}
modified src/base/orgs/Create.svelte
@@ -1,14 +1,14 @@
<script lang="ts">
-
  import { createEventDispatcher } from 'svelte';
-
  import { navigate } from 'svelte-routing';
-
  import Modal from '@app/Modal.svelte';
-
  import Error from '@app/Error.svelte';
-
  import type { Err } from '@app/error';
-
  import { Org } from '@app/base/orgs/Org';
-
  import type { Config } from '@app/config';
-
  import Loading from '@app/Loading.svelte';
-
  import Options from '@app/Options.svelte';
-
  import Address from '@app/Address.svelte';
+
  import { createEventDispatcher } from "svelte";
+
  import { navigate } from "svelte-routing";
+
  import Modal from "@app/Modal.svelte";
+
  import Error from "@app/Error.svelte";
+
  import type { Err } from "@app/error";
+
  import { Org } from "@app/base/orgs/Org";
+
  import type { Config } from "@app/config";
+
  import Loading from "@app/Loading.svelte";
+
  import Options from "@app/Options.svelte";
+
  import Address from "@app/Address.svelte";

  export let config: Config;
  export let owner: string;
@@ -26,21 +26,23 @@
  }

  const orgTypes = [
-
    { label: "Multi-signature",
+
    {
+
      label: "Multi-signature",
      description: [
        "Creates an org with a multi-signature contract as its owner, and the specified account as the first member.",
        "A [Gnosis Safe](https://gnosis-safe.io) will be deployed for your org.",
-
        "Transactions such as anchoring have to be approved by a quorum of signers."
+
        "Transactions such as anchoring have to be approved by a quorum of signers.",
      ],
-
      value: Governance.Quorum
+
      value: Governance.Quorum,
    },
-
    { label: "Existing owner",
+
    {
+
      label: "Existing owner",
      description: [
        `Creates an org with the specified account as the sole owner.`,
        `Org transactions such as anchoring are signed and executed from that account.`,
-
        `This option allows for using an existing contract or EOA as the owner of the org.`
+
        `This option allows for using an existing contract or EOA as the owner of the org.`,
      ],
-
      value: Governance.Existing
+
      value: Governance.Existing,
    },
  ];

@@ -54,9 +56,10 @@
    state = State.Signing;

    try {
-
      const tx = governance === Governance.Quorum
-
        ? await Org.createMultiSig([owner], 1, config)
-
        : await Org.create(owner, config);
+
      const tx =
+
        governance === Governance.Quorum
+
          ? await Org.createMultiSig([owner], 1, config)
+
          : await Org.create(owner, config);

      state = State.Pending;

@@ -73,8 +76,12 @@

  const onGovernanceChanged = (event: { detail: string }) => {
    switch (event.detail) {
-
      case "existing": governance = Governance.Existing; break;
-
      case "quorum": governance = Governance.Quorum; break;
+
      case "existing":
+
        governance = Governance.Existing;
+
        break;
+
      case "quorum":
+
        governance = Governance.Quorum;
+
        break;
    }
  };
</script>
@@ -122,11 +129,10 @@

{#if error}
  <Error {error} floating on:close />
-
{:else if org} <!-- Org created -->
+
{:else if org}
+
  <!-- Org created -->
  <Modal floating on:close>
-
    <span slot="title">
-
      🎉
-
    </span>
+
    <span slot="title">🎉</span>

    <span slot="subtitle">
      <strong>Your org was successfully created.</strong>
@@ -143,12 +149,11 @@
    </span>

    <span slot="actions">
-
      <button on:click={() => navigate(`/${org?.address}`)}>
-
        Done
-
      </button>
+
      <button on:click={() => navigate(`/${org?.address}`)}>Done</button>
    </span>
  </Modal>
-
{:else} <!-- Org creation flow -->
+
{:else}
+
  <!-- Org creation flow -->
  <Modal floating on:close center>
    <span slot="title">
      <div>🎪</div>
@@ -159,7 +164,9 @@
      {#if state === State.Idle}
        <div class="highlight">Select how you'd like to create your org</div>
      {:else if state === State.Signing}
-
        <div class="highlight">Please confirm the transaction in your wallet.</div>
+
        <div class="highlight">
+
          Please confirm the transaction in your wallet.
+
        </div>
      {:else if state === State.Pending}
        <div class="highlight">Waiting for transaction to be processed...</div>
      {/if}
@@ -169,18 +176,27 @@
      {#if state === State.Idle}
        <div class="configuration">
          <div class="notice">
-
            <strong>Notice:</strong> Orgs V1 are being deprecated. It is recommended
-
            not to create new orgs at this point.
+
            <strong>Notice:</strong>
+
            Orgs V1 are being deprecated. It is recommended not to create new orgs
+
            at this point.
          </div>

          <div class="governance">
-
            <Options name="governance" disabled={state !== State.Idle}
-
                     selected="{governance}" options={orgTypes}
-
                     on:changed={onGovernanceChanged} />
+
            <Options
+
              name="governance"
+
              disabled={state !== State.Idle}
+
              selected={governance}
+
              options={orgTypes}
+
              on:changed={onGovernanceChanged} />
          </div>

          <label class="input" for="address">Ethereum account address</label>
-
          <input name="address" class="small" type="text" maxlength="42" bind:value={owner} />
+
          <input
+
            name="address"
+
            class="small"
+
            type="text"
+
            maxlength="42"
+
            bind:value={owner} />
        </div>
      {:else}
        <Loading center small />
@@ -193,12 +209,11 @@
          on:click={createOrg}
          class="primary regular"
          data-waiting={[State.Signing, State.Pending].includes(state) || null}
-
          disabled={state !== State.Idle}
-
        >
+
          disabled={state !== State.Idle}>
          Create
        </button>

-
        <button on:click={() => dispatch('close')} class="text regular">
+
        <button on:click={() => dispatch("close")} class="text regular">
          Close
        </button>
      {/if}
modified src/base/orgs/Index.svelte
@@ -1,23 +1,23 @@
<script lang="ts">
-
  import type { SvelteComponent } from 'svelte';
-
  import { session } from '@app/session';
-
  import Create from '@app/base/orgs/Create.svelte';
-
  import { Org } from '@app/base/orgs/Org';
-
  import type { Config } from '@app/config';
-
  import Loading from '@app/Loading.svelte';
-
  import Message from '@app/Message.svelte';
-
  import Cards from '@app/Cards.svelte';
-
  import { setOpenGraphMetaTag } from '@app/utils';
+
  import type { SvelteComponent } from "svelte";
+
  import { session } from "@app/session";
+
  import Create from "@app/base/orgs/Create.svelte";
+
  import { Org } from "@app/base/orgs/Org";
+
  import type { Config } from "@app/config";
+
  import Loading from "@app/Loading.svelte";
+
  import Message from "@app/Message.svelte";
+
  import Cards from "@app/Cards.svelte";
+
  import { setOpenGraphMetaTag } from "@app/utils";

  export let config: Config;

  setOpenGraphMetaTag([
    { prop: "og:title", content: "Radicle Orgs" },
    { prop: "og:description", content: "Orgs of the Radicle Network" },
-
    { prop: "og:url", content: window.location.href }
+
    { prop: "og:url", content: window.location.href },
  ]);

-
  const onCreate = () => modal = Create;
+
  const onCreate = () => (modal = Create);
  let modal: typeof SvelteComponent | null = null;

  $: account = $session && $session.address;
@@ -65,8 +65,13 @@
  {#if account}
    <div class="my-orgs">
      <header>
-
        <span>My <strong>Orgs</strong></span>
-
        <button class="create regular secondary" on:click={onCreate} disabled={!account}>
+
        <span>
+
          My <strong>Orgs</strong>
+
        </span>
+
        <button
+
          class="create regular secondary"
+
          on:click={onCreate}
+
          disabled={!account}>
          Create
        </button>
      </header>
@@ -82,7 +87,8 @@
      {:catch}
        <div>
          <Message error>
-
            <strong>Error: </strong> failed to load orgs.
+
            <strong>Error:</strong>
+
            failed to load orgs.
          </Message>
        </div>
      {/await}
@@ -90,7 +96,10 @@
  {/if}

  <header>
-
    <span><strong>Orgs</strong> of the Radicle network</span>
+
    <span>
+
      <strong>Orgs</strong>
+
      of the Radicle network
+
    </span>
  </header>

  {#await Org.getAll(config)}
@@ -104,10 +113,15 @@
  {:catch}
    <div>
      <Message error>
-
        <strong>Error: </strong> failed to load orgs.
+
        <strong>Error:</strong>
+
        failed to load orgs.
      </Message>
    </div>
  {/await}
</main>

-
<svelte:component this={modal} owner={account} {config} on:close={() => modal = null} />
+
<svelte:component
+
  this={modal}
+
  owner={account}
+
  {config}
+
  on:close={() => (modal = null)} />
modified src/base/orgs/Org.ts
@@ -1,16 +1,16 @@
-
import * as ethers from 'ethers';
-
import type { TransactionResponse } from '@ethersproject/providers';
-
import type { ContractReceipt } from '@ethersproject/contracts';
+
import * as ethers from "ethers";
+
import type { TransactionResponse } from "@ethersproject/providers";
+
import type { ContractReceipt } from "@ethersproject/contracts";
import { OperationType } from "@gnosis.pm/safe-core-sdk-types";

-
import { assert } from '@app/error';
-
import * as utils from '@app/utils';
+
import { assert } from "@app/error";
+
import * as utils from "@app/utils";
import * as cache from "@app/cache";
-
import type { SafeMultisigTransactionListResponse } from '@gnosis.pm/safe-service-client';
-
import type SafeServiceClient from '@gnosis.pm/safe-service-client';
-
import type { Safe } from '@app/utils';
-
import type { Config } from '@app/config';
-
import type { PendingAnchor, Anchor } from '@app/project';
+
import type { SafeMultisigTransactionListResponse } from "@gnosis.pm/safe-service-client";
+
import type SafeServiceClient from "@gnosis.pm/safe-service-client";
+
import type { Safe } from "@app/utils";
+
import type { Config } from "@app/config";
+
import type { PendingAnchor, Anchor } from "@app/project";

const GetProjects = `
  query GetProjects($org: ID!) {
@@ -75,7 +75,12 @@ export class Org {
  name?: string | null;
  safe?: Safe | null;

-
  constructor(address: string, owner: string, name?: string | null, safe?: Safe | null) {
+
  constructor(
+
    address: string,
+
    owner: string,
+
    name?: string | null,
+
    safe?: Safe | null,
+
  ) {
    assert(ethers.utils.isAddress(address), "address must be valid");

    this.address = address.toLowerCase(); // Don't store address checksum.
@@ -90,10 +95,11 @@ export class Org {
    const org = new ethers.Contract(
      this.address,
      config.abi.org,
-
      config.signer
+
      config.signer,
    );
-
    return org.setName(name, config.provider.network.ensAddress,
-
      { gasLimit: 200_000 });
+
    return org.setName(name, config.provider.network.ensAddress, {
+
      gasLimit: 200_000,
+
    });
  }

  async setNameMultisig(name: string, config: Config): Promise<void> {
@@ -105,7 +111,7 @@ export class Org {
    const org = new ethers.Contract(
      this.address,
      config.abi.org,
-
      config.signer
+
      config.signer,
    );
    const unsignedTx = await org.populateTransaction.setName(
      name,
@@ -113,8 +119,10 @@ export class Org {
    );

    const txData = unsignedTx.data;
-
    if (! txData) {
-
      throw new Error("Org::setNameMultisig: Could not generate transaction for `setName` call");
+
    if (!txData) {
+
      throw new Error(
+
        "Org::setNameMultisig: Could not generate transaction for `setName` call",
+
      );
    }

    const safeTx = {
@@ -126,13 +134,16 @@ export class Org {
    await utils.proposeSafeTransaction(safeTx, safeAddress, config);
  }

-
  async setOwner(address: string, config: Config): Promise<TransactionResponse> {
+
  async setOwner(
+
    address: string,
+
    config: Config,
+
  ): Promise<TransactionResponse> {
    assert(config.signer);

    const org = new ethers.Contract(
      this.address,
      config.abi.org,
-
      config.signer
+
      config.signer,
    );
    return org.setOwner(address);
  }
@@ -146,15 +157,15 @@ export class Org {
    const org = new ethers.Contract(
      this.address,
      config.abi.org,
-
      config.signer
-
    );
-
    const unsignedTx = await org.populateTransaction.setOwner(
-
      owner
+
      config.signer,
    );
+
    const unsignedTx = await org.populateTransaction.setOwner(owner);

    const txData = unsignedTx.data;
-
    if (! txData) {
-
      throw new Error("Org::setOwnerMultisig: Could not generate transaction for `setOwner` call");
+
    if (!txData) {
+
      throw new Error(
+
        "Org::setOwnerMultisig: Could not generate transaction for `setOwner` call",
+
      );
    }

    const safeTx = {
@@ -191,7 +202,7 @@ export class Org {
    const result = await utils.querySubgraph(
      config.orgs.subgraph,
      GetProjects,
-
      { org: this.address }
+
      { org: this.address },
    );
    const projects: Anchor[] = [];

@@ -203,7 +214,7 @@ export class Org {
          anchor: {
            stateHash: utils.formatProjectHash(
              ethers.utils.arrayify(p.anchor.multihash),
-
            )
+
            ),
          },
        };
        projects.push(proj);
@@ -215,12 +226,13 @@ export class Org {
  }

  async getPendingProjects(config: Config): Promise<PendingAnchor[]> {
-
    if (! config.safe.client) return [];
+
    if (!config.safe.client) return [];

    try {
      const orgAddr = ethers.utils.getAddress(this.address);
      const response = await getPendingProjects(
-
        ethers.utils.getAddress(this.owner), config.safe.client
+
        ethers.utils.getAddress(this.owner),
+
        config.safe.client,
      );
      const projects: PendingAnchor[] = [];

@@ -246,17 +258,17 @@ export class Org {
    }
  }

-
  static async getAnchor(orgAddr: string, urn: string, config: Config): Promise<string | null> {
-
    const org = new ethers.Contract(
-
      orgAddr,
-
      config.abi.org,
-
      config.provider
-
    );
+
  static async getAnchor(
+
    orgAddr: string,
+
    urn: string,
+
    config: Config,
+
  ): Promise<string | null> {
+
    const org = new ethers.Contract(orgAddr, config.abi.org, config.provider);
    const unpadded = utils.decodeRadicleId(urn);
    const id = ethers.utils.zeroPad(unpadded, 32);

    try {
-
      const [,hash] = await org.anchors(id);
+
      const [, hash] = await org.anchors(id);
      const anchor = utils.formatProjectHash(ethers.utils.arrayify(hash));

      return anchor;
@@ -288,7 +300,7 @@ export class Org {
  }

  static fromReceipt(receipt: ContractReceipt): Org | null {
-
    const event = receipt.events?.find(e => e.event === 'OrgCreated');
+
    const event = receipt.events?.find(e => e.event === "OrgCreated");

    if (event && event.args) {
      const address = event.args[0];
@@ -299,10 +311,7 @@ export class Org {
    return null;
  }

-
  static async get(
-
    addressOrName: string,
-
    config: Config,
-
  ): Promise<Org | null> {
+
  static async get(addressOrName: string, config: Config): Promise<Org | null> {
    const org = await getOrgContract(addressOrName, config);

    try {
@@ -327,10 +336,20 @@ export class Org {

    // TODO: We use two subgraph queries since we can't do a filter query yet in the subgraph
    // https://github.com/graphprotocol/graph-node/issues/2539#issuecomment-855979841
-
    const safesByOwner = await utils.querySubgraph(config.orgs.subgraph, GetSafesByOwners, { owners: [owner] });
+
    const safesByOwner = await utils.querySubgraph(
+
      config.orgs.subgraph,
+
      GetSafesByOwners,
+
      { owners: [owner] },
+
    );
    const safes = safesByOwner.safes.reduce(
-
      (prev: any, curr: Safe) => prev.concat(curr.id), []);
-
    const orgsByOwner = await utils.querySubgraph(config.orgs.subgraph, GetOrgsByOwners, { owners: [...safes, owner] });
+
      (prev: any, curr: Safe) => prev.concat(curr.id),
+
      [],
+
    );
+
    const orgsByOwner = await utils.querySubgraph(
+
      config.orgs.subgraph,
+
      GetOrgsByOwners,
+
      { owners: [...safes, owner] },
+
    );
    const orgs: { id: string; owner: string }[] = [...orgsByOwner.orgs];

    return orgs.map(o => new Org(o.id, o.owner));
@@ -346,11 +365,11 @@ export class Org {
    const orgFactory = new ethers.Contract(
      config.orgFactory.address,
      config.abi.orgFactory,
-
      config.signer
+
      config.signer,
    );

-
    return orgFactory['createOrg(address[],uint256)'](owners, threshold, {
-
      gasLimit: config.gasLimits.createOrg
+
    return orgFactory["createOrg(address[],uint256)"](owners, threshold, {
+
      gasLimit: config.gasLimits.createOrg,
    });
  }

@@ -363,16 +382,19 @@ export class Org {
    const orgFactory = new ethers.Contract(
      config.orgFactory.address,
      config.abi.orgFactory,
-
      config.signer
+
      config.signer,
    );

-
    return orgFactory['createOrg(address)'](owner, {
-
      gasLimit: config.gasLimits.createOrg
+
    return orgFactory["createOrg(address)"](owner, {
+
      gasLimit: config.gasLimits.createOrg,
    });
  }
}

-
export function parseAnchorTx(data: string, config: Config): { id: string; stateHash: string } | null {
+
export function parseAnchorTx(
+
  data: string,
+
  config: Config,
+
): { id: string; stateHash: string } | null {
  const iface = new ethers.utils.Interface(config.abi.org);
  const parsedTx = iface.parseTransaction({ data });

@@ -380,7 +402,7 @@ export function parseAnchorTx(data: string, config: Config): { id: string; state
    const encodedProjectUrn = parsedTx.args[0];
    const encodedCommitHash = parsedTx.args[2];
    const id = utils.formatRadicleId(
-
      ethers.utils.arrayify(`${encodedProjectUrn}`)
+
      ethers.utils.arrayify(`${encodedProjectUrn}`),
    );
    const byteArray = ethers.utils.arrayify(encodedCommitHash);
    const stateHash = utils.formatProjectHash(byteArray);
@@ -392,35 +414,31 @@ export function parseAnchorTx(data: string, config: Config): { id: string; state

export const getOrgContract = cache.cached(
  async (addressOrName: string, config: Config) => {
-
    return new ethers.Contract(
-
      addressOrName,
-
      config.abi.org,
-
      config.provider
-
    );
+
    return new ethers.Contract(addressOrName, config.abi.org, config.provider);
  },
-
  (addressOrName) => addressOrName
+
  addressOrName => addressOrName,
);

export const resolveOrgOwner = cache.cached(
  async (org: ethers.Contract) => {
-
    return await Promise.all([
-
      org.owner(),
-
      org.resolvedAddress,
-
    ]);
+
    return await Promise.all([org.owner(), org.resolvedAddress]);
  },
-
  (org) => org.address,
+
  org => org.address,
);

export const getPendingProjects = cache.cached(
-
  async (owner: string, client: SafeServiceClient): Promise<SafeMultisigTransactionListResponse> => {
+
  async (
+
    owner: string,
+
    client: SafeServiceClient,
+
  ): Promise<SafeMultisigTransactionListResponse> => {
    try {
      return await client.getPendingTransactions(
-
        ethers.utils.getAddress(owner)
+
        ethers.utils.getAddress(owner),
      );
    } catch (e) {
      return { count: 0, results: [] };
    }
  },
-
  (owner) => owner,
-
  { max: 1000, ttl: 5 * 60 * 1000 } // Cache results for 5 minutes.
+
  owner => owner,
+
  { max: 1000, ttl: 5 * 60 * 1000 }, // Cache results for 5 minutes.
);
modified src/base/orgs/Routes.svelte
@@ -5,7 +5,9 @@
</script>

<Route path="/orgs">
-
  <NotFound title="404" subtitle="Radicle Orgs are in the process of being re-designed." />
+
  <NotFound
+
    title="404"
+
    subtitle="Radicle Orgs are in the process of being re-designed." />
</Route>

<Route path="/orgs/:addressOrName" let:params>
modified src/base/orgs/TransferOwnership.svelte
@@ -1,14 +1,14 @@
<script lang="ts">
-
  import { onMount, createEventDispatcher } from 'svelte';
-
  import Modal from '@app/Modal.svelte';
-
  import type { Config } from '@app/config';
+
  import { onMount, createEventDispatcher } from "svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import type { Config } from "@app/config";
  import { formatAddress, isAddress } from "@app/utils";
-
  import Loading from '@app/Loading.svelte';
-
  import { assert } from '@app/error';
-
  import * as utils from '@app/utils';
-
  import Address from '@app/Address.svelte';
+
  import Loading from "@app/Loading.svelte";
+
  import { assert } from "@app/error";
+
  import * as utils from "@app/utils";
+
  import Address from "@app/Address.svelte";

-
  import type { Org } from './Org';
+
  import type { Org } from "./Org";

  const dispatch = createEventDispatcher();

@@ -46,14 +46,14 @@
  const onSubmit = async () => {
    assert(newOwner);

-
    if (! isAddress(newOwner)) {
+
    if (!isAddress(newOwner)) {
      state = State.Failed;
      error = `"${newOwner}" is not a valid Ethereum address.`;
      return;
    }

    try {
-
      if (org && await utils.isSafe(org.owner, config)) {
+
      if (org && (await utils.isSafe(org.owner, config))) {
        state = State.Proposing;
        await org.setOwnerMultisig(newOwner, config);
        state = State.Proposed;
@@ -74,37 +74,37 @@

{#if state === State.Success && newOwner}
  <Modal floating small>
-
    <div slot="title">
-
-
    </div>
+
    <div slot="title">✅</div>

    <div slot="subtitle">
-
      The ownership of <strong>{formatAddress(org.address)}</strong> was
-
      successfully transfered to <strong>{newOwner}</strong>.
+
      The ownership of <strong>{formatAddress(org.address)}</strong>
+
      was successfully transfered to
+
      <strong>{newOwner}</strong>
+
      .
    </div>

    <div slot="actions">
-
      <button class="regular" on:click={() => dispatch('close')}>
-
        Done
-
      </button>
+
      <button class="regular" on:click={() => dispatch("close")}>Done</button>
    </div>
  </Modal>
{:else if state === State.Proposed && org}
  <Modal floating>
-
    <div slot="title">
-
      🪴
-
    </div>
+
    <div slot="title">🪴</div>

    <div slot="subtitle">
-
      <p>The transaction to set the owner of <strong>{formatAddress(org.address)}</strong>
-
      to <strong>{newOwner}</strong> was proposed to:</p>
+
      <p>
+
        The transaction to set the owner of <strong>
+
          {formatAddress(org.address)}
+
        </strong>
+
        to
+
        <strong>{newOwner}</strong>
+
        was proposed to:
+
      </p>
      <p><Address address={org.owner} {config} compact /></p>
    </div>

    <div slot="actions">
-
      <button class="regular" on:click={() => dispatch('close')}>
-
        Done
-
      </button>
+
      <button class="regular" on:click={() => dispatch("close")}>Done</button>
    </div>
  </Modal>
{:else}
@@ -121,10 +121,13 @@
        Waiting for transaction to be processed...
      {:else if state === State.Proposing && org}
        Proposal is being submitted to the safe
-
        <strong>{formatAddress(org.owner)}</strong>,
-
        please sign the transaction in your wallet.
+
        <strong>{formatAddress(org.owner)}</strong>
+
        , please sign the transaction in your wallet.
      {:else if state === State.Idle}
-
        Transfer the ownership of Org <strong>{formatAddress(org.address)}</strong> to a new address.
+
        Transfer the ownership of Org <strong>
+
          {formatAddress(org.address)}
+
        </strong>
+
        to a new address.
      {:else if state === State.Failed}
        <div class="error">
          {error}
@@ -134,7 +137,12 @@

    <div slot="body">
      {#if state === State.Idle}
-
        <input type="text" size="40" disabled={state !== State.Idle} bind:this={input} bind:value={newOwner} />
+
        <input
+
          type="text"
+
          size="40"
+
          disabled={state !== State.Idle}
+
          bind:this={input}
+
          bind:value={newOwner} />
      {:else if state === State.Pending || state === State.Proposing || state === State.Signing}
        <Loading small center />
      {:else if state === State.Failed}
@@ -144,25 +152,24 @@

    <div slot="actions">
      {#if state === State.Signing}
-
        <button class="regular" on:click={() => dispatch('close')}>
+
        <button class="regular" on:click={() => dispatch("close")}>
          Cancel
        </button>
      {:else if state === State.Pending}
-
        <button class="regular" on:click={() => dispatch('close')}>
+
        <button class="regular" on:click={() => dispatch("close")}>
          Close
        </button>
      {:else if state === State.Failed}
-
        <button class="regular" on:click={resetForm}>
-
          Back
-
        </button>
+
        <button class="regular" on:click={resetForm}>Back</button>
      {:else}
-
        <button class="primary" on:click={onSubmit} disabled={!newOwner || state !== State.Idle}>
+
        <button
+
          class="primary"
+
          on:click={onSubmit}
+
          disabled={!newOwner || state !== State.Idle}>
          Submit
        </button>

-
        <button class="text" on:click={() => dispatch('close')}>
-
          Cancel
-
        </button>
+
        <button class="text" on:click={() => dispatch("close")}>Cancel</button>
      {/if}
    </div>
  </Modal>
modified src/base/orgs/View/Projects.svelte
@@ -5,7 +5,7 @@
  import * as proj from "@app/project";
  import Loading from "@app/Loading.svelte";
  import Message from "@app/Message.svelte";
-
  import Widget from '@app/base/projects/Widget.svelte';
+
  import Widget from "@app/base/projects/Widget.svelte";
  import type { Profile } from "@app/profile";
  import type { ProjectInfo, Anchor, PendingAnchor } from "@app/project";
  import type { Seed } from "@app/base/seeds/Seed";
@@ -38,7 +38,7 @@
        seed: seed?.host,
        profile: profile?.name ?? profile?.address,
        revision: project.head,
-
      })
+
      }),
    );
  };

@@ -70,9 +70,13 @@
          <Widget {project} {seed} {anchor} on:click={() => onClick(project)}>
            <span class="actions" slot="actions">
              {#if profile?.org?.safe && account && anchor}
-
                {#if pendingAnchor} <!-- Pending anchor -->
+
                {#if pendingAnchor}
+
                  <!-- Pending anchor -->
                  <AnchorActions
-
                    {account} {config} anchor={pendingAnchor} safe={profile.org.safe}
+
                    {account}
+
                    {config}
+
                    anchor={pendingAnchor}
+
                    safe={profile.org.safe}
                    on:success={() => loadAnchors()} />
                {/if}
              {/if}
@@ -83,7 +87,8 @@
    {/each}
  {:catch err}
    <Message error>
-
      <strong>Error: </strong> failed to load projects: {err.message}.
+
      <strong>Error:</strong>
+
      failed to load projects: {err.message}.
    </Message>
  {/await}
</div>
modified src/base/profiles/AnchorActions.svelte
@@ -6,7 +6,7 @@
  import * as utils from "@app/utils";
  import Modal from "@app/Modal.svelte";
  import Avatar from "@app/Avatar.svelte";
-
  import { createEventDispatcher } from 'svelte';
+
  import { createEventDispatcher } from "svelte";
  import Badge from "@app/Badge.svelte";

  export let safe: Safe;
@@ -44,7 +44,11 @@
    try {
      action = Action.Execute;
      state = State.Signing;
-
      const txResult = await utils.executeSignedSafeTransaction(safe.address, safeTxHash, config);
+
      const txResult = await utils.executeSignedSafeTransaction(
+
        safe.address,
+
        safeTxHash,
+
        config,
+
      );

      state = State.Submitting;
      await txResult.transactionResponse?.wait();
@@ -61,7 +65,11 @@
    try {
      action = Action.Sign;
      state = State.Signing;
-
      const signature = await utils.signSafeTransaction(safe.address, safeTxHash, config);
+
      const signature = await utils.signSafeTransaction(
+
        safe.address,
+
        safeTxHash,
+
        config,
+
      );

      state = State.Submitting;
      await config.safe.client?.confirmTransaction(safeTxHash, signature.data);
@@ -74,9 +82,7 @@
    }
  };

-
  $: isSigned = anchor.confirmations.includes(
-
    ethers.utils.getAddress(account)
-
  );
+
  $: isSigned = anchor.confirmations.includes(ethers.utils.getAddress(account));
</script>

<style>
@@ -89,10 +95,12 @@
    grid-gap: 1rem;
    text-align: left;
  }
-
  .table > *:nth-child(odd) { /* Labels */
+
  .table > *:nth-child(odd) {
+
    /* Labels */
    color: var(--color-secondary);
  }
-
  .table > *:nth-child(even) { /* Values */
+
  .table > *:nth-child(even) {
+
    /* Values */
    display: flex;
    align-items: center;
    justify-content: left;
@@ -110,7 +118,8 @@

<span class="confirmations">
  {#if pending > 0}
-
    <strong>{pending}</strong> signature(s) pending
+
    <strong>{pending}</strong>
+
    signature(s) pending
  {/if}
</span>

@@ -122,20 +131,24 @@

<!-- Check whether the threshold has been matched or passed -->
{#if pending <= 0}
-
  <button on:click|stopPropagation={() => {
-
    action = Action.Execute;
-
    state = State.Confirm;
-
  }} class="small execute">
+
  <button
+
    on:click|stopPropagation={() => {
+
      action = Action.Execute;
+
      state = State.Confirm;
+
    }}
+
    class="small execute">
    <Avatar inline source={account} title={account} /> Execute
  </button>
  <!-- Check whether or not we've signed this proposal -->
{:else if isSigned}
  <Badge variant="caution">✓ signed</Badge>
{:else}
-
  <button on:click|stopPropagation={() => {
-
    action = Action.Sign;
-
    state = State.Confirm;
-
    }} class="small">
+
  <button
+
    on:click|stopPropagation={() => {
+
      action = Action.Sign;
+
      state = State.Confirm;
+
    }}
+
    class="small">
    Confirm
  </button>
{/if}
@@ -165,8 +178,10 @@
    <span slot="body">
      {#if state === State.Confirm}
        <div class="table">
-
          <div>Project</div><code>{anchor.id}</code>
-
          <div>Hash</div><code>{anchor.anchor.stateHash}</code>
+
          <div>Project</div>
+
          <code>{anchor.id}</code>
+
          <div>Hash</div>
+
          <code>{anchor.anchor.stateHash}</code>
        </div>
      {:else if state === State.Failed}
        <div>{error}</div>
@@ -175,17 +190,20 @@

    <span slot="actions">
      {#if state === State.Confirm}
-
        <button class="primary" on:click={() => confirmAnchor(anchor.safeTxHash)}>
+
        <button
+
          class="primary"
+
          on:click={() => confirmAnchor(anchor.safeTxHash)}>
          Confirm
        </button>
-
        <button class="text" on:click={close}>
-
          Cancel
-
        </button>
+
        <button class="text" on:click={close}>Cancel</button>
      {:else if state === State.Success || state === State.Failed}
-
        <button on:click={() => {
-
          close();
-
          dispatch("success");
-
        }}>Done</button>
+
        <button
+
          on:click={() => {
+
            close();
+
            dispatch("success");
+
          }}>
+
          Done
+
        </button>
      {/if}
    </span>
  </Modal>
@@ -213,8 +231,10 @@
    <span slot="body">
      {#if state === State.Confirm}
        <div class="table">
-
          <div>TxHash</div><code>{utils.formatHash(anchor.safeTxHash)}</code>
-
          <div>Quorum</div><code>{anchor.confirmations.length} of {safe.threshold}</code>
+
          <div>TxHash</div>
+
          <code>{utils.formatHash(anchor.safeTxHash)}</code>
+
          <div>Quorum</div>
+
          <code>{anchor.confirmations.length} of {safe.threshold}</code>
        </div>
      {:else if state === State.Failed}
        <div>{error}</div>
@@ -223,17 +243,20 @@

    <span slot="actions">
      {#if state === State.Confirm}
-
        <button class="primary" on:click={() => executeTransaction(anchor.safeTxHash)}>
+
        <button
+
          class="primary"
+
          on:click={() => executeTransaction(anchor.safeTxHash)}>
          Confirm
        </button>
-
        <button class="text" on:click={close}>
-
          Cancel
-
        </button>
+
        <button class="text" on:click={close}>Cancel</button>
      {:else if state === State.Success || state === State.Failed}
-
        <button on:click={() => {
-
          close();
-
          dispatch("success");
-
        }}>Done</button>
+
        <button
+
          on:click={() => {
+
            close();
+
            dispatch("success");
+
          }}>
+
          Done
+
        </button>
      {/if}
    </span>
  </Modal>
modified src/base/profiles/AnchorBadge.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import { createEventDispatcher } from 'svelte';
+
  import { createEventDispatcher } from "svelte";

  const dispatch = createEventDispatcher();

@@ -48,27 +48,46 @@
  <!-- commit is head and latest anchor  -->
  {#if commit === anchors[0] && commit === head}
    <span class="anchor-widget anchor-latest" class:no-bg={noBg}>
-
      <span class="anchor-label" title="{anchors[0]}">{#if text}latest&nbsp;{/if}🔐</span>
+
      <span class="anchor-label" title={anchors[0]}>
+
        {#if text}latest&nbsp;{/if}🔐
+
      </span>
    </span>
-
  <!-- commit is not head but latest anchor  -->
+
    <!-- commit is not head but latest anchor  -->
  {:else if commit === anchors[0] && commit !== head}
-
    <span class="anchor-widget" class:no-bg={noBg} on:click={() => dispatch("click", head)}>
-
      <span class="anchor-label" title="{anchors[0]}">{#if text}latest&nbsp;{/if}🔐</span>
+
    <span
+
      class="anchor-widget"
+
      class:no-bg={noBg}
+
      on:click={() => dispatch("click", head)}>
+
      <span class="anchor-label" title={anchors[0]}>
+
        {#if text}latest&nbsp;{/if}🔐
+
      </span>
    </span>
-
  <!-- commit is not head a stale anchor  -->
+
    <!-- commit is not head a stale anchor  -->
  {:else if anchors.includes(commit)}
-
    <span class="anchor-widget" class:no-bg={noBg} on:click={() => dispatch("click", anchors[0])}>
-
      <span class="anchor-label" title="{commit}">{#if text}stale&nbsp;{/if}🔒</span>
+
    <span
+
      class="anchor-widget"
+
      class:no-bg={noBg}
+
      on:click={() => dispatch("click", anchors[0])}>
+
      <span class="anchor-label" title={commit}>
+
        {#if text}stale&nbsp;{/if}🔒
+
      </span>
    </span>
-
  <!-- commit is not anchored, could be head or any other commit  -->
+
    <!-- commit is not anchored, could be head or any other commit  -->
  {:else}
-
    <span class="anchor-widget not-anchored" class:no-bg={noBg} on:click={() => dispatch("click", anchors[0])}>
-
      <span class="anchor-label">{#if text}not anchored&nbsp;{/if}🔓</span>
+
    <span
+
      class="anchor-widget not-anchored"
+
      class:no-bg={noBg}
+
      on:click={() => dispatch("click", anchors[0])}>
+
      <span class="anchor-label">
+
        {#if text}not anchored&nbsp;{/if}🔓
+
      </span>
    </span>
  {/if}
{:else}
  <!-- commit is not head and neither an anchor, and there are no anchors available  -->
  <span class="anchor-widget not-anchored not-allowed" class:no-bg={noBg}>
-
    <span class="anchor-label">{#if text}not anchored&nbsp;{/if}🔓</span>
+
    <span class="anchor-label">
+
      {#if text}not anchored&nbsp;{/if}🔓
+
    </span>
  </span>
{/if}
modified src/base/projects/Blob.svelte
@@ -7,8 +7,13 @@

  const lastCommit = blob.info.lastCommit;
  const lines = blob.binary ? 0 : (blob.content.match(/\n/g) || []).length;
-
  const lineNumbers = Array(lines).fill(0).map((_, index) => index + 1);
-
  const parentDir = blob.path.match(/^.*\/|/)?.values().next().value;
+
  const lineNumbers = Array(lines)
+
    .fill(0)
+
    .map((_, index) => index + 1);
+
  const parentDir = blob.path
+
    .match(/^.*\/|/)
+
    ?.values()
+
    .next().value;

  // Waiting onMount, due to the line numbers still loading.
  onMount(() => {
@@ -66,7 +71,8 @@
  .line-number {
    display: block;
  }
-
  .line-number:hover, .line-number.highlighted {
+
  .line-number:hover,
+
  .line-number.highlighted {
    color: var(--color-foreground-90);
  }

@@ -80,7 +86,7 @@
    display: flex;
    border: 1px solid var(--color-foreground-subtle);
    border-top-style: dashed;
-
	border-bottom-left-radius: var(--border-radius-small);
+
    border-bottom-left-radius: var(--border-radius-small);
    border-bottom-right-radius: var(--border-radius-small);
  }

@@ -116,7 +122,8 @@
  }

  @media (max-width: 960px) {
-
    .code, .line-numbers {
+
    .code,
+
    .line-numbers {
      font-size: 0.875rem;
    }
    .highlight {
@@ -130,9 +137,10 @@
    <header>
      <div class="file-header">
        <span class="file-name">
-
          <span class="faded">{parentDir}</span><span>{blob.info.name}</span>
+
          <span class="faded">{parentDir}</span>
+
          <span>{blob.info.name}</span>
        </span>
-
        <div class="last-commit" title="{lastCommit.author.name}">
+
        <div class="last-commit" title={lastCommit.author.name}>
          <span class="hash">{lastCommit.sha1.slice(0, 7)}</span>
          {lastCommit.summary}
        </div>
@@ -146,14 +154,19 @@
        </div>
      {:else}
        {#if line}
-
          <div class="highlight" style="top: {line === 1 ? 1 : (1.5 * line) - 0.5}rem" />
+
          <div
+
            class="highlight"
+
            style="top: {line === 1 ? 1 : 1.5 * line - 0.5}rem" />
        {/if}
        <div class="line-numbers">
          {#each lineNumbers as lineNumber}
-
            <a href="#L{lineNumber}"
-
               class="line-number"
-
               class:highlighted={lineNumber === line}
-
               id="L{lineNumber}">{lineNumber}</a>
+
            <a
+
              href="#L{lineNumber}"
+
              class="line-number"
+
              class:highlighted={lineNumber === line}
+
              id="L{lineNumber}">
+
              {lineNumber}
+
            </a>
          {/each}
        </div>
        {#if blob.html}
modified src/base/projects/BranchSelector.spec.ts
@@ -7,26 +7,27 @@ const defaultProps = {
    head: "e678629cd37c770c640a2cd997fc76303c815772",
    urn: "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio",
    name: "nakamoto",
-
    description: "Privacy-preserving Bitcoin light-client implementation in Rust",
+
    description:
+
      "Privacy-preserving Bitcoin light-client implementation in Rust",
    defaultBranch: "master",
-
    maintainers: [
-
      "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"
-
    ],
-
    delegates: [
-
      "hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"
-
    ]
+
    maintainers: ["rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"],
+
    delegates: ["hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"],
  },
-
  branches: { "master": "e678629cd37c770c640a2cd997fc76303c815772" },
+
  branches: { master: "e678629cd37c770c640a2cd997fc76303c815772" },
  revision: "e678629cd37c770c640a2cd997fc76303c815772",
};

-
describe('Logic', () => {
+
describe("Logic", () => {
  it("should show defaultBranch label and head commit if revision === head", () => {
    const { rerender } = render(BranchSelector, {
      props: defaultProps,
    });
-
    cy.get("div.stat.branch").should("be.visible").should("have.text", "master");
-
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "e678629");
+
    cy.get("div.stat.branch")
+
      .should("be.visible")
+
      .should("have.text", "master");
+
    cy.get("div.hash.mobile")
+
      .should("be.visible")
+
      .should("have.text", "e678629");

    // If project.head is null we should get the head from branches.
    rerender({
@@ -36,19 +37,20 @@ describe('Logic', () => {
          head: null,
          urn: "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio",
          name: "nakamoto",
-
          description: "Privacy-preserving Bitcoin light-client implementation in Rust",
+
          description:
+
            "Privacy-preserving Bitcoin light-client implementation in Rust",
          defaultBranch: "master",
-
          maintainers: [
-
            "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"
-
          ],
-
          delegates: [
-
            "hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"
-
          ]
-
        }
-
      }
+
          maintainers: ["rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio"],
+
          delegates: ["hyn9diwfnytahjq8u3iw63h9jte1ydcatxax3saymwdxqu1zo645pe"],
+
        },
+
      },
    });
-
    cy.get("div.stat.branch").should("be.visible").should("have.text", "master");
-
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "e678629");
+
    cy.get("div.stat.branch")
+
      .should("be.visible")
+
      .should("have.text", "master");
+
    cy.get("div.hash.mobile")
+
      .should("be.visible")
+
      .should("have.text", "e678629");
  });

  it("should show the branch dropdown if branches available", () => {
@@ -56,18 +58,19 @@ describe('Logic', () => {
      props: {
        ...defaultProps,
        branches: {
-
          "master": "e678629cd37c770c640a2cd997fc76303c815772",
+
          master: "e678629cd37c770c640a2cd997fc76303c815772",
          "feature-branch": "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5",
-
          "xyz": "debf82ef3623ec11751a993bda85bac2ff1c6f00",
+
          xyz: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
        },
-
      }
+
      },
    });
    cy.get("div.commit div.stat.branch").click();
    cy.get("div.dropdown div.dropdown-item")
      .first()
      .should("contain.text", "feature-branch")
      .next()
-
      .should("contain.text", "master").should("have.class", "selected")
+
      .should("contain.text", "master")
+
      .should("have.class", "selected")
      .next()
      .should("contain.text", "xyz");
  });
@@ -75,16 +78,21 @@ describe('Logic', () => {
  it("should show feature-branch label and head commit, if branch label is passed as revision", () => {
    render(BranchSelector, {
      props: {
-
        ...defaultProps, branches: {
-
          "master": "e678629cd37c770c640a2cd997fc76303c815772",
+
        ...defaultProps,
+
        branches: {
+
          master: "e678629cd37c770c640a2cd997fc76303c815772",
          "feature-branch": "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5",
-
          "xyz": "debf82ef3623ec11751a993bda85bac2ff1c6f00",
+
          xyz: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
        },
-
        revision: "feature-branch"
-
      }
+
        revision: "feature-branch",
+
      },
    });
-
    cy.get("div.stat.branch").should("be.visible").should("have.text", "feature-branch");
-
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "29e8b7b");
+
    cy.get("div.stat.branch")
+
      .should("be.visible")
+
      .should("have.text", "feature-branch");
+
    cy.get("div.hash.mobile")
+
      .should("be.visible")
+
      .should("have.text", "29e8b7b");
  });

  it("should show only commit if no branchLabel nor branches are available", () => {
@@ -92,12 +100,16 @@ describe('Logic', () => {
      props: {
        ...defaultProps,
        revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
-
        branches: {}
-
      }
+
        branches: {},
+
      },
    });
-
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "debf82e");
+
    cy.get("div.hash.mobile")
+
      .should("be.visible")
+
      .should("have.text", "debf82e");
    cy.viewport("macbook-13");
-
    cy.get("div.hash.desktop").should("be.visible").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
+
    cy.get("div.hash.desktop")
+
      .should("be.visible")
+
      .should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
  });

  it("should show only commit if branches are available but no branchLabel", () => {
@@ -105,11 +117,15 @@ describe('Logic', () => {
      props: {
        ...defaultProps,
        revision: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
-
      }
+
      },
    });
-
    cy.get("div.hash.mobile").should("be.visible").should("have.text", "debf82e");
+
    cy.get("div.hash.mobile")
+
      .should("be.visible")
+
      .should("have.text", "debf82e");
    cy.viewport("macbook-13");
-
    cy.get("div.hash.desktop").should("be.visible").should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
+
    cy.get("div.hash.desktop")
+
      .should("be.visible")
+
      .should("have.text", "debf82ef3623ec11751a993bda85bac2ff1c6f00");
  });

  it("should show defaultBranch label if revision === head", () => {
@@ -117,10 +133,12 @@ describe('Logic', () => {
      props: {
        ...defaultProps,
        revision: "e678629cd37c770c640a2cd997fc76303c815772",
-
        branches: {}
-
      }
+
        branches: {},
+
      },
    });
-
    cy.get("div.stat.branch.not-allowed").should("be.visible").should("have.text", "master");
+
    cy.get("div.stat.branch.not-allowed")
+
      .should("be.visible")
+
      .should("have.text", "master");
  });
});

@@ -129,8 +147,8 @@ describe("Layout", () => {
    render(BranchSelector, {
      props: {
        ...defaultProps,
-
        revision: "e678629cd37c770c640a2cd997fc76303c815772"
-
      }
+
        revision: "e678629cd37c770c640a2cd997fc76303c815772",
+
      },
    });
    cy.viewport("iphone-x");
    cy.get("div.hash.mobile").should("be.visible");
@@ -149,19 +167,21 @@ describe("Events", () => {
        revision: "feature-branch",
        branches: {
          "feature-branch": "29e8b7b0f3019b8e8a6d9bfb0964ee78f4ff12f5",
-
          "xyz": "debf82ef3623ec11751a993bda85bac2ff1c6f00",
-
        }
-
      }
+
          xyz: "debf82ef3623ec11751a993bda85bac2ff1c6f00",
+
        },
+
      },
    });

-
    cy.get("div.commit div.stat.branch").click().then(() => {
-
      const branchLabel = getByText("xyz");
+
    cy.get("div.commit div.stat.branch")
+
      .click()
+
      .then(() => {
+
        const branchLabel = getByText("xyz");

-
      const mock = cy.spy();
-
      component.$on("branchChanged", mock);
+
        const mock = cy.spy();
+
        component.$on("branchChanged", mock);

-
      fireEvent.click(branchLabel);
-
      expect(mock).to.have.been.calledOnce;
-
    });
+
        fireEvent.click(branchLabel);
+
        expect(mock).to.have.been.calledOnce;
+
      });
  });
});
modified src/base/projects/BranchSelector.svelte
@@ -16,7 +16,9 @@

  let branchLabel: string | null = null;

-
  $: branchList = Object.keys(branches).sort().map(b => ({ key: b, value: b, title: `Switch to ${b}`, badge: null }));
+
  $: branchList = Object.keys(branches)
+
    .sort()
+
    .map(b => ({ key: b, value: b, title: `Switch to ${b}`, badge: null }));
  $: showSelector = branchList.length > 1;
  $: head = project.head ?? branches[project.defaultBranch];
  $: commit = getOid(revision, branches) || head;
@@ -82,7 +84,7 @@
          <Dropdown
            items={branchList}
            selected={branchLabel}
-
            on:select={(e) => switchBranch(e.detail)} />
+
            on:select={e => switchBranch(e.detail)} />
        </svelte:fragment>
      </Floating>
      <div class="hash desktop">
@@ -96,7 +98,7 @@
    <div class="hash mobile">
      {formatCommit(commit)}
    </div>
-
  <!-- If there is no branch listing available, show default branch name if commit is head and else show entire commit -->
+
    <!-- If there is no branch listing available, show default branch name if commit is head and else show entire commit -->
  {:else if commit === head}
    <div class="stat branch not-allowed">
      {project.defaultBranch}
modified src/base/projects/Browser.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
-
  import type { Readable } from 'svelte/store';
-
  import type * as proj from '@app/project';
-
  import Loading from '@app/Loading.svelte';
-
  import Placeholder from '@app/Placeholder.svelte';
-
  import * as utils from '@app/utils';
+
  import type { Readable } from "svelte/store";
+
  import type * as proj from "@app/project";
+
  import Loading from "@app/Loading.svelte";
+
  import Placeholder from "@app/Placeholder.svelte";
+
  import * as utils from "@app/utils";

-
  import Tree from './Tree.svelte';
-
  import Blob from './Blob.svelte';
-
  import Readme from './Readme.svelte';
+
  import Tree from "./Tree.svelte";
+
  import Blob from "./Blob.svelte";
+
  import Readme from "./Readme.svelte";

  enum Status {
    Loading,
@@ -15,7 +15,7 @@
  }

  type State =
-
      { status: Status.Loading; path: string }
+
    | { status: Status.Loading; path: string }
    | { status: Status.Loaded; path: string; blob: proj.Blob };

  export let project: proj.Project;
@@ -38,9 +38,10 @@
    }

    const isMarkdownPath = utils.isMarkdownPath(path);
-
    const promise = path === "/"
-
      ? project.getReadme(commit)
-
      : project.getBlob(commit, path, { highlight: !isMarkdownPath });
+
    const promise =
+
      path === "/"
+
        ? project.getReadme(commit)
+
        : project.getBlob(commit, path, { highlight: !isMarkdownPath });

    state = { status: Status.Loading, path };
    state = { status: Status.Loaded, path, blob: await promise };
@@ -160,7 +161,9 @@
  <!-- Mobile navigation -->
  {#if tree.entries.length > 0}
    <nav class="mobile">
-
      <button class="regular browse secondary center-content" on:click={toggleMobileFileTree}>
+
      <button
+
        class="regular browse secondary center-content"
+
        on:click={toggleMobileFileTree}>
        Browse
      </button>
    </nav>
@@ -202,12 +205,8 @@
    {:else}
      <div class="placeholder">
        <Placeholder icon="👀">
-
          <span slot="title">
-
            Nothing to show
-
          </span>
-
          <span slot="body">
-
            We couldn't find any files at this revision.
-
          </span>
+
          <span slot="title">Nothing to show</span>
+
          <span slot="body">We couldn't find any files at this revision.</span>
        </Placeholder>
      </div>
    {/if}
modified src/base/projects/CloneButton.svelte
@@ -48,9 +48,7 @@
</style>

<Floating>
-
  <div slot="toggle" class="clone-button">
-
    Clone
-
  </div>
+
  <div slot="toggle" class="clone-button">Clone</div>
  <svelte:fragment slot="modal">
    <div class="dropdown">
      <Input
@@ -62,7 +60,10 @@
        Use the <a
          target="_blank"
          href="https://radicle.xyz/get-started.html"
-
          class="link">Radicle CLI</a> to clone this project.
+
          class="link">
+
          Radicle CLI
+
        </a>
+
        to clone this project.
      </label>
      <br />
      <Input
@@ -70,8 +71,9 @@
        value="https://{seedHost}/{utils.parseRadicleId(urn)}.git"
        class="yellow"
        clipboard />
-
      <label for="git-clone-url"
-
        >Use Git to clone this repository from the URL above.</label>
+
      <label for="git-clone-url">
+
        Use Git to clone this repository from the URL above.
+
      </label>
    </div>
  </svelte:fragment>
</Floating>
modified src/base/projects/Commit.svelte
@@ -14,7 +14,7 @@
    project.navigateTo({
      content: proj.ProjectContent.Tree,
      revision: commit.header.sha1,
-
      path: event.detail
+
      path: event.detail,
    });
  };
</script>
modified src/base/projects/Commit/CommitAuthorship.svelte
@@ -14,7 +14,8 @@
    color: var(--color-foreground-faded);
    padding: 0.125rem 0;
  }
-
  .authorship .author, .authorship .committer {
+
  .authorship .author,
+
  .authorship .committer {
    color: var(--color-foreground);
    white-space: nowrap;
  }
@@ -37,23 +38,34 @@

<span class="authorship text-xsmall">
  {#if commit.header.author.email === commit.header.committer.email}
-
    <img class="avatar" alt="avatar" src="{gravatarURL(commit.header.committer.email)}" />
+
    <img
+
      class="avatar"
+
      alt="avatar"
+
      src={gravatarURL(commit.header.committer.email)} />
    {#if commit.context?.committer}
      <span class="bold committer verified-committer">
        {commit.context?.committer.peer.person.name}
      </span>
      <span>&nbsp;committed</span>
    {:else}
-
      <span class="desktop-inline committer">{commit.header.committer.name}</span>
+
      <span class="desktop-inline committer">
+
        {commit.header.committer.name}
+
      </span>
      <span>&nbsp;committed</span>
    {/if}
  {:else}
    {#if showAuthor}
-
      <img class="avatar" alt="avatar" src="{gravatarURL(commit.header.author.email)}" />
+
      <img
+
        class="avatar"
+
        alt="avatar"
+
        src={gravatarURL(commit.header.author.email)} />
      <span class="desktop-inline author">{commit.header.author.name}</span>
      <span>&nbsp;authored&nbsp;</span>
    {/if}
-
    <img class="avatar" alt="avatar" src="{gravatarURL(commit.header.committer.email)}" />
+
    <img
+
      class="avatar"
+
      alt="avatar"
+
      src={gravatarURL(commit.header.committer.email)} />
    {#if commit.context?.committer}
      <span class="bold committer verified-committer">
        {commit.context?.committer.peer.person.name}
@@ -68,6 +80,8 @@
  {/if}
  {#if showTime}
    <span>&nbsp;</span>
-
    <span class="desktop-inline text-xsmall time">{formatTimestamp(commit.header.committerTime)}</span>
+
    <span class="desktop-inline text-xsmall time">
+
      {formatTimestamp(commit.header.committerTime)}
+
    </span>
  {/if}
</span>
modified src/base/projects/Commit/CommitTeaser.svelte
@@ -100,7 +100,10 @@
      </div>
    {/if}
    <span class="secondary hash">{formatCommit(commit.header.sha1)}</span>
-
    <div class="browse" title="View file" on:click|stopPropagation={() => browseCommit(commit.header.sha1)}>
+
    <div
+
      class="browse"
+
      title="View file"
+
      on:click|stopPropagation={() => browseCommit(commit.header.sha1)}>
      <Icon name="browse" />
    </div>
  </div>
modified src/base/projects/Commit/CommitVerifiedBadge.svelte
@@ -45,8 +45,12 @@

<Badge
  variant="tertiary"
-
  on:mouseenter={() => {hover = true;}}
-
  on:mouseleave={() => {hover = false;}}>
+
  on:mouseenter={() => {
+
    hover = true;
+
  }}
+
  on:mouseleave={() => {
+
    hover = false;
+
  }}>
  Verified
</Badge>

modified src/base/projects/Header.svelte
@@ -37,7 +37,6 @@
  const updateRevision = (revision: string) => {
    project.navigateTo({ revision });
  };
-

</script>

<style>
@@ -96,14 +95,14 @@
    <PeerSelector
      {peers}
      peer={browser.peer}
-
      on:peerChanged={(event) => updatePeer(event.detail)} />
+
      on:peerChanged={event => updatePeer(event.detail)} />
  {/if}

  <BranchSelector
    {branches}
    {project}
    {revision}
-
    on:branchChanged={(event) => updateRevision(event.detail)} />
+
    on:branchChanged={event => updateRevision(event.detail)} />

  {#if !noAnchor}
    <div class="anchor widget">
@@ -111,12 +110,12 @@
        {commit}
        {anchors}
        head={project.head}
-
        on:click={(event) => updateRevision(event.detail)} />
+
        on:click={event => updateRevision(event.detail)} />
    </div>
  {/if}

  {#if seed.git.host}
-
    <CloneButton seedHost={seed.git.host} {urn}/>
+
    <CloneButton seedHost={seed.git.host} {urn} />
  {/if}
  <span>
    {#if seed.api.host}
@@ -132,7 +131,8 @@
    class="stat commit-count clickable widget"
    class:active={content === ProjectContent.History}
    on:click={() => toggleContent(ProjectContent.History, true)}>
-
    <strong>{tree.stats.commits}</strong> commit(s)
+
    <strong>{tree.stats.commits}</strong>
+
    commit(s)
  </div>
  {#if project.issues}
    <div
@@ -141,7 +141,8 @@
      class:not-allowed={project.issues === 0}
      class:clickable={project.issues > 0}
      on:click={() => toggleContent(ProjectContent.Issues, false)}>
-
      <strong>{project.issues}</strong> issue(s)
+
      <strong>{project.issues}</strong>
+
      issue(s)
    </div>
  {/if}
  {#if project.patches}
@@ -151,10 +152,12 @@
      class:not-allowed={project.patches === 0}
      class:clickable={project.patches > 0}
      on:click={() => toggleContent(ProjectContent.Patches, false)}>
-
      <strong>{project.patches}</strong> patch(es)
+
      <strong>{project.patches}</strong>
+
      patch(es)
    </div>
  {/if}
  <div class="stat contributor-count widget">
-
    <strong>{tree.stats.contributors}</strong> contributor(s)
+
    <strong>{tree.stats.contributors}</strong>
+
    contributor(s)
  </div>
</header>
modified src/base/projects/History.svelte
@@ -7,11 +7,22 @@
  export let history: GroupedCommitsHistory;

  const navigateHistory = (revision: string, content?: ProjectContent) => {
-
    project.navigateTo({ content, revision, issue: null, patch: null, path: null });
+
    project.navigateTo({
+
      content,
+
      revision,
+
      issue: null,
+
      patch: null,
+
      path: null,
+
    });
  };

  const browseCommit = (event: { detail: string }) => {
-
    project.navigateTo({ content: ProjectContent.Tree, revision: event.detail, issue: null, path: null });
+
    project.navigateTo({
+
      content: ProjectContent.Tree,
+
      revision: event.detail,
+
      issue: null,
+
      path: null,
+
    });
  };
</script>

@@ -53,19 +64,22 @@
  }
</style>

-
  <div class="history">
-
    {#each history.headers as group (group.time)}
-
      <div class="commit-group">
-
        <header class="commit-date">
-
          <p>{group.date}</p>
-
        </header>
-
        <div class="commit-group-headers">
-
          {#each group.commits as commit (commit.header.sha1)}
-
            <div class="commit" on:click={() => navigateHistory(commit.header.sha1, ProjectContent.Commit)}>
-
              <CommitTeaser {commit} on:browseCommit={browseCommit} />
-
            </div>
-
          {/each}
-
        </div>
+
<div class="history">
+
  {#each history.headers as group (group.time)}
+
    <div class="commit-group">
+
      <header class="commit-date">
+
        <p>{group.date}</p>
+
      </header>
+
      <div class="commit-group-headers">
+
        {#each group.commits as commit (commit.header.sha1)}
+
          <div
+
            class="commit"
+
            on:click={() =>
+
              navigateHistory(commit.header.sha1, ProjectContent.Commit)}>
+
            <CommitTeaser {commit} on:browseCommit={browseCommit} />
+
          </div>
+
        {/each}
      </div>
-
    {/each}
-
  </div>
+
    </div>
+
  {/each}
+
</div>
modified src/base/projects/Issue.svelte
@@ -121,13 +121,15 @@
      <div
        class="summary-state"
        class:closed={issue.state.status === "closed"}
-
        class:open={issue.state.status === "open"}
-
      >
+
        class:open={issue.state.status === "open"}>
        {capitalize(issue.state.status)}
      </div>
    </div>
-
    <Authorship {config}
-
      author={issue.author} timestamp={issue.timestamp} caption="opened on" />
+
    <Authorship
+
      {config}
+
      author={issue.author}
+
      timestamp={issue.timestamp}
+
      caption="opened on" />
  </header>
  <main>
    <div class="comments">
@@ -145,18 +147,14 @@
    </div>
    <div class="metadata desktop">
      <div class="metadata-section">
-
        <div class="metadata-section-header">
-
          Labels
-
        </div>
+
        <div class="metadata-section-header">Labels</div>
        <div class="metadata-section-body">
          {#if issue.labels?.length}
            {#each issue.labels as label}
              <span class="label">{label}</span>
            {/each}
          {:else}
-
            <div class="metadata-section-empty">
-
              No labels.
-
            </div>
+
            <div class="metadata-section-empty">No labels.</div>
          {/if}
        </div>
      </div>
modified src/base/projects/Issue/IssueTeaser.svelte
@@ -14,7 +14,11 @@

  onMount(async () => {
    if (issue.author.profile?.ens?.name) {
-
      profile = await Profile.get(issue.author.profile.ens.name, ProfileType.Minimal, config);
+
      profile = await Profile.get(
+
        issue.author.profile.ens.name,
+
        ProfileType.Minimal,
+
        config,
+
      );
    }
  });

@@ -100,8 +104,7 @@
    <div
      class="state-icon"
      class:closed={issue.state.status === "closed"}
-
      class:open={issue.state.status === "open"}
-
    />
+
      class:open={issue.state.status === "open"} />
  </div>
  <div class="column-left">
    <div class="summary">
@@ -109,7 +112,9 @@
      {issue.title}
      <span class="issue-id">{formatObjectId(issue.id)}</span>
    </div>
-
    <Authorship {profile} {config}
+
    <Authorship
+
      {profile}
+
      {config}
      caption="opened"
      author={issue.author}
      timestamp={issue.timestamp} />
modified src/base/projects/Issues.svelte
@@ -25,16 +25,18 @@
  const { open, closed } = groupIssues(issues);

  $: filteredIssues = state === "open" ? open : closed;
-
  $: sortedIssues = filteredIssues.sort(({ timestamp: t1 }, { timestamp: t2 }) => t2 - t1);
+
  $: sortedIssues = filteredIssues.sort(
+
    ({ timestamp: t1 }, { timestamp: t2 }) => t2 - t1,
+
  );

  $: options = [
    {
      value: "open",
-
      count: open.length
+
      count: open.length,
    },
    {
      value: "closed",
-
      count: closed.length
+
      count: closed.length,
    },
  ];
</script>
@@ -61,19 +63,26 @@

<div class="issues">
  <div style="margin-bottom: 1rem;">
-
    <ToggleButton {options} on:select={(e) => {navigate(`?state=${e.detail}`);}} active={state} />
+
    <ToggleButton
+
      {options}
+
      on:select={e => {
+
        navigate(`?state=${e.detail}`);
+
      }}
+
      active={state} />
  </div>

  {#if filteredIssues.length}
    <div class="issues-list">
      {#each sortedIssues as issue}
-
        <div class="teaser" on:click={() => {
+
        <div
+
          class="teaser"
+
          on:click={() => {
            project.navigateTo({
              content: ProjectContent.Issue,
              issue: issue.id,
              patch: null,
              revision: null,
-
              path: null
+
              path: null,
            });
          }}>
          <IssueTeaser {config} {issue} />
modified src/base/projects/Patch.svelte
@@ -29,7 +29,7 @@
      content: ProjectContent.Tree,
      revision,
      patch: null,
-
      path: event.detail
+
      path: event.detail,
    });
  };

@@ -111,7 +111,9 @@
        {capitalize(patch.state)}
      </div>
    </div>
-
    <Authorship noAvatar {config}
+
    <Authorship
+
      noAvatar
+
      {config}
      author={patch.author}
      timestamp={patch.timestamp}
      caption="opened" />
@@ -137,9 +139,7 @@
        on:browse={e => onBrowse(e, revision.oid)} />
    {:else if activeTab === PatchTab.Diff}
      <Placeholder icon="🍳">
-
        <span slot="title">
-
          No changeset found
-
        </span>
+
        <span slot="title">No changeset found</span>
        <span slot="body">
          We couldn't find a changeset related to this patch or revision
        </span>
modified src/base/projects/Patch/PatchTabBar.svelte
@@ -17,7 +17,7 @@

  const formatRevisionName = (revision: Revision, index: number) => {
    return `R${index} ${formatCommit(revision.oid)} ${formatTimestamp(
-
      revision.timestamp
+
      revision.timestamp,
    )}`;
  };

@@ -72,7 +72,12 @@
</style>

<div class="bar text-small">
-
  <ToggleButton {options} on:select={(e) => {dispatch("switchTab", e.detail);}} active={activeTab} />
+
  <ToggleButton
+
    {options}
+
    on:select={e => {
+
      dispatch("switchTab", e.detail);
+
    }}
+
    active={activeTab} />

  <Floating disabled={revisions.length <= 1}>
    <button
@@ -84,7 +89,8 @@

    <svelte:fragment slot="modal">
      <Dropdown
-
        items={revisionList} selected={revisionNumber.toString()}
+
        items={revisionList}
+
        selected={revisionNumber.toString()}
        on:select={onRevisionChange} />
    </svelte:fragment>
  </Floating>
modified src/base/projects/Patch/PatchTeaser.svelte
@@ -14,7 +14,11 @@

  onMount(async () => {
    if (patch.author.profile?.ens?.name) {
-
      profile = await Profile.get(patch.author.profile.ens.name, ProfileType.Minimal, config);
+
      profile = await Profile.get(
+
        patch.author.profile.ens.name,
+
        ProfileType.Minimal,
+
        config,
+
      );
    }
  });

@@ -100,8 +104,7 @@
    <div
      class="state-icon"
      class:closed={patch.state === "archived"}
-
      class:open={patch.state === "proposed"}
-
    />
+
      class:open={patch.state === "proposed"} />
  </div>
  <div class="column-left">
    <div class="summary">
@@ -109,7 +112,9 @@
      {patch.title}
      <span class="patch-id">{formatObjectId(patch.id)}</span>
    </div>
-
    <Authorship {profile} {config}
+
    <Authorship
+
      {profile}
+
      {config}
      caption="opened"
      author={patch.author}
      timestamp={patch.timestamp} />
modified src/base/projects/Patches.svelte
@@ -25,16 +25,16 @@
  $: options = [
    {
      value: "proposed",
-
      count: sortedPatches.proposed.length
+
      count: sortedPatches.proposed.length,
    },
    {
      value: "draft",
-
      count: sortedPatches.draft.length
+
      count: sortedPatches.draft.length,
    },
    {
      value: "archived",
-
      count: sortedPatches.archived.length
-
    }
+
      count: sortedPatches.archived.length,
+
    },
  ];
</script>

@@ -60,19 +60,26 @@

<div class="patches">
  <div style="margin-bottom: 1rem;">
-
    <ToggleButton {options} on:select={(e) => {state = e.detail;}} active={state} />
+
    <ToggleButton
+
      {options}
+
      on:select={e => {
+
        state = e.detail;
+
      }}
+
      active={state} />
  </div>

  {#if filteredPatches.length}
    <div class="patches-list">
      {#each filteredPatches as patch}
-
        <div class="teaser" on:click={() => {
+
        <div
+
          class="teaser"
+
          on:click={() => {
            project.navigateTo({
              content: ProjectContent.Patch,
              patch: patch.id,
              issue: null,
              revision: null,
-
              path: null
+
              path: null,
            });
          }}>
          <PatchTeaser {config} {patch} />
modified src/base/projects/PeerSelector.spec.ts
@@ -6,17 +6,17 @@ const defaultProps = {
  peer: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
  peers: [
    {
-
      "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
-
      "person": { "name": "sebastinez" },
-
      "delegate": true
-
    }
+
      id: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
      person: { name: "sebastinez" },
+
      delegate: true,
+
    },
  ],
};

-
describe('Logic', () => {
+
describe("Logic", () => {
  it("show delegate name and badge", () => {
    render(PeerSelector, {
-
      props: defaultProps
+
      props: defaultProps,
    });
    cy.get("span.peer-id").should("have.text", "sebastinez");
    cy.get("span.badge.primary").should("have.text", "delegate");
@@ -28,11 +28,11 @@ describe('Logic', () => {
        ...defaultProps,
        peers: [
          {
-
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
-
            "delegate": true
-
          }
+
            id: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
            delegate: true,
+
          },
        ],
-
      }
+
      },
    });
    cy.get("span.peer-id").should("have.text", "hyyg55…p7ofue");
    cy.get("span.badge.primary").should("have.text", "delegate");
@@ -44,10 +44,10 @@ describe('Logic', () => {
        ...defaultProps,
        peers: [
          {
-
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
-
          }
-
        ]
-
      }
+
            id: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
          },
+
        ],
+
      },
    });
    cy.get("span.peer-id").should("have.text", "hyyg55…p7ofue");
  });
@@ -56,14 +56,14 @@ describe('Logic', () => {
describe("Layout", () => {
  it("should highlight the current peer", () => {
    render(PeerSelector, {
-
      props: { ...defaultProps }
+
      props: { ...defaultProps },
    });
    cy.get("div.selector").click();
    cy.get("div.dropdown-item").should("have.class", "selected");
  });
});

-
describe('Events', () => {
+
describe("Events", () => {
  it("dispatch peerChanged event if clicking on a peer", () => {
    cy.viewport("macbook-13");
    const { getByText, component } = render(PeerSelector, {
@@ -71,26 +71,28 @@ describe('Events', () => {
        ...defaultProps,
        peers: [
          {
-
            "id": "hyy841u4phudmr8s5rg1jjwd1ct7x7438wmjwtsm464y8uyxyhyi6c",
-
            "person": { "name": "cloudhead" },
-
            "delegate": true
+
            id: "hyy841u4phudmr8s5rg1jjwd1ct7x7438wmjwtsm464y8uyxyhyi6c",
+
            person: { name: "cloudhead" },
+
            delegate: true,
          },
          {
-
            "id": "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
-
            "person": { "name": "sebastinez" },
-
            "delegate": true
-
          }
-
        ]
-
      }
+
            id: "hyyg555wwkkutaysg6yr67qnu5d5ji54iur3n5uzzszndh8dp7ofue",
+
            person: { name: "sebastinez" },
+
            delegate: true,
+
          },
+
        ],
+
      },
    });

-
    cy.get("div.selector").click().then(() => {
-
      const peer = getByText("cloudhead");
-
      const mock = cy.spy();
-
      component.$on("peerChanged", mock);
+
    cy.get("div.selector")
+
      .click()
+
      .then(() => {
+
        const peer = getByText("cloudhead");
+
        const mock = cy.spy();
+
        component.$on("peerChanged", mock);

-
      fireEvent.click(peer);
-
      expect(mock).to.have.been.calledOnce;
-
    });
+
        fireEvent.click(peer);
+
        expect(mock).to.have.been.calledOnce;
+
      });
  });
});
modified src/base/projects/PeerSelector.svelte
@@ -12,20 +12,36 @@

  let meta: Peer | undefined;
  // List of items to be created for the Dropdown component.
-
  let items: { key: string; value: string; title: string; badge: string | null }[] = [];
+
  let items: {
+
    key: string;
+
    value: string;
+
    title: string;
+
    badge: string | null;
+
  }[] = [];

  function createTitle(p: Peer): string {
    const name = p.person?.name ? p.person.name : p.id;
-
    return p.delegate ? `${name} is a delegate of this project` : `${name} is a peer tracked by this seed`;
+
    return p.delegate
+
      ? `${name} is a delegate of this project`
+
      : `${name} is a peer tracked by this seed`;
  }

  onMount(() => {
    meta = peers.find(p => p.id === peer);
    items = peers.map(p => {
-
      if (! p.person?.name) console.debug("Not able to resolve peer identity for: ", p.id);
-
      const key = p.person?.name ? `<strong>${p.person.name}</strong> ${p.id}` : p.id;
+
      if (!p.person?.name) {
+
        console.debug("Not able to resolve peer identity for: ", p.id);
+
      }
+
      const key = p.person?.name
+
        ? `<strong>${p.person.name}</strong> ${p.id}`
+
        : p.id;

-
      return { key, value: p.id, title: createTitle(p), badge: p.delegate ? "delegate" : null };
+
      return {
+
        key,
+
        value: p.id,
+
        title: createTitle(p),
+
        badge: p.delegate ? "delegate" : null,
+
      };
    });
  });

@@ -80,7 +96,7 @@
        {#if meta.delegate}
          <Badge variant="primary">delegate</Badge>
        {/if}
-
      <!-- If the delegate metadata is not found -->
+
        <!-- If the delegate metadata is not found -->
      {:else if peer}
        <span class="peer-id">
          {formatSeedId(peer)}
@@ -90,10 +106,6 @@
  </div>

  <svelte:fragment slot="modal">
-
    <Dropdown
-
      {items}
-
      selected={peer}
-
      on:select={(e) => switchPeer(e.detail)}
-
    />
+
    <Dropdown {items} selected={peer} on:select={e => switchPeer(e.detail)} />
  </svelte:fragment>
</Floating>
modified src/base/projects/Project.svelte
@@ -1,27 +1,27 @@
<script lang="ts">
-
  import type { Config } from '@app/config';
-
  import type { State as IssueState } from './Issues.svelte';
+
  import type { Config } from "@app/config";
+
  import type { State as IssueState } from "./Issues.svelte";

-
  import * as proj from '@app/project';
-
  import Placeholder from '@app/Placeholder.svelte';
-
  import Loading from '@app/Loading.svelte';
-
  import { formatProfile, formatSeedId, setOpenGraphMetaTag } from '@app/utils';
-
  import { browserStore } from '@app/project';
-
  import { fetchCommits } from '@app/commit';
+
  import * as proj from "@app/project";
+
  import Placeholder from "@app/Placeholder.svelte";
+
  import Loading from "@app/Loading.svelte";
+
  import { formatProfile, formatSeedId, setOpenGraphMetaTag } from "@app/utils";
+
  import { browserStore } from "@app/project";
+
  import { fetchCommits } from "@app/commit";
  import * as patch from "@app/patch";
  import * as issue from "@app/issue";

-
  import Header from '@app/base/projects/Header.svelte';
-
  import Async from '@app/Async.svelte';
+
  import Header from "@app/base/projects/Header.svelte";
+
  import Async from "@app/Async.svelte";

  import Browser from "./Browser.svelte";
  import Commit from "./Commit.svelte";
  import History from "./History.svelte";
-
  import Issues from './Issues.svelte';
-
  import Issue from './Issue.svelte';
-
  import ProjectMeta from './ProjectMeta.svelte';
-
  import Patches from './Patches.svelte';
-
  import Patch from './Patch.svelte';
+
  import Issues from "./Issues.svelte";
+
  import Issue from "./Issue.svelte";
+
  import ProjectMeta from "./ProjectMeta.svelte";
+
  import Patches from "./Patches.svelte";
+
  import Patch from "./Patch.svelte";

  export let peer: string | null = null;
  export let config: Config;
@@ -29,14 +29,14 @@
  export let content: proj.ProjectContent;
  export let revision: string | null;

-
  const parentName = project.profile ? formatProfile(project.profile.nameOrAddress, config) : null;
+
  const parentName = project.profile
+
    ? formatProfile(project.profile.nameOrAddress, config)
+
    : null;
  let pageTitle = parentName ? `${parentName}/${project.name}` : project.name;

-
  $: issueFilter = $browserStore.search?.get("state") as IssueState ?? "open";
+
  $: issueFilter = ($browserStore.search?.get("state") as IssueState) ?? "open";

-
  const baseName = parentName
-
    ? `${parentName}/${project.name}`
-
    : project.name;
+
  const baseName = parentName ? `${parentName}/${project.name}` : project.name;

  if (project.description) {
    pageTitle = `${baseName}: ${project.description}`;
@@ -47,7 +47,7 @@
  setOpenGraphMetaTag([
    { prop: "og:title", content: project.name },
    { prop: "og:description", content: project.description },
-
    { prop: "og:url", content: window.location.href }
+
    { prop: "og:url", content: window.location.href },
  ]);
</script>

@@ -66,7 +66,10 @@
  <title>{pageTitle}</title>
</svelte:head>

-
<ProjectMeta noDescription={content !== proj.ProjectContent.Tree} {project} {peer} />
+
<ProjectMeta
+
  noDescription={content !== proj.ProjectContent.Tree}
+
  {project}
+
  {peer} />

{#if revision}
  {#await project.getRoot(revision)}
@@ -90,25 +93,43 @@
      <div class="error error-message text-xsmall">
        <!-- TODO: Differentiate between (1) commit doesn't exist and (2) failed
             to fetch - this needs a change to the backend. -->
-
        API request to <code class="text-xsmall">{err.url}</code> failed
+
        API request to
+
        <code class="text-xsmall">{err.url}</code>
+
        failed
      </div>
    </div>
  {/await}

  {#if content === proj.ProjectContent.Issues}
-
    <Async fetch={issue.Issue.getIssues(project.urn, project.seed.api)} let:result>
+
    <Async
+
      fetch={issue.Issue.getIssues(project.urn, project.seed.api)}
+
      let:result>
      <Issues {project} state={issueFilter} {config} issues={result} />
    </Async>
  {:else if content === proj.ProjectContent.Issue && $browserStore.issue}
-
    <Async fetch={issue.Issue.getIssue(project.urn, $browserStore.issue, project.seed.api)} let:result>
+
    <Async
+
      fetch={issue.Issue.getIssue(
+
        project.urn,
+
        $browserStore.issue,
+
        project.seed.api,
+
      )}
+
      let:result>
      <Issue {project} {config} issue={result} />
    </Async>
  {:else if content === proj.ProjectContent.Patches}
-
    <Async fetch={patch.Patch.getPatches(project.urn, project.seed.api)} let:result>
+
    <Async
+
      fetch={patch.Patch.getPatches(project.urn, project.seed.api)}
+
      let:result>
      <Patches {project} {config} patches={result} />
    </Async>
  {:else if content === proj.ProjectContent.Patch && $browserStore.patch}
-
    <Async fetch={patch.Patch.getPatch(project.urn, $browserStore.patch, project.seed.api)} let:result>
+
    <Async
+
      fetch={patch.Patch.getPatch(
+
        project.urn,
+
        $browserStore.patch,
+
        project.seed.api,
+
      )}
+
      let:result>
      <Patch {project} {config} patch={result} />
    </Async>
  {/if}
modified src/base/projects/ProjectMeta.svelte
@@ -37,6 +37,8 @@
  .title .peer-id {
    color: var(--color-foreground-subtle);
    font-weight: normal;
+
    display: flex;
+
    align-items: center;
  }
  .org-avatar {
    display: inline-block;
@@ -74,8 +76,13 @@
<header class="content">
  <div class="title bold">
    {#if project.profile}
-
      <a class="org-avatar" title={project.profile.nameOrAddress} href="/{project.profile.nameOrAddress}">
-
        <Avatar source={project.profile.avatar || project.profile.address} title={project.profile.address}/>
+
      <a
+
        class="org-avatar"
+
        title={project.profile.nameOrAddress}
+
        href="/{project.profile.nameOrAddress}">
+
        <Avatar
+
          source={project.profile.avatar || project.profile.address}
+
          title={project.profile.address} />
      </a>
      <span class="divider">/</span>
    {/if}
@@ -84,7 +91,9 @@
    </span>
    {#if peer}
      <span class="peer-id">
-
        <span class="divider">/</span><span title={peer}>{formatSeedId(peer)}</span><Clipboard text={peer} />
+
        <span class="divider">/</span>
+
        <span title={peer}>{formatSeedId(peer)}</span>
+
        <Clipboard text={peer} />
      </span>
    {/if}
  </div>
modified src/base/projects/ProjectRoute.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
-
  import type { Writable } from 'svelte/store';
+
  import type { Writable } from "svelte/store";
  import type { Config } from "@app/config";
-
  import { formatLocationHash } from '@app/utils';
-
  import * as proj from '@app/project';
+
  import { formatLocationHash } from "@app/utils";
+
  import * as proj from "@app/project";
  import type { RouteLocation } from "@app/index";

-
  import Project from '@app/base/projects/Project.svelte';
+
  import Project from "@app/base/projects/Project.svelte";

  export let browserStore: Writable<proj.Browser> = proj.browserStore;
  export let route: string | null = null;
@@ -56,5 +56,4 @@
  revision={browser.revision || head}
  content={browser.content}
  {project}
-
  {config}
-
/>
+
  {config} />
modified src/base/projects/Readme.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
-
  import Markdown from '@app/Markdown.svelte';
-
  import type * as proj from '@app/project';
+
  import Markdown from "@app/Markdown.svelte";
+
  import type * as proj from "@app/project";

  export let content: string;
  export let getImage: (path: string) => Promise<proj.Blob>;
modified src/base/projects/Routes.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import { Route } from "svelte-routing";
-
  import View from '@app/base/projects/View.svelte';
-
  import type { Config } from '@app/config';
+
  import View from "@app/base/projects/View.svelte";
+
  import type { Config } from "@app/config";
  import Redirect from "@app/Redirect.svelte";

  export let config: Config;
@@ -19,16 +19,20 @@

<!-- Explicit user and org context, will at some point be replaced by the generic route -->
<Route path="/orgs/:addressOrName/projects/:id/*" let:params>
-
  <Redirect to="/{params.addressOrName}/{params.id}/{params["*"]}" />
+
  <Redirect to="/{params.addressOrName}/{params.id}/{params['*']}" />
</Route>

<Route path="/users/:addressOrName/projects/:id/*" let:params>
-
  <Redirect to="/{params.addressOrName}/{params.id}/{params["*"]}" />
+
  <Redirect to="/{params.addressOrName}/{params.id}/{params['*']}" />
</Route>
<!-- End of eventual dropped routes -->

<Route path="/:profile/:id/remotes/:peer/*" let:params>
-
  <View {config} profileName={params.profile} id={params.id} peer={params.peer} />
+
  <View
+
    {config}
+
    profileName={params.profile}
+
    id={params.id}
+
    peer={params.peer} />
</Route>

<Route path="/:profile/:id/*" let:params>
modified src/base/projects/SourceBrowser/Changeset.svelte
@@ -38,9 +38,9 @@
<div class="changeset-summary">
  <span>{diffDescription(diff)}</span>
  with
-
  <span class="additions"> {stats.additions} addition(s) </span>
+
  <span class="additions">{stats.additions} addition(s)</span>
  and
-
  <span class="deletions"> {stats.deletions} deletion(s) </span>
+
  <span class="deletions">{stats.deletions} deletion(s)</span>
</div>
<div class="diff-listing">
  {#each diff.created as file}
modified src/base/projects/SourceBrowser/FileDiff.svelte
@@ -126,7 +126,9 @@
        <Badge variant="negative">deleted</Badge>
      {/if}
    </div>
-
    <div class="browse clickable" on:click|stopPropagation={() => dispatch("browse", file.path)}>
+
    <div
+
      class="browse clickable"
+
      on:click|stopPropagation={() => dispatch("browse", file.path)}>
      <span title="View file" style="transform: scale(1.25);">
        <Icon name="browse" />
      </span>
@@ -145,14 +147,10 @@
            </tr>
            {#each hunk.lines as line}
              <tr class="diff-line" data-expanded data-type={lineSign(line)}>
-
                <td
-
                  class="diff-line-number left"
-
                  data-type={lineSign(line)}>
+
                <td class="diff-line-number left" data-type={lineSign(line)}>
                  {lineNumberL(line)}
                </td>
-
                <td
-
                  class="diff-line-number right"
-
                  data-type={lineSign(line)}>
+
                <td class="diff-line-number right" data-type={lineSign(line)}>
                  {lineNumberR(line)}
                </td>
                <td class="diff-line-type" data-type={line.type}>
modified src/base/projects/Tree.svelte
@@ -4,8 +4,8 @@
  import type { Tree } from "@app/project";
  import { ObjectType } from "@app/project";

-
  import File from './Tree/File.svelte';
-
  import Folder from './Tree/Folder.svelte';
+
  import File from "./Tree/File.svelte";
+
  import Folder from "./Tree/Folder.svelte";

  export let fetchTree: (path: string) => Promise<Tree>;
  export let path: string;
@@ -26,14 +26,12 @@
      name={entry.info.name}
      prefix={`${entry.path}/`}
      currentPath={path}
-
      on:select={onSelect}
-
    />
+
      on:select={onSelect} />
  {:else}
    <File
      active={entry.path === path}
      loading={entry.path === loadingPath}
      name={entry.info.name}
-
      on:click={() => onSelect({ detail: entry.path })}
-
    />
+
      on:click={() => onSelect({ detail: entry.path })} />
  {/if}
{/each}
modified src/base/projects/Tree/File.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import Loading from '@app/Loading.svelte';
+
  import Loading from "@app/Loading.svelte";

  export let active: boolean;
  export let loading: boolean;
modified src/base/projects/Tree/Folder.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import { createEventDispatcher } from "svelte";

-
  import Loading from '@app/Loading.svelte';
+
  import Loading from "@app/Loading.svelte";
  import type { Tree } from "@app/project";
  import { ObjectType } from "@app/project";

modified src/base/projects/View.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
-
  import type { Config } from '@app/config';
+
  import type { Config } from "@app/config";
  import { Route, Router } from "svelte-routing";
-
  import { Project, ProjectContent } from '@app/project';
-
  import Loading from '@app/Loading.svelte';
-
  import NotFound from '@app/NotFound.svelte';
+
  import { Project, ProjectContent } from "@app/project";
+
  import Loading from "@app/Loading.svelte";
+
  import NotFound from "@app/NotFound.svelte";

  import ProjectRoute from "./ProjectRoute.svelte";

@@ -51,35 +51,82 @@
        <ProjectRoute content={ProjectContent.Tree} {peer} {project} {config} />
      </Route>
      <Route path="/tree/*" let:params let:location>
-
        <ProjectRoute route={params["*"]} content={ProjectContent.Tree} {location} {peer} {project} {config} />
+
        <ProjectRoute
+
          route={params["*"]}
+
          content={ProjectContent.Tree}
+
          {location}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>

      <Route path="/history">
-
        <ProjectRoute content={ProjectContent.History} {peer} {project} {config} />
+
        <ProjectRoute
+
          content={ProjectContent.History}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>
      <Route path="/history/*" let:params let:location>
-
        <ProjectRoute route={params["*"]} content={ProjectContent.History} {location} {peer} {project} {config} />
+
        <ProjectRoute
+
          route={params["*"]}
+
          content={ProjectContent.History}
+
          {location}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>

      <Route path="/commits/:commit" let:params>
-
        <ProjectRoute revision={params.commit} content={ProjectContent.Commit} {peer} {project} {config} />
+
        <ProjectRoute
+
          revision={params.commit}
+
          content={ProjectContent.Commit}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>
      <Route path="/commits/*" let:params let:location>
-
        <ProjectRoute route={params["*"]} content={ProjectContent.Commit} {location} {peer} {project} {config} />
+
        <ProjectRoute
+
          route={params["*"]}
+
          content={ProjectContent.Commit}
+
          {location}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>

      <Route path="/issues" let:location>
-
        <ProjectRoute content={ProjectContent.Issues} {peer} {project} {location} {config} />
+
        <ProjectRoute
+
          content={ProjectContent.Issues}
+
          {peer}
+
          {project}
+
          {location}
+
          {config} />
      </Route>
      <Route path="/issues/:issue" let:params let:location>
-
        <ProjectRoute content={ProjectContent.Issue} issue={params.issue} {peer} {project} {location} {config} />
+
        <ProjectRoute
+
          content={ProjectContent.Issue}
+
          issue={params.issue}
+
          {peer}
+
          {project}
+
          {location}
+
          {config} />
      </Route>

      <Route path="/patches">
-
        <ProjectRoute content={ProjectContent.Patches} {peer} {project} {config} />
+
        <ProjectRoute
+
          content={ProjectContent.Patches}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>
      <Route path="/patches/:patch" let:params>
-
        <ProjectRoute content={ProjectContent.Patch} patch={params.patch} {peer} {project} {config} />
+
        <ProjectRoute
+
          content={ProjectContent.Patch}
+
          patch={params.patch}
+
          {peer}
+
          {project}
+
          {config} />
      </Route>
    </Router>
  {:catch}
modified src/base/projects/Widget.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
-
  import type * as proj from '@app/project';
-
  import AnchorBadge from '@app/base/profiles/AnchorBadge.svelte';
-
  import Diagram from '@app/Diagram.svelte';
-
  import { groupCommitsByWeek } from '@app/commit';
-
  import type { Host } from '@app/api';
-
  import { Project } from '@app/project';
-
  import { formatCommit } from '@app/utils';
+
  import type * as proj from "@app/project";
+
  import AnchorBadge from "@app/base/profiles/AnchorBadge.svelte";
+
  import Diagram from "@app/Diagram.svelte";
+
  import { groupCommitsByWeek } from "@app/commit";
+
  import type { Host } from "@app/api";
+
  import { Project } from "@app/project";
+
  import { formatCommit } from "@app/utils";

  export let project: proj.ProjectInfo;
  export let seed: { api: Host };
@@ -101,13 +101,15 @@
  article .actions {
    margin-right: 1rem;
  }
-
  article .commit, article .actions {
+
  article .commit,
+
  article .actions {
    font-family: var(--font-family-monospace);
  }
  article.project-faded .anchor {
    color: var(--color-foreground-faded);
  }
-
  article .id, article .anchor {
+
  article .id,
+
  article .anchor {
    display: flex;
    justify-content: space-between;
  }
@@ -165,15 +167,16 @@
      <div class="anchor">
        <span class="anchor-info">
          <span class="actions">
-
            <slot name="actions">
-
            </slot>
+
            <slot name="actions" />
          </span>
          <span class="anchor-badge">
            <slot name="anchor">
              {#if anchor && project.head}
                <AnchorBadge
                  commit={project.head}
-
                  head={project.head} noText noBg
+
                  head={project.head}
+
                  noText
+
                  noBg
                  anchors={[anchor.anchor.stateHash]} />
              {/if}
            </slot>
@@ -182,11 +185,11 @@
      </div>
      {#await loadCommits() then points}
        <div class="desktop activity">
-
          <Diagram {points}
+
          <Diagram
+
            {points}
            strokeWidth={3}
            viewBoxHeight={100}
-
            viewBoxWidth={600}
-
          />
+
            viewBoxWidth={600} />
        </div>
      {/await}
    </div>
modified src/base/registrations/Index.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
  import { navigate } from "svelte-routing";
-
  import type { Config } from '@app/config';
+
  import type { Config } from "@app/config";

-
  import DomainInput from '@app/ens/DomainInput.svelte';
+
  import DomainInput from "@app/ens/DomainInput.svelte";

  export let config: Config;

@@ -68,9 +68,12 @@
<main class="off-centered">
  <div>
    <div class="input-caption">
-
      Register a <strong>{config.registrar.domain}</strong> name
+
      Register a <strong>{config.registrar.domain}</strong>
+
      name
      <div class="text-small explainer">
-
        Register a unique name with our ENS registrar, under the <strong>radicle.eth</strong>
+
        Register a unique name with our ENS registrar, under the <strong>
+
          radicle.eth
+
        </strong>
        domain (e.g. cloudhead.radicle.eth). Radicle names never expire and free
        to register.
      </div>
@@ -81,8 +84,7 @@
          bind:value={input}
          autofocus
          placeholder=""
-
          root={config.registrar.domain}
-
        />
+
          root={config.registrar.domain} />
        {#if errors}
          <div class="input-info">
            {#each errors as error}
@@ -92,12 +94,12 @@
        {/if}
      </span>

-
        <button
-
          disabled={!label || errors.length !== 0}
-
          class="primary register regular"
-
          on:click={register}>
-
            Check
-
        </button>
+
      <button
+
        disabled={!label || errors.length !== 0}
+
        class="primary register regular"
+
        on:click={register}>
+
        Check
+
      </button>
    </div>
  </div>
</main>
modified src/base/registrations/New.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { navigate } from 'svelte-routing';
-
  import { formatAddress } from '@app/utils';
-
  import { session } from '@app/session';
-
  import type { Config } from '@app/config';
+
  import { onMount } from "svelte";
+
  import { navigate } from "svelte-routing";
+
  import { formatAddress } from "@app/utils";
+
  import { session } from "@app/session";
+
  import type { Config } from "@app/config";

-
  import Connect from '@app/Connect.svelte';
-
  import Modal from '@app/Modal.svelte';
-
  import Loading from '@app/Loading.svelte';
-
  import Message from '@app/Message.svelte';
+
  import Connect from "@app/Connect.svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import Loading from "@app/Loading.svelte";
+
  import Message from "@app/Message.svelte";

-
  import { registrar } from './registrar';
+
  import { registrar } from "./registrar";

  enum State {
    CheckingAvailability,
@@ -31,9 +31,13 @@
  $: registrationOwner = owner || ($session && $session.address);

  function begin() {
-
    navigate(`/registrations/${name}/submit?${
-
      registrationOwner ? new URLSearchParams({ owner: registrationOwner }) : ''
-
    }`);
+
    navigate(
+
      `/registrations/${name}/submit?${
+
        registrationOwner
+
          ? new URLSearchParams({ owner: registrationOwner })
+
          : ""
+
      }`,
+
    );
  }

  onMount(async () => {
@@ -69,18 +73,23 @@
  <span slot="body">
    {#if state === State.NameAvailable}
      {#if registrationOwner}
-
        The name <strong>{name}</strong> is available for registration
-
        under account <strong>{formatAddress(registrationOwner)}</strong>.
+
        The name <strong>{name}</strong>
+
        is available for registration under account
+
        <strong>{formatAddress(registrationOwner)}</strong>
+
        .
      {:else}
-
        The name <strong>{name}</strong> is available.
+
        The name <strong>{name}</strong>
+
        is available.
      {/if}
    {:else if state === State.NameUnavailable}
-
      This name is <strong>not available</strong> for registration.
+
      This name is <strong>not available</strong>
+
      for registration.
    {:else if state === State.CheckingAvailability}
      <Loading small center />
    {:else if state === State.CheckingFailed && error}
      <Message error>
-
        <strong>Error:</strong> {error}
+
        <strong>Error:</strong>
+
        {error}
      </Message>
    {/if}
  </span>
@@ -99,9 +108,7 @@
        Cancel
      </button>
    {:else if state === State.NameUnavailable || state === State.CheckingFailed}
-
      <button on:click={() => navigate("/registrations")} class="">
-
        Back
-
      </button>
+
      <button on:click={() => navigate("/registrations")} class="">Back</button>
    {/if}
  </span>
</Modal>
modified src/base/registrations/Routes.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
  import { Route, navigate } from "svelte-routing";
-
  import Index from '@app/base/registrations/Index.svelte';
-
  import New from '@app/base/registrations/New.svelte';
-
  import Submit from '@app/base/registrations/Submit.svelte';
-
  import View from '@app/base/registrations/View.svelte';
-
  import Error from '@app/Error.svelte';
-
  import type { Config } from '@app/config';
-
  import type { Session } from '@app/session';
-
  import { getSearchParam } from '@app/utils';
+
  import Index from "@app/base/registrations/Index.svelte";
+
  import New from "@app/base/registrations/New.svelte";
+
  import Submit from "@app/base/registrations/Submit.svelte";
+
  import View from "@app/base/registrations/View.svelte";
+
  import Error from "@app/Error.svelte";
+
  import type { Config } from "@app/config";
+
  import type { Session } from "@app/session";
+
  import { getSearchParam } from "@app/utils";

  export let session: Session | null;
  export let config: Config;
@@ -23,12 +23,15 @@

<Route path="registrations/:name/submit" let:params let:location>
  {#if session}
-
    <Submit {config} name={params.name} owner={getSearchParam("owner", location)} {session} />
+
    <Submit
+
      {config}
+
      name={params.name}
+
      owner={getSearchParam("owner", location)}
+
      {session} />
  {:else}
    <Error
      message={"You must connect your wallet to register"}
-
      on:close={() => navigate("/registrations")}
-
    />
+
      on:close={() => navigate("/registrations")} />
  {/if}
</Route>

modified src/base/registrations/Submit.svelte
@@ -1,16 +1,16 @@
<script lang="ts">
  // TODO: When name is registered, prompt user to edit records.
  // TODO: When transfering name, warn about transfering to org.
-
  import { onMount } from 'svelte';
-
  import { navigate } from 'svelte-routing';
-
  import type { Session } from '@app/session';
-
  import type { Config } from '@app/config';
-
  import Loading from '@app/Loading.svelte';
-
  import Modal from '@app/Modal.svelte';
-
  import Err from '@app/Error.svelte';
+
  import { onMount } from "svelte";
+
  import { navigate } from "svelte-routing";
+
  import type { Session } from "@app/session";
+
  import type { Config } from "@app/config";
+
  import Loading from "@app/Loading.svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import Err from "@app/Error.svelte";
  import BlockTimer from "@app/BlockTimer.svelte";

-
  import { registerName, State, state } from './registrar';
+
  import { registerName, State, state } from "./registrar";

  export let config: Config;
  export let name: string;
@@ -20,7 +20,8 @@
  let error: Error | null = null;
  const registrationOwner = owner || session.address;

-
  const view = () => navigate(`/registrations/${name}.radicle.eth`, { state: { retry: true } });
+
  const view = () =>
+
    navigate(`/registrations/${name}.radicle.eth`, { state: { retry: true } });

  onMount(async () => {
    try {
@@ -50,8 +51,7 @@
  <Err
    title="Transaction failed"
    message={error.message}
-
    on:close={() => navigate('/registrations')}
-
  />
+
    on:close={() => navigate("/registrations")} />
{:else}
  <Modal>
    <span slot="title">
@@ -69,15 +69,18 @@
      {:else if $state.connection === State.SigningPermit}
        Approving registration fee. Please confirm in your wallet.
      {:else if $state.connection === State.SigningCommit}
-
        Committing to <strong>{name}</strong>. Please confirm transaction in your wallet.
+
        Committing to <strong>{name}</strong>
+
        . Please confirm transaction in your wallet.
      {:else if $state.connection === State.Committing}
-
        Waiting for <strong>commit</strong> transaction to be processed&hellip;
+
        Waiting for <strong>commit</strong>
+
        transaction to be processed&hellip;
      {:else if $state.connection === State.WaitingToRegister && $state.commitmentBlock}
        Waiting for commitment to mature. This may take a moment.
      {:else if $state.connection === State.SigningRegister}
        Proceeding with registration. Please confirm transaction in your wallet.
      {:else if $state.connection === State.Registering}
-
        Waiting for <strong>register</strong> transaction to be processed&hellip;
+
        Waiting for <strong>register</strong>
+
        transaction to be processed&hellip;
      {/if}
    </span>

@@ -86,7 +89,10 @@
        This name has been successfully registered to
        <span class="highlight">{registrationOwner}</span>
      {:else if $state.connection === State.WaitingToRegister && $state.commitmentBlock}
-
        <BlockTimer {config} startBlock={$state.commitmentBlock} duration={$state.minAge} />
+
        <BlockTimer
+
          {config}
+
          startBlock={$state.commitmentBlock}
+
          duration={$state.minAge} />
      {:else}
        <Loading small center />
      {/if}
@@ -94,9 +100,7 @@

    <span slot="actions">
      {#if $state.connection === State.Registered}
-
        <button on:click={view} class="register">
-
          View
-
        </button>
+
        <button on:click={view} class="register">View</button>
      {/if}
    </span>
  </Modal>
modified src/base/registrations/Update.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
-
  import { onMount, createEventDispatcher } from 'svelte';
-
  import { setRecords } from './resolver';
-
  import type { EnsRecord } from './resolver';
-
  import type { Registration } from './registrar';
-
  import type { Config } from '@app/config';
-
  import Loading from '@app/Loading.svelte';
-
  import Modal from '@app/Modal.svelte';
+
  import { onMount, createEventDispatcher } from "svelte";
+
  import { setRecords } from "./resolver";
+
  import type { EnsRecord } from "./resolver";
+
  import type { Registration } from "./registrar";
+
  import type { Config } from "@app/config";
+
  import Loading from "@app/Loading.svelte";
+
  import Modal from "@app/Modal.svelte";
  import { Status, State } from "@app/utils";

  export let domain: string;
@@ -15,12 +15,20 @@

  const dispatch = createEventDispatcher();

-
  let state: State = { status: Status.Failed, error: "Error registering, something happened." };
+
  let state: State = {
+
    status: Status.Failed,
+
    error: "Error registering, something happened.",
+
  };

  onMount(async () => {
    try {
      state.status = Status.Signing;
-
      const tx = await setRecords(domain, records, registration.resolver, config);
+
      const tx = await setRecords(
+
        domain,
+
        records,
+
        registration.resolver,
+
        config,
+
      );
      state.status = Status.Pending;
      await tx.wait();
      state.status = Status.Success;
@@ -36,7 +44,7 @@
  };

  const onClose = () => {
-
    dispatch('close');
+
    dispatch("close");
  };
</script>

@@ -54,7 +62,8 @@
      <p>Your registration was successfully updated.</p>
    {:else if state.status === Status.Failed}
      <p class="error">
-
        <strong>Error:</strong> {state.error}
+
        <strong>Error:</strong>
+
        {state.error}
      </p>
    {/if}
  </span>
@@ -62,13 +71,9 @@
    {#if [Status.Signing, Status.Pending].includes(state.status)}
      <Loading center small />
    {:else if state.status === Status.Success}
-
      <button on:click={onDone}>
-
        Done
-
      </button>
+
      <button on:click={onDone}>Done</button>
    {:else if state.status === Status.Failed}
-
      <button on:click={onClose}>
-
        Close
-
      </button>
+
      <button on:click={onClose}>Close</button>
    {/if}
  </span>
</Modal>
modified src/base/registrations/View.svelte
@@ -1,22 +1,22 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { navigate } from 'svelte-routing';
-
  import type { Config } from '@app/config';
+
  import { onMount } from "svelte";
+
  import { navigate } from "svelte-routing";
+
  import type { Config } from "@app/config";
  import type { ethers } from "ethers";
-
  import { session } from '@app/session';
-
  import Loading from '@app/Loading.svelte';
-
  import Link from '@app/Link.svelte';
-
  import Modal from '@app/Modal.svelte';
-
  import Form from '@app/Form.svelte';
-
  import type { Field } from '@app/Form.svelte';
-
  import { assert } from '@app/error';
-
  import Error from '@app/Error.svelte';
-
  import { isAddressEqual, isReverseRecordSet } from '@app/utils';
-

-
  import { getRegistration, getOwner } from './registrar';
-
  import type { EnsRecord } from './resolver';
-
  import type { Registration } from './registrar';
-
  import Update from './Update.svelte';
+
  import { session } from "@app/session";
+
  import Loading from "@app/Loading.svelte";
+
  import Link from "@app/Link.svelte";
+
  import Modal from "@app/Modal.svelte";
+
  import Form from "@app/Form.svelte";
+
  import type { Field } from "@app/Form.svelte";
+
  import { assert } from "@app/error";
+
  import Error from "@app/Error.svelte";
+
  import { isAddressEqual, isReverseRecordSet } from "@app/utils";
+

+
  import { getRegistration, getOwner } from "./registrar";
+
  import type { EnsRecord } from "./resolver";
+
  import type { Registration } from "./registrar";
+
  import Update from "./Update.svelte";

  enum Status {
    Loading,
@@ -26,7 +26,7 @@
  }

  type State =
-
      { status: Status.Loading }
+
    | { status: Status.Loading }
    | { status: Status.NotFound }
    | { status: Status.Found; registration: Registration; owner: string }
    | { status: Status.Failed; error: string };
@@ -43,56 +43,122 @@
  let retries = 3;
  let resolver: ethers.providers.EnsResolver | undefined = undefined;

-
  async function parseRecords(r: Registration | null): Promise<Registration | null> {
+
  async function parseRecords(
+
    r: Registration | null,
+
  ): Promise<Registration | null> {
    if (r) {
      let reverseRecord = false;
      if (r.profile.address) {
-
        reverseRecord = await isReverseRecordSet(r.profile.address, domain, config);
+
        reverseRecord = await isReverseRecordSet(
+
          r.profile.address,
+
          domain,
+
          config,
+
        );
      }
      const owner = await getOwner(domain, config);
      resolver = r.resolver;

      fields = [
-
        { name: "owner", validate: "address", placeholder: "",
+
        {
+
          name: "owner",
+
          validate: "address",
+
          placeholder: "",
          description: "The owner and controller of this name.",
-
          value: owner, resolve: true, editable: false },
-
        { name: "address", validate: "address", placeholder: "Ethereum address, eg. 0x4a9cf21...bc91",
-
          description: "The address this name resolves to. " + (
-
            reverseRecord
+
          value: owner,
+
          resolve: true,
+
          editable: false,
+
        },
+
        {
+
          name: "address",
+
          validate: "address",
+
          placeholder: "Ethereum address, eg. 0x4a9cf21...bc91",
+
          description:
+
            "The address this name resolves to. " +
+
            (reverseRecord
              ? `The reverse record for this address is set to **${domain}**`
-
              : "The reverse record for this address is **not set**. "
-
              + "For this name to be correctly associated with the address, "
-
              + "a reverse record should be set."
-
          ),
-
          value: r.profile.address, editable: true },
-
        { name: "url", label: "URL", validate: "URL", placeholder: "https://acme.org",
+
              : "The reverse record for this address is **not set**. " +
+
                "For this name to be correctly associated with the address, " +
+
                "a reverse record should be set."),
+
          value: r.profile.address,
+
          editable: true,
+
        },
+
        {
+
          name: "url",
+
          label: "URL",
+
          validate: "URL",
+
          placeholder: "https://acme.org",
          description: "A homepage or other URL associated with this name.",
-
          value: r.profile.url,editable: true },
-
        { name: "avatar", validate: "URL", placeholder: "https://acme.org/avatar.png",
+
          value: r.profile.url,
+
          editable: true,
+
        },
+
        {
+
          name: "avatar",
+
          validate: "URL",
+
          placeholder: "https://acme.org/avatar.png",
          description: "An avatar or square image associated with this name.",
-
          value: r.profile.avatar, editable: true },
-
        { name: "twitter", validate: "handle", placeholder: "Twitter username, eg. 'acme'",
+
          value: r.profile.avatar,
+
          editable: true,
+
        },
+
        {
+
          name: "twitter",
+
          validate: "handle",
+
          placeholder: "Twitter username, eg. 'acme'",
          description: "The Twitter handle associated with this name.",
-
          value: r.profile.twitter, editable: true },
-
        { name: "github", validate: "handle", label: "GitHub", placeholder: "GitHub username, eg. 'acme'",
+
          value: r.profile.twitter,
+
          editable: true,
+
        },
+
        {
+
          name: "github",
+
          validate: "handle",
+
          label: "GitHub",
+
          placeholder: "GitHub username, eg. 'acme'",
          description: "The GitHub username associated with this name.",
-
          value: r.profile.github, editable: true },
-
        { name: "id", label: "Radicle", validate: "identity", placeholder: "Radicle URN, eg. rad:git:hnrkqdpm9ub19oc8d...",
+
          value: r.profile.github,
+
          editable: true,
+
        },
+
        {
+
          name: "id",
+
          label: "Radicle",
+
          validate: "identity",
+
          placeholder: "Radicle URN, eg. rad:git:hnrkqdpm9ub19oc8d...",
          description: "The local radicle identity associated with this name.",
-
          value: r.profile.id, editable: true },
-
        { name: "seed.host", label: "Seed Host", validate: "domain", placeholder: "seed.acme.org",
+
          value: r.profile.id,
+
          editable: true,
+
        },
+
        {
+
          name: "seed.host",
+
          label: "Seed Host",
+
          validate: "domain",
+
          placeholder: "seed.acme.org",
          url: r.profile.seed && `/seeds/${r.profile.seed.host}`,
-
          description: "The seed host address. " +
+
          description:
+
            "The seed host address. " +
            "Only domain names with TLS are supported. " +
            `HTTP(S) API requests use port ${config.seed.api.port}.`,
-
          value: r.profile.seed?.host, editable: true },
-
        { name: "seed.id", label: "Seed ID", validate: "id", placeholder: "hynkyndc6w3p8urucakobzncqny7xxtw88...",
-
          description: "The Device ID of a Radicle Link node that hosts entities associated with this name.",
-
          value: r.profile.seed?.id, editable: true },
-
        { name: "anchors", label: "Anchors", validate: "URN", placeholder: "URN, eg. eip155:1:0x4a9cf21...",
-
          description: "URN under which associated project anchors can be found. "
-
            + "To point to a Radicle org on Ethereum, use the CAIP-10 ID, eg. *eip155:1:0x4a9cf21...*",
-
          value: r.profile.anchorsAccount, editable: true },
+
          value: r.profile.seed?.host,
+
          editable: true,
+
        },
+
        {
+
          name: "seed.id",
+
          label: "Seed ID",
+
          validate: "id",
+
          placeholder: "hynkyndc6w3p8urucakobzncqny7xxtw88...",
+
          description:
+
            "The Device ID of a Radicle Link node that hosts entities associated with this name.",
+
          value: r.profile.seed?.id,
+
          editable: true,
+
        },
+
        {
+
          name: "anchors",
+
          label: "Anchors",
+
          validate: "URN",
+
          placeholder: "URN, eg. eip155:1:0x4a9cf21...",
+
          description:
+
            "URN under which associated project anchors can be found. " +
+
            "To point to a Radicle org on Ethereum, use the CAIP-10 ID, eg. *eip155:1:0x4a9cf21...*",
+
          value: r.profile.anchorsAccount,
+
          editable: true,
+
        },
      ];
      state = { status: Status.Found, registration: r, owner };
    } else {
@@ -104,12 +170,15 @@

  onMount(() => {
    getRegistration(domain, config, resolver)
-
      .then(parseRecords).catch(err => {
+
      .then(parseRecords)
+
      .catch(err => {
        state = { status: Status.Failed, error: err };
      });
  });

-
  const onSave = async (event: { detail: { name: string; value: string | null }[] }) => {
+
  const onSave = async (event: {
+
    detail: { name: string; value: string | null }[];
+
  }) => {
    assert(state.status === Status.Found, "registration must be found");

    updateRecords = event.detail
@@ -119,10 +188,16 @@
      });
  };

-
  $: if (window.history.state?.retry && state.status === Status.NotFound && retries > 0) {
-
    getRegistration(domain, config, resolver).then(parseRecords).catch(err => {
-
      state = { status: Status.Failed, error: err };
-
    });
+
  $: if (
+
    window.history.state?.retry &&
+
    state.status === Status.NotFound &&
+
    retries > 0
+
  ) {
+
    getRegistration(domain, config, resolver)
+
      .then(parseRecords)
+
      .catch(err => {
+
        state = { status: Status.Failed, error: err };
+
      });
  }

  $: isOwner = (owner: string): boolean => {
@@ -159,7 +234,9 @@
{#if state.status === Status.Loading}
  <Loading />
{:else if state.status === Status.Failed}
-
  <Error title="Registration could not be loaded" on:close={() => navigate('/registrations')}>
+
  <Error
+
    title="Registration could not be loaded"
+
    on:close={() => navigate("/registrations")}>
    {state.error}
  </Error>
{:else if state.status === Status.NotFound}
@@ -170,7 +247,10 @@
    </span>

    <span slot="body">
-
      <p>The name <strong>{domain}</strong> is not registered.</p>
+
      <p>
+
        The name <strong>{domain}</strong>
+
        is not registered.
+
      </p>
    </span>

    <span slot="actions">
@@ -183,17 +263,28 @@
      <h1 class="bold">{domain}</h1>
      <button
        style="min-width: 60px;"
-
        class="small primary" class:active={editable} disabled={!isOwner(state.owner)}
+
        class="small primary"
+
        class:active={editable}
+
        disabled={!isOwner(state.owner)}
        title={!isOwner(state.owner) ? "Only owner can edit this profile" : ""}
-
        on:click={() => editable = !editable}>
-
          Edit
+
        on:click={() => (editable = !editable)}>
+
        Edit
      </button>
    </header>
-
    <Form {config} {editable} {fields} on:save={onSave} on:cancel={() => editable = false} />
+
    <Form
+
      {config}
+
      {editable}
+
      {fields}
+
      on:save={onSave}
+
      on:cancel={() => (editable = false)} />
  </main>

  {#if updateRecords}
-
    <Update {config} {domain} on:close={() => updateRecords = null}
-
            registration={state.registration} records={updateRecords} />
+
    <Update
+
      {config}
+
      {domain}
+
      on:close={() => (updateRecords = null)}
+
      registration={state.registration}
+
      records={updateRecords} />
  {/if}
{/if}
modified src/base/registrations/registrar.ts
@@ -1,15 +1,15 @@
-
import { ethers } from 'ethers';
-
import { writable } from 'svelte/store';
-
import type { BigNumber } from 'ethers';
-
import type { EnsResolver } from '@ethersproject/providers';
-
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
-
import * as session from '@app/session';
-
import { Failure } from '@app/error';
-
import type { Config } from '@app/config';
-
import { unixTime } from '@app/utils';
-
import { assert } from '@app/error';
-
import { Seed, InvalidSeed } from '@app/base/seeds/Seed';
-
import * as cache from '@app/cache';
+
import { ethers } from "ethers";
+
import { writable } from "svelte/store";
+
import type { BigNumber } from "ethers";
+
import type { EnsResolver } from "@ethersproject/providers";
+
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
+
import * as session from "@app/session";
+
import { Failure } from "@app/error";
+
import type { Config } from "@app/config";
+
import { unixTime } from "@app/utils";
+
import { assert } from "@app/error";
+
import { Seed, InvalidSeed } from "@app/base/seeds/Seed";
+
import * as cache from "@app/cache";

export interface Registration {
  profile: EnsProfile;
@@ -42,12 +42,16 @@ export enum State {
}

export type Connection =
-
    { connection: State.Failed }
+
  | { connection: State.Failed }
  | { connection: State.Connecting }
  | { connection: State.SigningPermit }
  | { connection: State.SigningCommit }
  | { connection: State.Committing }
-
  | { connection: State.WaitingToRegister; commitmentBlock: number; minAge: number }
+
  | {
+
      connection: State.WaitingToRegister;
+
      commitmentBlock: number;
+
      minAge: number;
+
    }
  | { connection: State.SigningRegister }
  | { connection: State.Registering }
  | { connection: State.Registered };
@@ -60,33 +64,50 @@ state.subscribe((s: Connection) => {
  console.debug("register.state", s);
});

-
export async function getRegistration(name: string, config: Config, resolver?: EnsResolver | null): Promise<Registration | null> {
+
export async function getRegistration(
+
  name: string,
+
  config: Config,
+
  resolver?: EnsResolver | null,
+
): Promise<Registration | null> {
  name = name.toLowerCase();

-
  if (! resolver) {
+
  if (!resolver) {
    resolver = await getResolver(name, config);

-
    if (! resolver) {
+
    if (!resolver) {
      return null;
    }
  }

  const meta = await Promise.allSettled([
    getAddress(resolver),
-
    getText(resolver, 'avatar'),
-
    getText(resolver, 'url'),
-
    getText(resolver, 'eth.radicle.id'),
-
    getText(resolver, 'eth.radicle.seed.id'),
-
    getText(resolver, 'eth.radicle.seed.host'),
-
    getText(resolver, 'eth.radicle.seed.git'),
-
    getText(resolver, 'eth.radicle.seed.api'),
-
    getText(resolver, 'eth.radicle.anchors'),
-
    getText(resolver, 'com.twitter'),
-
    getText(resolver, 'com.github'),
+
    getText(resolver, "avatar"),
+
    getText(resolver, "url"),
+
    getText(resolver, "eth.radicle.id"),
+
    getText(resolver, "eth.radicle.seed.id"),
+
    getText(resolver, "eth.radicle.seed.host"),
+
    getText(resolver, "eth.radicle.seed.git"),
+
    getText(resolver, "eth.radicle.seed.api"),
+
    getText(resolver, "eth.radicle.anchors"),
+
    getText(resolver, "com.twitter"),
+
    getText(resolver, "com.github"),
  ]);

-
  const [address, avatar, url, id, seedId, seedHost, seedGit, seedApi, anchorsAccount, twitter, github] =
-
    meta.map(r => r.status === "fulfilled" && r.value ? r.value : undefined);
+
  const [
+
    address,
+
    avatar,
+
    url,
+
    id,
+
    seedId,
+
    seedHost,
+
    seedGit,
+
    seedApi,
+
    anchorsAccount,
+
    twitter,
+
    github,
+
  ] = meta.map(r =>
+
    r.status === "fulfilled" && r.value ? r.value : undefined,
+
  );

  const profile: EnsProfile = {
    name,
@@ -102,9 +123,15 @@ export async function getRegistration(name: string, config: Config, resolver?: E
  // If no seed provided profile.seed ends up being undefined
  if (seedHost && seedId) {
    try {
-
      profile.seed = new Seed({
-
        host: seedHost, id: seedId, git: seedGit, api: seedApi
-
      }, config);
+
      profile.seed = new Seed(
+
        {
+
          host: seedHost,
+
          id: seedId,
+
          git: seedGit,
+
          api: seedApi,
+
        },
+
        config,
+
      );
    } catch (e: any) {
      console.debug(e, seedHost, seedId);
      profile.seed = new InvalidSeed(seedHost, seedId);
@@ -114,42 +141,54 @@ export async function getRegistration(name: string, config: Config, resolver?: E
  return { resolver, profile };
}

-
export async function getAvatar(name: string, config: Config, resolver?: EnsResolver | null): Promise<string | null> {
+
export async function getAvatar(
+
  name: string,
+
  config: Config,
+
  resolver?: EnsResolver | null,
+
): Promise<string | null> {
  name = name.toLowerCase();

-
  resolver = resolver ?? await getResolver(name, config);
-
  if (! resolver) {
+
  resolver = resolver ?? (await getResolver(name, config));
+
  if (!resolver) {
    return null;
  }
-
  return getText(resolver, 'avatar');
+
  return getText(resolver, "avatar");
}

-
export async function getAnchorsAccount(name: string, config: Config, resolver?: EnsResolver | null): Promise<string | null> {
+
export async function getAnchorsAccount(
+
  name: string,
+
  config: Config,
+
  resolver?: EnsResolver | null,
+
): Promise<string | null> {
  name = name.toLowerCase();

-
  resolver = resolver ?? await getResolver(name, config);
-
  if (! resolver) {
+
  resolver = resolver ?? (await getResolver(name, config));
+
  if (!resolver) {
    return null;
  }
-
  return getText(resolver, 'eth.radicle.anchors');
+
  return getText(resolver, "eth.radicle.anchors");
}

-
export async function getSeed(name: string, config: Config, resolver?: EnsResolver | null): Promise<Seed | InvalidSeed | null> {
+
export async function getSeed(
+
  name: string,
+
  config: Config,
+
  resolver?: EnsResolver | null,
+
): Promise<Seed | InvalidSeed | null> {
  name = name.toLowerCase();

-
  resolver = resolver ?? await getResolver(name, config);
-
  if (! resolver) {
+
  resolver = resolver ?? (await getResolver(name, config));
+
  if (!resolver) {
    return null;
  }

  const [id, host, git, api] = await Promise.all([
-
    getText(resolver, 'eth.radicle.seed.id'),
-
    getText(resolver, 'eth.radicle.seed.host'),
-
    getText(resolver, 'eth.radicle.seed.git'),
-
    getText(resolver, 'eth.radicle.seed.api'),
+
    getText(resolver, "eth.radicle.seed.id"),
+
    getText(resolver, "eth.radicle.seed.host"),
+
    getText(resolver, "eth.radicle.seed.git"),
+
    getText(resolver, "eth.radicle.seed.api"),
  ]);

-
  if (! host || ! id) {
+
  if (!host || !id) {
    console.debug("getSeed: No seed host or id provided");
    return null;
  }
@@ -163,21 +202,29 @@ export async function getSeed(name: string, config: Config, resolver?: EnsResolv
}

export function registrar(config: Config): ethers.Contract {
-
  return new ethers.Contract(config.registrar.address, config.abi.registrar, config.provider);
+
  return new ethers.Contract(
+
    config.registrar.address,
+
    config.abi.registrar,
+
    config.provider,
+
  );
}

export async function registrationFee(config: Config): Promise<BigNumber> {
  return await registrar(config).registrationFeeRad();
}

-
export async function registerName(name: string, owner: string, config: Config): Promise<void> {
+
export async function registerName(
+
  name: string,
+
  owner: string,
+
  config: Config,
+
): Promise<void> {
  assert(config.signer, "signer is not available");

-
  if (! name) return;
+
  if (!name) return;

  name = name.toLowerCase();

-
  const commitmentJson = window.localStorage.getItem('commitment');
+
  const commitmentJson = window.localStorage.getItem("commitment");
  const commitment = commitmentJson && JSON.parse(commitmentJson);

  try {
@@ -188,18 +235,29 @@ export async function registerName(name: string, owner: string, config: Config):
      await commitAndRegister(name, owner, config);
    }
  } catch (e: any) {
-
    throw { type: e.type || Failure.TransactionFailed, message: e.message, txHash: e.txHash };
+
    throw {
+
      type: e.type || Failure.TransactionFailed,
+
      message: e.message,
+
      txHash: e.txHash,
+
    };
  }
}

-
async function commitAndRegister(name: string, owner: string, config: Config): Promise<void> {
+
async function commitAndRegister(
+
  name: string,
+
  owner: string,
+
  config: Config,
+
): Promise<void> {
  const salt = ethers.utils.randomBytes(32);
  const minAge = (await registrar(config).minCommitmentAge()).toNumber();
  const fee = await registrationFee(config);
  // Avoids gas spent by the owner, trying to commit to a name and not having
  // enough RAD balance
  if ((await config.token.balanceOf(owner)).lt(fee)) {
-
    throw { type: Failure.InsufficientBalance, message: "Not enough RAD funds" };
+
    throw {
+
      type: Failure.InsufficientBalance,
+
      message: "Not enough RAD funds",
+
    };
  }
  name = name.toLowerCase();

@@ -213,7 +271,7 @@ async function commit(
  salt: Uint8Array,
  fee: BigNumber,
  minAge: number,
-
  config: Config
+
  config: Config,
): Promise<void> {
  assert(config.signer, "signer is not available");

@@ -231,7 +289,13 @@ async function commit(
      .connect(config.signer)
      .commit(commitment, { gasLimit: 180000 });
  } else {
-
    const signature = await permitSignature(config.signer, token, spender, fee, deadline);
+
    const signature = await permitSignature(
+
      config.signer,
+
      token,
+
      spender,
+
      fee,
+
      deadline,
+
    );

    state.set({ connection: State.SigningCommit });

@@ -245,7 +309,8 @@ async function commit(
        signature.v,
        signature.r,
        signature.s,
-
        { gasLimit: 180000 });
+
        { gasLimit: 180000 },
+
      );
  }

  state.set({ connection: State.Committing });
@@ -255,16 +320,19 @@ async function commit(

  // Save commitment in local storage in case the user refreshes or closes
  // the page.
-
  window.localStorage.setItem('commitment', JSON.stringify({
-
    name: name,
-
    owner: owner,
-
    salt: ethers.utils.hexlify(salt)
-
  }));
+
  window.localStorage.setItem(
+
    "commitment",
+
    JSON.stringify({
+
      name: name,
+
      owner: owner,
+
      salt: ethers.utils.hexlify(salt),
+
    }),
+
  );

  state.set({
    connection: State.WaitingToRegister,
    commitmentBlock: receipt.blockNumber,
-
    minAge
+
    minAge,
  });
  await tx.wait(minAge + 1);
}
@@ -290,32 +358,37 @@ async function permitSignature(
  };
  const types = {
    Permit: [
-
      { "name": "owner", "type": "address" },
-
      { "name": "spender", "type": "address" },
-
      { "name": "value", "type": "uint256" },
-
      { "name": "nonce", "type": "uint256" },
-
      { "name": "deadline", "type": "uint256" }
-
    ]
+
      { name: "owner", type: "address" },
+
      { name: "spender", type: "address" },
+
      { name: "value", type: "uint256" },
+
      { name: "nonce", type: "uint256" },
+
      { name: "deadline", type: "uint256" },
+
    ],
  };
  const values = {
-
    "owner": ownerAddr,
-
    "spender": spenderAddr,
-
    "value": value,
-
    "nonce": nonce,
-
    "deadline": deadline
+
    owner: ownerAddr,
+
    spender: spenderAddr,
+
    value: value,
+
    nonce: nonce,
+
    deadline: deadline,
  };
  const sig = await owner._signTypedData(domain, types, values);

  return ethers.utils.splitSignature(sig);
}

-
async function register(name: string, owner: string, salt: Uint8Array, config: Config) {
+
async function register(
+
  name: string,
+
  owner: string,
+
  salt: Uint8Array,
+
  config: Config,
+
) {
  assert(config.signer, "signer is not available");
  state.set({ connection: State.SigningRegister });

-
  const tx = await registrar(config).connect(config.signer).register(
-
    name, owner, ethers.BigNumber.from(salt), { gasLimit: 150000 }
-
  );
+
  const tx = await registrar(config)
+
    .connect(config.signer)
+
    .register(name, owner, ethers.BigNumber.from(salt), { gasLimit: 150000 });
  state.set({ connection: State.Registering });

  console.debug("Sent", tx);
@@ -336,11 +409,15 @@ function makeCommitment(name: string, owner: string, salt: Uint8Array): string {

export async function getOwner(name: string, config: Config): Promise<string> {
  const ensAddr = config.provider.network.ensAddress;
-
  if (! ensAddr) {
+
  if (!ensAddr) {
    throw new Error("ENS address is not defined");
  }

-
  const registry = new ethers.Contract(ensAddr, config.abi.ens, config.provider);
+
  const registry = new ethers.Contract(
+
    ensAddr,
+
    config.abi.ens,
+
    config.provider,
+
  );
  const owner = await registry.owner(ethers.utils.namehash(name));

  return owner;
@@ -350,8 +427,8 @@ export const getResolver = cache.cached(
  async (name: string, config: Config) => {
    return await config.provider.getResolver(name);
  },
-
  (name) => name,
-
  { max: 1000 }
+
  name => name,
+
  { max: 1000 },
);

export const getText = cache.cached(
@@ -359,13 +436,13 @@ export const getText = cache.cached(
    return await resolver.getText(key);
  },
  (resolver, key) => `${resolver.name} ${key}`,
-
  { max: 1000 }
+
  { max: 1000 },
);

export const getAddress = cache.cached(
  async (resolver: EnsResolver) => {
    return await resolver.getAddress();
  },
-
  (resolver) => resolver.name,
-
  { max: 1000 }
+
  resolver => resolver.name,
+
  { max: 1000 },
);
modified src/base/registrations/resolver.ts
@@ -1,15 +1,24 @@
-
import type { TransactionResponse } from '@ethersproject/providers';
-
import type { EnsResolver } from '@ethersproject/providers';
-
import { ethers } from 'ethers';
-
import type { Config } from '@app/config';
-
import { assert } from '@app/error';
+
import type { TransactionResponse } from "@ethersproject/providers";
+
import type { EnsResolver } from "@ethersproject/providers";
+
import { ethers } from "ethers";
+
import type { Config } from "@app/config";
+
import { assert } from "@app/error";

export type EnsRecord = { name: string; value: string };

-
export async function setRecords(name: string, records: EnsRecord[], resolver: EnsResolver, config: Config): Promise<TransactionResponse> {
+
export async function setRecords(
+
  name: string,
+
  records: EnsRecord[],
+
  resolver: EnsResolver,
+
  config: Config,
+
): Promise<TransactionResponse> {
  assert(config.signer, "no signer available");

-
  const resolverContract = new ethers.Contract(resolver.address, config.abi.resolver, config.signer);
+
  const resolverContract = new ethers.Contract(
+
    resolver.address,
+
    config.abi.resolver,
+
    config.signer,
+
  );
  const node = ethers.utils.namehash(name);

  const calls = [];
@@ -18,20 +27,18 @@ export async function setRecords(name: string, records: EnsRecord[], resolver: E
  for (const r of records) {
    switch (r.name) {
      case "address":
-
        calls.push(
-
          iface.encodeFunctionData("setAddr", [node, r.value])
-
        );
+
        calls.push(iface.encodeFunctionData("setAddr", [node, r.value]));
        break;
      case "url":
      case "avatar":
        calls.push(
-
          iface.encodeFunctionData("setText", [node, r.name, r.value])
+
          iface.encodeFunctionData("setText", [node, r.name, r.value]),
        );
        break;
      case "github":
      case "twitter":
        calls.push(
-
          iface.encodeFunctionData("setText", [node, "com." + r.name, r.value])
+
          iface.encodeFunctionData("setText", [node, "com." + r.name, r.value]),
        );
        break;
      case "id":
@@ -41,7 +48,11 @@ export async function setRecords(name: string, records: EnsRecord[], resolver: E
      case "seed.api":
      case "anchors":
        calls.push(
-
          iface.encodeFunctionData("setText", [node, "eth.radicle." + r.name, r.value])
+
          iface.encodeFunctionData("setText", [
+
            node,
+
            "eth.radicle." + r.name,
+
            r.value,
+
          ]),
        );
        break;
      default:
modified src/base/resolver/List.svelte
@@ -1,9 +1,9 @@
<script lang="ts">
-
  import Modal from '@app/Modal.svelte';
+
  import Modal from "@app/Modal.svelte";
  import Address from "@app/Address.svelte";
-
  import type { Config } from '@app/config';
-
  import { Profile } from '@app/profile';
-
  import Loading from '@app/Loading.svelte';
+
  import type { Config } from "@app/config";
+
  import { Profile } from "@app/profile";
+
  import Loading from "@app/Loading.svelte";

  export let config: Config;

@@ -19,27 +19,27 @@
  }
</style>

-
  <Modal subtle>
-
    <span slot="title">️🔍</span>
-
    <span slot="subtitle">
-
      <p class="highlight text-medium"><strong>Multiple names found for {query}</strong></p>
-
    </span>
-
    <span slot="body">
-
      <div class="list">
-
        {#await Profile.getMulti([`${query}.${config.registrar.domain}`, `${query}.eth`], config)}
-
          <Loading center />
-
        {:then profiles}
-
          {#each profiles as profile}
-
            {#if profile}
-
              <Address address={profile.address} profile={profile} {config} resolve />
-
            {/if}
-
          {/each}
-
        {/await}
-
      </div>
-
    </span>
-
    <span slot="actions">
-
      <button on:click={back}>
-
        Back
-
      </button>
-
    </span>
-
  </Modal>
+
<Modal subtle>
+
  <span slot="title">️🔍</span>
+
  <span slot="subtitle">
+
    <p class="highlight text-medium">
+
      <strong>Multiple names found for {query}</strong>
+
    </p>
+
  </span>
+
  <span slot="body">
+
    <div class="list">
+
      {#await Profile.getMulti([`${query}.${config.registrar.domain}`, `${query}.eth`], config)}
+
        <Loading center />
+
      {:then profiles}
+
        {#each profiles as profile}
+
          {#if profile}
+
            <Address address={profile.address} {profile} {config} resolve />
+
          {/if}
+
        {/each}
+
      {/await}
+
    </div>
+
  </span>
+
  <span slot="actions">
+
    <button on:click={back}>Back</button>
+
  </span>
+
</Modal>
modified src/base/resolver/Query.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { ethers } from 'ethers';
-
  import { navigate } from 'svelte-routing';
-
  import type { Config } from '@app/config';
-
  import * as utils from '@app/utils';
-
  import Error from '@app/Error.svelte';
-
  import Loading from '@app/Loading.svelte';
+
  import { onMount } from "svelte";
+
  import { ethers } from "ethers";
+
  import { navigate } from "svelte-routing";
+
  import type { Config } from "@app/config";
+
  import * as utils from "@app/utils";
+
  import Error from "@app/Error.svelte";
+
  import Loading from "@app/Loading.svelte";

  export let config: Config;
  export let query: string | null;
@@ -15,8 +15,12 @@
  onMount(async () => {
    if (query) {
      if (ethers.utils.isAddress(query)) {
-
        const addressType = query && await utils.identifyAddress(query, config);
-
        if (addressType === utils.AddressType.Org || addressType === utils.AddressType.EOA) {
+
        const addressType =
+
          query && (await utils.identifyAddress(query, config));
+
        if (
+
          addressType === utils.AddressType.Org ||
+
          addressType === utils.AddressType.EOA
+
        ) {
          navigate(`/${query}`, { replace: true });
        }
      } else if (utils.isRadicleId(query)) {
@@ -26,15 +30,19 @@
        // Jump straight to org, if the ENS entry points to an org. Otherwise it checks if the
        // address type is an EOA and jumps to the user page else it just goes to the registration.
        const address = await utils.resolveLabel(query, config);
-
        const addressType = address && await utils.identifyAddress(address, config);
-
        if (addressType === utils.AddressType.Org || addressType === utils.AddressType.EOA) {
+
        const addressType =
+
          address && (await utils.identifyAddress(address, config));
+
        if (
+
          addressType === utils.AddressType.Org ||
+
          addressType === utils.AddressType.EOA
+
        ) {
          navigate(`/${address}`, { replace: true });
        } else {
          navigate(`/registrations/${query}`, { replace: true });
        }
      }
    } else {
-
      navigate('/');
+
      navigate("/");
    }
  });
</script>
@@ -45,7 +53,7 @@

<main class="off-centered">
  {#if error}
-
    <Error on:close={() => navigate('/')}>
+
    <Error on:close={() => navigate("/")}>
      Invalid query string “{query}”
    </Error>
  {:else}
modified src/base/resolver/Routes.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
  import { Route } from "svelte-routing";
-
  import Resolve from '@app/base/resolver/Query.svelte';
-
  import type { Config } from '@app/config';
-
  import * as utils from '@app/utils';
+
  import Resolve from "@app/base/resolver/Query.svelte";
+
  import type { Config } from "@app/config";
+
  import * as utils from "@app/utils";

  export let config: Config;
</script>
modified src/base/seeds/Routes.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import { Route } from "svelte-routing";
-
  import View from '@app/base/seeds/View.svelte';
-
  import type { Config } from '@app/config';
+
  import View from "@app/base/seeds/View.svelte";
+
  import type { Config } from "@app/config";
  import type { Session } from "@app/session";

  export let config: Config;
@@ -9,9 +9,9 @@
</script>

<Route path="/seeds/radicle.local">
-
  <View {config} {session} host={"0.0.0.0"}/>
+
  <View {config} {session} host={"0.0.0.0"} />
</Route>

<Route path="/seeds/:seed" let:params>
-
  <View {config} {session} host={params.seed}/>
+
  <View {config} {session} host={params.seed} />
</Route>
modified src/base/seeds/Seed.ts
@@ -1,8 +1,8 @@
-
import { Request, type Host } from '@app/api';
-
import type { Config } from '@app/config';
+
import { Request, type Host } from "@app/api";
+
import type { Config } from "@app/config";
import * as proj from "@app/project";
-
import { isDomain, isLocal } from '@app/utils';
-
import { assert } from '@app/error';
+
import { isDomain, isLocal } from "@app/utils";
+
import { assert } from "@app/error";

export class InvalidSeed {
  valid: false = false;
@@ -26,13 +26,16 @@ export class Seed {
  version?: string;
  emoji: string;

-
  constructor(seed: {
-
    host: string;
-
    id: string;
-
    git?: string | null;
-
    api?: string | null;
-
    version?: string | null;
-
  }, cfg: Config) {
+
  constructor(
+
    seed: {
+
      host: string;
+
      id: string;
+
      git?: string | null;
+
      api?: string | null;
+
      version?: string | null;
+
    },
+
    cfg: Config,
+
  ) {
    assert(isDomain(seed.host), `invalid seed host: ${seed.host}`);
    assert(/^[a-z0-9]+$/.test(seed.id), `invalid seed id ${seed.id}`);

@@ -107,7 +110,10 @@ export class Seed {
      ? await proj.Project.getDelegateProjects(id, this.api)
      : await proj.Project.getProjects(this.api);

-
    return result.map((project: proj.ProjectInfo) => ({ ...project, id: project.urn }));
+
    return result.map((project: proj.ProjectInfo) => ({
+
      ...project,
+
      id: project.urn,
+
    }));
  }

  static async getPeer(host: Host): Promise<{ id: string }> {
@@ -125,11 +131,14 @@ export class Seed {
      Seed.getPeer(host),
    ]);

-
    return new Seed({
-
      host: hostname,
-
      id: peer.id,
-
      version: info.version,
-
    }, cfg);
+
    return new Seed(
+
      {
+
        host: hostname,
+
        id: peer.id,
+
        version: info.version,
+
      },
+
      cfg,
+
    );
  }

  static async lookupMulti(hostnames: string[], cfg: Config): Promise<Seed[]> {
modified src/base/seeds/View.svelte
@@ -105,7 +105,8 @@
    <header>
      <span class="title">
        <span class="bold">
-
          {hostName} <span class="desktop inline">{seed.emoji}</span>
+
          {hostName}
+
          <span class="desktop inline">{seed.emoji}</span>
        </span>
      </span>
      <!-- User Session -->
@@ -114,13 +115,22 @@
          {#if siweSession}
            <div class="session-info">
              <span class="signed-in text-small">Signed in as</span>
-
              <Address address={siweSession.address} {config} small compact resolve />
+
              <Address
+
                address={siweSession.address}
+
                {config}
+
                small
+
                compact
+
                resolve />
            </div>
          {:else}
            <SiweConnect {seed} address={session.address} {config} />
          {/if}
        {:else}
-
          <SiweConnect disabled {seed} {config} tooltip={"Connect your wallet to sign in"} />
+
          <SiweConnect
+
            disabled
+
            {seed}
+
            {config}
+
            tooltip={"Connect your wallet to sign in"} />
        {/if}
      </div>
    </header>
@@ -148,6 +158,5 @@
{:catch}
  <NotFound
    title={host}
-
    subtitle="Not able to query information from this seed."
-
  />
+
    subtitle="Not able to query information from this seed." />
{/await}
modified src/base/users/User.ts
@@ -1,7 +1,7 @@
-
import * as ethers from 'ethers';
-
import type { Config } from '@app/config';
-
import { assert } from '@app/error';
-
import type { TransactionResponse } from '@ethersproject/providers';
+
import * as ethers from "ethers";
+
import type { Config } from "@app/config";
+
import { assert } from "@app/error";
+
import type { TransactionResponse } from "@ethersproject/providers";

export class User {
  address: string;
@@ -18,7 +18,7 @@ export class User {
    const reverseRegistrar = new ethers.Contract(
      config.reverseRegistrar.address,
      config.abi.reverseRegistrar,
-
      config.signer
+
      config.signer,
    );
    return reverseRegistrar.setName(name);
  }
modified src/base/vesting/Index.svelte
@@ -1,13 +1,13 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
-
  import { State, state } from './state';
-
  import { getInfo, withdrawVested } from './vesting';
-
  import type { VestingInfo } from './vesting';
-
  import type { Session } from '@app/session';
-
  import type { Config } from '@app/config';
-
  import Modal from '@app/Modal.svelte';
-
  import Address from '@app/Address.svelte';
-
  import { formatAddress, isAddressEqual } from '@app/utils';
+
  import { onMount } from "svelte";
+
  import { State, state } from "./state";
+
  import { getInfo, withdrawVested } from "./vesting";
+
  import type { VestingInfo } from "./vesting";
+
  import type { Session } from "@app/session";
+
  import type { Config } from "@app/config";
+
  import Modal from "@app/Modal.svelte";
+
  import Address from "@app/Address.svelte";
+
  import { formatAddress, isAddressEqual } from "@app/utils";

  let input: HTMLElement;

@@ -32,7 +32,8 @@
    state.set(State.Idle);
  }

-
  $: isBeneficiary = info && session && isAddressEqual(info.beneficiary, session.address);
+
  $: isBeneficiary =
+
    info && session && isAddressEqual(info.beneficiary, session.address);
</script>

<style>
@@ -69,10 +70,37 @@
            Tokens successfully withdrawn to {formatAddress(info.beneficiary)}.
          {:else}
            <table>
-
              <tr><td class="label">Beneficiary</td><td><Address {config} address={info.beneficiary} compact resolve /></td></tr>
-
              <tr><td class="label">Allocation</td><td>{info.totalVesting} <strong>{info.symbol}</strong></td></tr>
-
              <tr><td class="label">Withdrawn</td><td>{info.withdrawn} <strong>{info.symbol}</strong></td></tr>
-
              <tr><td class="label">Withdrawable</td><td>{info.withdrawableBalance} <strong>{info.symbol}</strong></td></tr>
+
              <tr>
+
                <td class="label">Beneficiary</td>
+
                <td>
+
                  <Address
+
                    {config}
+
                    address={info.beneficiary}
+
                    compact
+
                    resolve />
+
                </td>
+
              </tr>
+
              <tr>
+
                <td class="label">Allocation</td>
+
                <td>
+
                  {info.totalVesting}
+
                  <strong>{info.symbol}</strong>
+
                </td>
+
              </tr>
+
              <tr>
+
                <td class="label">Withdrawn</td>
+
                <td>
+
                  {info.withdrawn}
+
                  <strong>{info.symbol}</strong>
+
                </td>
+
              </tr>
+
              <tr>
+
                <td class="label">Withdrawable</td>
+
                <td>
+
                  {info.withdrawableBalance}
+
                  <strong>{info.symbol}</strong>
+
                </td>
+
              </tr>
            </table>
          {/if}
        </span>
@@ -87,19 +115,20 @@
                Withdrawing...
              </button>
            {:else if $state === State.Idle}
-
              <button on:click={() => withdrawVested(contractAddress, config)} class="primary regular">
+
              <button
+
                on:click={() => withdrawVested(contractAddress, config)}
+
                class="primary regular">
                Withdraw
              </button>
            {/if}
          {/if}
-
          <button on:click={reset} class="regular">
-
            Back
-
          </button>
+
          <button on:click={reset} class="regular">Back</button>
        </span>
      </Modal>
    {:else}
      <div class="input-caption">
-
        Enter your Radicle <strong>vesting contract</strong> address
+
        Enter your Radicle <strong>vesting contract</strong>
+
        address
      </div>
      <div class="input-main">
        <span class="name">
@@ -111,16 +140,14 @@
              disabled={$state === State.Loading}
              type="text"
              bind:this={input}
-
              bind:value={contractAddress}
-
            />
+
              bind:value={contractAddress} />
          </div>
        </span>
        <button
          on:click={() => loadContract(config)}
          class="primary"
          data-waiting={$state === State.Loading || null}
-
          disabled={$state === State.Loading}
-
        >
+
          disabled={$state === State.Loading}>
          Load
        </button>
      </div>
modified src/base/vesting/vesting.ts
@@ -14,10 +14,17 @@ export interface VestingInfo {
  withdrawn: string;
}

-
export async function withdrawVested(address: string, config: Config): Promise<void> {
+
export async function withdrawVested(
+
  address: string,
+
  config: Config,
+
): Promise<void> {
  assert(config.signer);

-
  const contract = new ethers.Contract(address, config.abi.vesting, config.provider);
+
  const contract = new ethers.Contract(
+
    address,
+
    config.abi.vesting,
+
    config.provider,
+
  );
  const signer = config.signer;

  state.set(State.WithdrawingSign);
@@ -30,15 +37,26 @@ export async function withdrawVested(address: string, config: Config): Promise<v
  state.set(State.Withdrawn);
}

-
export async function getInfo(address: string, config: Config): Promise<VestingInfo> {
-
  const contract = new ethers.Contract(address, config.abi.vesting, config.provider);
+
export async function getInfo(
+
  address: string,
+
  config: Config,
+
): Promise<VestingInfo> {
+
  const contract = new ethers.Contract(
+
    address,
+
    config.abi.vesting,
+
    config.provider,
+
  );
  const token = await contract.token();
  const beneficiary = await contract.beneficiary();
  const withdrawable = await contract.withdrawableBalance();
  const withdrawn = await contract.withdrawn();
  const total = await contract.totalVestingAmount();

-
  const tokenContract = new ethers.Contract(token, config.abi.token, config.provider);
+
  const tokenContract = new ethers.Contract(
+
    token,
+
    config.abi.token,
+
    config.provider,
+
  );
  const symbol = await tokenContract.symbol();

  return {
@@ -50,4 +68,3 @@ export async function getInfo(address: string, config: Config): Promise<VestingI
    withdrawn: formatBalance(withdrawn),
  };
}
-

modified src/blockies.ts
@@ -29,9 +29,9 @@ function createColor(): string {
  // Saturation is the whole color spectrum.
  const h = Math.floor(rand() * 360);
  // Saturation goes from 40 to 100, it avoids greyish colors.
-
  const s = rand() * 60 + 40 + '%';
+
  const s = rand() * 60 + 40 + "%";
  // Lightness can be anything from 0 to 100, but probabilities are a bell curve around 50%.
-
  const l = (rand() + rand() + rand() + rand()) * 25 + '%';
+
  const l = (rand() + rand() + rand() + rand()) * 25 + "%";

  return `hsl(${h}, ${s}, ${l})`;
}
@@ -68,15 +68,15 @@ function createCanvas(
  color: string,
  scale: number,
  bgcolor: string,
-
  spotcolor: string
+
  spotcolor: string,
): HTMLCanvasElement {
-
  const c = document.createElement('canvas');
+
  const c = document.createElement("canvas");
  const width = Math.sqrt(imageData.length);
  c.width = c.height = width * scale;

-
  const cc = c.getContext('2d');
+
  const cc = c.getContext("2d");

-
  if (! cc) throw new Error("Can't get 2D context");
+
  if (!cc) throw new Error("Can't get 2D context");

  cc.fillStyle = bgcolor;
  cc.fillRect(0, 0, c.width, c.height);
modified src/cache.ts
@@ -7,7 +7,7 @@ import LruCache from "lru-cache";
export function cached<Args extends unknown[], V>(
  f: (...args: Args) => Promise<V>,
  makeKey: (...args: Args) => string,
-
  options?: LruCache.Options<string, { value: V }>
+
  options?: LruCache.Options<string, { value: V }>,
): (...args: Args) => Promise<V> {
  const cache = new LruCache(options || { max: 500 });
  return async function (...args: Args): Promise<V> {
modified src/commit.ts
@@ -82,20 +82,22 @@ export interface Commit {

export function formatGroupTime(timestamp: number): string {
  return new Date(timestamp).toLocaleDateString("en-US", {
-
    day: 'numeric',
-
    weekday: 'long',
-
    month: 'long',
-
    year: 'numeric'
+
    day: "numeric",
+
    weekday: "long",
+
    month: "long",
+
    year: "numeric",
  });
}

export const groupCommitHistory = (
-
  history: CommitsHistory
+
  history: CommitsHistory,
): GroupedCommitsHistory => {
  return { ...history, headers: groupCommits(history.headers) };
};

-
export function groupCommits(commits: { header: CommitHeader; context: CommitContext }[]): CommitGroup[] {
+
export function groupCommits(
+
  commits: { header: CommitHeader; context: CommitContext }[],
+
): CommitGroup[] {
  const groupedCommits: CommitGroup[] = [];
  let groupDate: Date | undefined = undefined;

@@ -125,7 +127,7 @@ export function groupCommits(commits: { header: CommitHeader; context: CommitCon
          date: formatGroupTime(time),
          time,
          commits: [],
-
          week: 0
+
          week: 0,
        });
        groupDate = date;
      }
@@ -133,13 +135,19 @@ export function groupCommits(commits: { header: CommitHeader; context: CommitCon
    }
    return groupedCommits;
  } catch (err) {
-
    throw new ApiError("Not able to create commit history, please consider updating seed HTTP API.");
+
    throw new ApiError(
+
      "Not able to create commit history, please consider updating seed HTTP API.",
+
    );
  }
}

-
export async function fetchCommits(project: Project, parentCommit: string): Promise<GroupedCommitsHistory> {
+
export async function fetchCommits(
+
  project: Project,
+
  parentCommit: string,
+
): Promise<GroupedCommitsHistory> {
  const commitsQuery = await Project.getCommits(project.urn, project.seed.api, {
-
    parent: parentCommit, verified: true
+
    parent: parentCommit,
+
    verified: true,
  });
  return groupCommitHistory(commitsQuery);
}
@@ -152,10 +160,12 @@ export function groupCommitsByWeek(commits: number[]): WeeklyActivity[] {
    return [];
  }

-
  commits = commits.sort((a, b) => a > b ? -1 : a < b ? 1 : 0);
+
  commits = commits.sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));

  // A accumulator that increments by the amount of weeks between weekly commit groups
-
  let weekAccumulator = Math.floor(getDaysPassed(new Date(commits[0] * 1000), new Date()) / 7);
+
  let weekAccumulator = Math.floor(
+
    getDaysPassed(new Date(commits[0] * 1000), new Date()) / 7,
+
  );

  // Loops over all commits and stores them by week with some additional metadata in groupedCommits.
  for (const commit of commits) {
@@ -176,7 +186,7 @@ export function groupCommitsByWeek(commits: number[]): WeeklyActivity[] {
        date: formatGroupTime(time),
        time,
        commits: [],
-
        week: Math.floor(daysPassed / 7) + weekAccumulator
+
        week: Math.floor(daysPassed / 7) + weekAccumulator,
      });
      groupDate = date;
      weekAccumulator += Math.floor(daysPassed / 7);
modified src/components/Modal/ConnectWallet.svelte
@@ -20,8 +20,12 @@
  });

  const dispatch = createEventDispatcher();
-
  const onClickConnect = () => { state.connectMetamask(config); };
-
  const onClose = () => { dispatch("close"); };
+
  const onClickConnect = () => {
+
    state.connectMetamask(config);
+
  };
+
  const onClose = () => {
+
    dispatch("close");
+
  };
</script>

<style>
@@ -52,8 +56,10 @@

    <div slot="subtitle">
      <div class="text-small">
-
        Scan the QR code with <strong>WalletConnect</strong> or use
-
        <strong>Metamask</strong>.
+
        Scan the QR code with <strong>WalletConnect</strong>
+
        or use
+
        <strong>Metamask</strong>
+
        .
      </div>
    </div>

@@ -66,12 +72,13 @@
    </div>

    <div slot="actions">
-
      <button class="secondary small text-small" on:click={onClickConnect} disabled={!config.metamask.signer}>
+
      <button
+
        class="secondary small text-small"
+
        on:click={onClickConnect}
+
        disabled={!config.metamask.signer}>
        Connect with Metamask
      </button>
-
      <button class="text small text-small" on:click={onClose}>
-
        Close
-
      </button>
+
      <button class="text small text-small" on:click={onClose}>Close</button>
    </div>
  </Modal>
</div>
modified src/config.json
@@ -24,9 +24,7 @@
      ]
    },
    "users": {
-
      "pinned": [
-
        "cloudhead.radicle.eth"
-
      ]
+
      "pinned": ["cloudhead.radicle.eth"]
    },
    "seeds": {
      "pinned": {
@@ -89,9 +87,7 @@
    "api": "https://gateway.ceramic.network",
    "registry": "https://self.id/"
  },
-
  "reactions": [
-
    "👍", "👎", "😄", "🎉", "🙁", "🚀", "👀"
-
  ],
+
  "reactions": ["👍", "👎", "😄", "🎉", "🙁", "🚀", "👀"],
  "radicle": {
    "seed": {
      "api": { "port": 8777 },
@@ -175,9 +171,7 @@
      "function createOrg(address[], uint256) returns (address)",
      "event OrgCreated(address, address)"
    ],
-
    "reverseRegistrar": [
-
      "function setName(string) returns (bytes32)"
-
    ],
+
    "reverseRegistrar": ["function setName(string) returns (bytes32)"],
    "org": [
      "function owner() view returns (address)",
      "function anchors(bytes32) view returns (uint32, bytes)",
modified src/config.ts
@@ -1,14 +1,13 @@
import { get, writable } from "svelte/store";
import type { Writable } from "svelte/store";
import { ethers } from "ethers";
-
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
+
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
import SafeServiceClient from "@gnosis.pm/safe-service-client";
-
import { Core } from '@self.id/core';
+
import { Core } from "@self.id/core";
import WalletConnect from "@walletconnect/client";
import config from "@app/config.json";
import { WalletConnectSigner } from "./WalletConnectSigner";

-

declare global {
  interface Window {
    // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -24,7 +23,7 @@ const gasLimits = {
};

export type WalletConnectState =
-
    { state: "close" }
+
  | { state: "close" }
  | { state: "open"; uri: string; onClose: any };

export class Config {
@@ -39,21 +38,23 @@ export class Config {
  seeds: { pinned: Record<string, { emoji: string }> };
  gasLimits: { createOrg: number };
  provider: ethers.providers.JsonRpcProvider;
-
  signer: ethers.Signer & TypedDataSigner | WalletConnectSigner | null;
+
  signer: (ethers.Signer & TypedDataSigner) | WalletConnectSigner | null;
  walletConnect: {
    client: WalletConnect;
    bridge: string;
    signer: WalletConnectSigner;
    state: Writable<WalletConnectState>;
  };
-
  metamask: {
-
    connected: true;
-
    signer: ethers.Signer & TypedDataSigner;
-
    session: { address: string };
-
  } | {
-
    connected: false;
-
    signer: ethers.Signer & TypedDataSigner | null;
-
  };
+
  metamask:
+
    | {
+
        connected: true;
+
        signer: ethers.Signer & TypedDataSigner;
+
        session: { address: string };
+
      }
+
    | {
+
        connected: false;
+
        signer: (ethers.Signer & TypedDataSigner) | null;
+
      };
  safe: {
    api?: string;
    client?: SafeServiceClient;
@@ -66,8 +67,8 @@ export class Config {
    link: { port: number };
  };
  ceramic: {
-
   client: Core;
-
   registry: string;
+
    client: Core;
+
    registry: string;
  };
  tokens: string[];
  token: ethers.Contract;
@@ -75,31 +76,32 @@ export class Config {
  constructor(
    network: { name: string; chainId: number },
    provider: ethers.providers.JsonRpcProvider,
-
    metamaskSigner: ethers.Signer & TypedDataSigner | null,
+
    metamaskSigner: (ethers.Signer & TypedDataSigner) | null,
  ) {
-
    const cfg = (<Record<string, any>> config)[network.name];
+
    const cfg = (<Record<string, any>>config)[network.name];
    const ceramic = new Core({ ceramic: config.ceramic.api });

-
    const walletConnectState = writable<WalletConnectState>(
-
      { state: "close" }
-
    );
+
    const walletConnectState = writable<WalletConnectState>({ state: "close" });
    const wc = Config.initializeWalletConnect(
      config.walletConnect.bridge,
      walletConnectState,
-
      provider
+
      provider,
    );
    const metamaskSession = window.localStorage.getItem("metamask");
    const metamask = metamaskSession ? JSON.parse(metamaskSession) : null;

    this.network = network;
-
    this.metamask = metamask && metamaskSigner ? {
-
      connected: true,
-
      session: { address: metamask["address"] },
-
      signer: metamaskSigner,
-
    } : {
-
      connected: false,
-
      signer: metamaskSigner,
-
    };
+
    this.metamask =
+
      metamask && metamaskSigner
+
        ? {
+
            connected: true,
+
            session: { address: metamask["address"] },
+
            signer: metamaskSigner,
+
          }
+
        : {
+
            connected: false,
+
            signer: metamaskSigner,
+
          };
    this.walletConnect = {
      bridge: config.walletConnect.bridge,
      client: wc.connector,
@@ -125,7 +127,7 @@ export class Config {
    this.abi = config.abi;
    this.ceramic = {
      client: ceramic,
-
      registry: config.ceramic.registry
+
      registry: config.ceramic.registry,
    };
    this.tokens = cfg.tokens;
    this.token = new ethers.Contract(
@@ -139,7 +141,9 @@ export class Config {
    this.network = ethers.providers.getNetwork(chainId);
  }

-
  setSigner(signer: ethers.Signer & TypedDataSigner | WalletConnectSigner): void {
+
  setSigner(
+
    signer: (ethers.Signer & TypedDataSigner) | WalletConnectSigner,
+
  ): void {
    this.signer = signer;
  }

@@ -151,7 +155,7 @@ export class Config {
    const wc = Config.initializeWalletConnect(
      this.walletConnect.bridge,
      this.walletConnect.state,
-
      this.provider
+
      this.provider,
    );
    this.walletConnect.client = wc.connector;
    this.walletConnect.signer = wc.signer;
@@ -163,7 +167,7 @@ export class Config {
  static initializeWalletConnect(
    bridge: string,
    state: Writable<WalletConnectState>,
-
    provider: ethers.providers.JsonRpcProvider
+
    provider: ethers.providers.JsonRpcProvider,
  ): {
    connector: WalletConnect;
    signer: WalletConnectSigner;
@@ -176,8 +180,8 @@ export class Config {
        },
        close: () => {
          // We handle the "close" event through the "disconnect" handler.
-
        }
-
      }
+
        },
+
      },
    });
    walletConnect.on("modal_closed", () => {
      state.set({ state: "close" });
@@ -196,10 +200,13 @@ export class Config {
    // designed for browsers and not mobile apps which often show a much bigger
    // icon, resulting in a blurry image.
    (walletConnect as any)._clientMeta.icons = [
-
      `${window.location.protocol}//${window.location.host}/logo.png`
+
      `${window.location.protocol}//${window.location.host}/logo.png`,
    ];

-
    const walletConnectSigner = new WalletConnectSigner(walletConnect, provider);
+
    const walletConnectSigner = new WalletConnectSigner(
+
      walletConnect,
+
      provider,
+
    );

    return {
      connector: walletConnect,
@@ -219,7 +226,10 @@ function getProvider(
  metamask: ethers.providers.JsonRpcProvider | null,
): ethers.providers.JsonRpcProvider {
  if (import.meta.env.PROD) {
-
    return new ethers.providers.AlchemyWebSocketProvider(network.name, config.alchemy.key);
+
    return new ethers.providers.AlchemyWebSocketProvider(
+
      network.name,
+
      config.alchemy.key,
+
    );
  } else if (metamask) {
    return metamask;
  } else if (import.meta.env.DEV) {
@@ -233,8 +243,10 @@ function getProvider(
}

// Checks if the promise metamask.ready returns the network, else timesout after 4 seconds.
-
function checkMetaMask(metamask: ethers.providers.Web3Provider): Promise<ethers.providers.Network | null> {
-
  return new Promise((resolve) => {
+
function checkMetaMask(
+
  metamask: ethers.providers.Web3Provider,
+
): Promise<ethers.providers.Network | null> {
+
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(null);
    }, 4000);
@@ -258,17 +270,13 @@ export async function getConfig(): Promise<Config> {
    if (ready) network = ready;
  }

-
  const networkConfig = (<Record<string, any>> config)[network.name];
-
  if (! networkConfig) {
+
  const networkConfig = (<Record<string, any>>config)[network.name];
+
  if (!networkConfig) {
    throw new Error(`Network ${network.name} is not supported`);
  }

  const provider = getProvider(network, networkConfig, metamask);
-
  const cfg = new Config(
-
    network,
-
    provider,
-
    metamaskSigner,
-
  );
+
  const cfg = new Config(network, provider, metamaskSigner);
  console.debug("config", cfg);

  return cfg;
modified src/ens/DomainInput.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import { onMount } from 'svelte';
+
  import { onMount } from "svelte";

  export let root: string;
  export let placeholder = "";
@@ -21,13 +21,13 @@
    /* margin: 1rem; */
    /* margin-left: 0; */
    /* margin-right: 0; */
-
	/* padding: 0.7rem 2rem; */
-
	padding: .4rem 1rem;
+
    /* padding: 0.7rem 2rem; */
+
    padding: 0.4rem 1rem;
    color: var(--color-secondary);
    border-radius: 0 var(--border-radius-round) var(--border-radius-round) 0;
    border: 1px solid var(--color-secondary);
    border-left: none;
-
	height: var(--button-regular-height);
+
    height: var(--button-regular-height);
  }
  input {
    line-height: 1.5;
@@ -55,9 +55,8 @@
    {disabled}
    {placeholder}
    bind:this={element}
-
    bind:value={value}
+
    bind:value
    on:input
-
    on:click
-
  />
+
    on:click />
  <span class="root">.{root}</span>
</main>
modified src/ens/SetName.svelte
@@ -1,25 +1,23 @@
<script lang="ts">
-
  import { createEventDispatcher } from 'svelte';
-
  import { navigate } from 'svelte-routing';
-
  import Modal from '@app/Modal.svelte';
-
  import type { Config } from '@app/config';
-
  import { formatAddress, isAddressEqual } from '@app/utils';
-
  import DomainInput from '@app/ens/DomainInput.svelte';
-
  import { Org } from '@app/base/orgs/Org';
-
  import type { User } from '@app/base/users/User';
-
  import Loading from '@app/Loading.svelte';
-
  import Error from '@app/Error.svelte';
-
  import Address from '@app/Address.svelte';
-
  import * as utils from '@app/utils';
+
  import { createEventDispatcher } from "svelte";
+
  import { navigate } from "svelte-routing";
+
  import Modal from "@app/Modal.svelte";
+
  import type { Config } from "@app/config";
+
  import { formatAddress, isAddressEqual } from "@app/utils";
+
  import DomainInput from "@app/ens/DomainInput.svelte";
+
  import { Org } from "@app/base/orgs/Org";
+
  import type { User } from "@app/base/users/User";
+
  import Loading from "@app/Loading.svelte";
+
  import Error from "@app/Error.svelte";
+
  import Address from "@app/Address.svelte";
+
  import * as utils from "@app/utils";

  const dispatch = createEventDispatcher();

  export let entity: Org | User;
  export let config: Config;

-
  const org = Org.hasOwnProperty.call(entity, "owner")
-
    ? entity as Org
-
    : null;
+
  const org = Org.hasOwnProperty.call(entity, "owner") ? (entity as Org) : null;

  const label = org ? "org" : "profile";

@@ -52,7 +50,7 @@

    if (resolved && isAddressEqual(resolved, entity.address)) {
      try {
-
        if (org && await utils.isSafe(org.owner, config)) {
+
        if (org && (await utils.isSafe(org.owner, config))) {
          state = State.Proposing;
          await org.setNameMultisig(domain, config);
          state = State.Proposed;
@@ -76,53 +74,51 @@

{#if state === State.Success}
  <Modal floating>
-
    <div slot="title">
-
-
    </div>
+
    <div slot="title">✅</div>

    <div slot="subtitle">
      The ENS name for {entity.address} was set to
-
      <strong>{name}.{config.registrar.domain}</strong>.
+
      <strong>{name}.{config.registrar.domain}</strong>
+
      .
    </div>

    <div slot="actions">
-
      <button class="regular" on:click={() => dispatch('close')}>
-
        Done
-
      </button>
+
      <button class="regular" on:click={() => dispatch("close")}>Done</button>
    </div>
  </Modal>
{:else if state === State.Proposed && org}
  <Modal floating>
-
    <div slot="title">
-
      🪴
-
    </div>
+
    <div slot="title">🪴</div>

    <div slot="subtitle">
-
      <p>The transaction to set the ENS name for <strong>{formatAddress(entity.address)}</strong>
-
      to <strong>{name}.{config.registrar.domain}</strong> was proposed to:</p>
+
      <p>
+
        The transaction to set the ENS name for <strong>
+
          {formatAddress(entity.address)}
+
        </strong>
+
        to
+
        <strong>{name}.{config.registrar.domain}</strong>
+
        was proposed to:
+
      </p>
      <p><Address address={org.owner} {config} compact /></p>
    </div>

    <div slot="actions">
-
      <button class="regular" on:click={() => dispatch('close')}>
-
        Done
-
      </button>
+
      <button class="regular" on:click={() => dispatch("close")}>Done</button>
    </div>
  </Modal>
{:else if state === State.Mismatch}
  <Error floating title="🧣" on:close>
-
    The name <strong>{name}.{config.registrar.domain}</strong> does not
-
    resolve to <strong>{entity.address}</strong>. Please update
-
    the ENS record for {name}.{config.registrar.domain} to
-
    point to the correct address and try again.
+
    The name <strong>{name}.{config.registrar.domain}</strong>
+
    does not resolve to
+
    <strong>{entity.address}</strong>
+
    . Please update the ENS record for {name}.{config.registrar.domain} to point
+
    to the correct address and try again.

    <div slot="actions">
      <button on:click={() => navigate(`/registrations/${name}`)}>
        Go to registration &rarr;
      </button>
-
      <button on:click={() => dispatch('close')} class="text">
-
        Close
-
      </button>
+
      <button on:click={() => dispatch("close")} class="text">Close</button>
    </div>
  </Error>
{:else if state === State.Failed && error}
@@ -141,20 +137,23 @@
        Waiting for transaction to be processed...
      {:else if state === State.Proposing && org}
        Proposal is being submitted
-
        <strong>{formatAddress(org.owner)}</strong>,
-
        please sign the transaction in your wallet.
+
        <strong>{formatAddress(org.owner)}</strong>
+
        , please sign the transaction in your wallet.
      {:else}
        Set an ENS name for <strong>{formatAddress(entity.address)}</strong>
-
        to associate a profile.
-
        ENS profiles provide human-identifiable data to your {label}, such as a
-
        unique name, avatar and URL, and help make your {label} more discoverable.
+
        to associate a profile. ENS profiles provide human-identifiable data to your
+
        {label}, such as a unique name, avatar and URL, and help make your {label}
+
        more discoverable.
      {/if}
    </div>

    <div slot="body">
      {#if state === State.Idle || state === State.Checking}
-
        <DomainInput root={config.registrar.domain}
-
          autofocus disabled={state !== State.Idle} bind:value={name} />
+
        <DomainInput
+
          root={config.registrar.domain}
+
          autofocus
+
          disabled={state !== State.Idle}
+
          bind:value={name} />
      {:else}
        <Loading small center />
      {/if}
@@ -162,15 +161,18 @@

    <div slot="actions">
      {#if state === State.Signing}
-
        <button class="regular" on:click={() => dispatch('close')}>
+
        <button class="regular" on:click={() => dispatch("close")}>
          Cancel
        </button>
      {:else if state === State.Pending}
-
        <button class="regular" on:click={() => dispatch('close')}>
+
        <button class="regular" on:click={() => dispatch("close")}>
          Close
        </button>
      {:else}
-
        <button class="primary" on:click={onSubmit} disabled={!name || state !== State.Idle}>
+
        <button
+
          class="primary"
+
          on:click={onSubmit}
+
          disabled={!name || state !== State.Idle}>
          {#if state === State.Checking}
            Checking...
          {:else}
@@ -178,9 +180,7 @@
          {/if}
        </button>

-
        <button class="text" on:click={() => dispatch('close')}>
-
          Cancel
-
        </button>
+
        <button class="text" on:click={() => dispatch("close")}>Cancel</button>
      {/if}
    </div>
  </Modal>
modified src/error.ts
@@ -17,9 +17,9 @@ export const error = writable<Err | null>(null);
export class Unreachable extends Error {
  constructor(value?: never) {
    if (value) {
-
      super('unreachable value reached: ' + value);
+
      super("unreachable value reached: " + value);
    } else {
-
      super('unreachable code reached');
+
      super("unreachable code reached");
    }
  }
}
@@ -55,13 +55,19 @@ class AssertionError extends Error {
}

export function assert(value: unknown, message?: string): asserts value {
-
  if (! value) {
+
  if (!value) {
    throw new AssertionError(message);
  }
}

-
export function assertEq(actual: unknown, expected: unknown, message?: string): void {
+
export function assertEq(
+
  actual: unknown,
+
  expected: unknown,
+
  message?: string,
+
): void {
  if (actual !== expected) {
-
    throw new AssertionError(`assertion failed: expected '${expected}', got '${actual}': ${message}`);
+
    throw new AssertionError(
+
      `assertion failed: expected '${expected}', got '${actual}': ${message}`,
+
    );
  }
}
modified src/issue.ts
@@ -1,5 +1,5 @@
-
import { type Host, Request } from '@app/api';
-
import type { Author } from '@app/cobs';
+
import { type Host, Request } from "@app/api";
+
import type { Author } from "@app/cobs";

export interface TimelineItem {
  person: Author;
@@ -18,12 +18,14 @@ export interface IIssue {
  timestamp: number;
}

-
export type State = {
-
  status: "open";
-
} | {
-
  status: "closed";
-
  reason: string;
-
};
+
export type State =
+
  | {
+
      status: "open";
+
    }
+
  | {
+
      status: "closed";
+
      reason: string;
+
    };

export interface Comment<R = null> {
  author: Author;
@@ -37,11 +39,17 @@ export type Thread = Comment<Comment[]>;

export type Label = string;

-
export function groupIssues(issues: Issue[]): { open: Issue[]; closed: Issue[] } {
-
  return issues.reduce((acc, issue) => {
-
    acc[issue.state.status].push(issue);
-
    return acc;
-
  }, { open: [] as Issue[], closed: [] as Issue[] });
+
export function groupIssues(issues: Issue[]): {
+
  open: Issue[];
+
  closed: Issue[];
+
} {
+
  return issues.reduce(
+
    (acc, issue) => {
+
      acc[issue.state.status].push(issue);
+
      return acc;
+
    },
+
    { open: [] as Issue[], closed: [] as Issue[] },
+
  );
}

export class Issue {
@@ -74,12 +82,22 @@ export class Issue {
  }

  static async getIssues(urn: string, host: Host): Promise<Issue[]> {
-
    const response: IIssue[] = await new Request(`projects/${urn}/issues`, host).get();
+
    const response: IIssue[] = await new Request(
+
      `projects/${urn}/issues`,
+
      host,
+
    ).get();
    return response.map(issue => new Issue(issue));
  }

-
  static async getIssue(urn: string, issue: string, host: Host): Promise<Issue> {
-
    const response: IIssue = await new Request(`projects/${urn}/issues/${issue}`, host).get();
+
  static async getIssue(
+
    urn: string,
+
    issue: string,
+
    host: Host,
+
  ): Promise<Issue> {
+
    const response: IIssue = await new Request(
+
      `projects/${urn}/issues/${issue}`,
+
      host,
+
    ).get();
    return new Issue(response);
  }
}
modified src/patch.ts
@@ -66,10 +66,13 @@ export interface Merge {
}

export function groupPatches(patches: Patch[]) {
-
  return patches.reduce((acc: { [state: string]: Patch[] }, patch) => {
-
    acc[patch.state].push(patch);
-
    return acc;
-
  }, { proposed: [] as Patch[], draft: [] as Patch[], archived: [] as Patch[] });
+
  return patches.reduce(
+
    (acc: { [state: string]: Patch[] }, patch) => {
+
      acc[patch.state].push(patch);
+
      return acc;
+
    },
+
    { proposed: [] as Patch[], draft: [] as Patch[], archived: [] as Patch[] },
+
  );
}

export class Patch implements IPatch {
@@ -108,20 +111,24 @@ export class Patch implements IPatch {
      timestamp: this.revisions[rev].comment.timestamp,
      inner: this.revisions[rev].comment,
    };
-
    const discussions = this.revisions[rev].discussion.map((comment): TimelineElement => {
-
      return {
-
        type: TimelineType.Thread,
-
        timestamp: comment.timestamp,
-
        inner: comment,
-
      };
-
    });
-
    const reviews = Object.entries(this.revisions[rev].reviews).map(([, review]): TimelineElement => {
-
      return {
-
        type: TimelineType.Review,
-
        timestamp: review.timestamp,
-
        inner: review,
-
      };
-
    });
+
    const discussions = this.revisions[rev].discussion.map(
+
      (comment): TimelineElement => {
+
        return {
+
          type: TimelineType.Thread,
+
          timestamp: comment.timestamp,
+
          inner: comment,
+
        };
+
      },
+
    );
+
    const reviews = Object.entries(this.revisions[rev].reviews).map(
+
      ([, review]): TimelineElement => {
+
        return {
+
          type: TimelineType.Review,
+
          timestamp: review.timestamp,
+
          inner: review,
+
        };
+
      },
+
    );
    const merges = this.revisions[rev].merges.map((merge): TimelineElement => {
      return {
        type: TimelineType.Merge,
@@ -134,12 +141,22 @@ export class Patch implements IPatch {
  }

  static async getPatches(urn: string, host: Host): Promise<Patch[]> {
-
    const response: IPatch[] = await new Request(`projects/${urn}/patches`, host).get();
-
    return response.map((patch) => new Patch(patch));
+
    const response: IPatch[] = await new Request(
+
      `projects/${urn}/patches`,
+
      host,
+
    ).get();
+
    return response.map(patch => new Patch(patch));
  }

-
  static async getPatch(urn: string, patch: string, host: Host): Promise<Patch> {
-
    const response: IPatch = await new Request(`projects/${urn}/patches/${patch}`, host).get();
+
  static async getPatch(
+
    urn: string,
+
    patch: string,
+
    host: Host,
+
  ): Promise<Patch> {
+
    const response: IPatch = await new Request(
+
      `projects/${urn}/patches/${patch}`,
+
      host,
+
    ).get();
    return new Patch(response);
  }
}
@@ -157,28 +174,31 @@ export const formatVerdict = (verdict: string | null): string => {
  }
};

-

export enum TimelineType {
  Comment,
  Thread,
  Review,
-
  Merge
+
  Merge,
}

-
export type TimelineElement = {
-
  type: TimelineType.Thread;
-
  inner: Thread;
-
  timestamp: number;
-
} | {
-
  type: TimelineType.Comment;
-
  inner: Comment;
-
  timestamp: number;
-
} | {
-
  type: TimelineType.Merge;
-
  inner: Merge;
-
  timestamp: number;
-
} | {
-
  type: TimelineType.Review;
-
  inner: Review;
-
  timestamp: number;
-
};
+
export type TimelineElement =
+
  | {
+
      type: TimelineType.Thread;
+
      inner: Thread;
+
      timestamp: number;
+
    }
+
  | {
+
      type: TimelineType.Comment;
+
      inner: Comment;
+
      timestamp: number;
+
    }
+
  | {
+
      type: TimelineType.Merge;
+
      inner: Merge;
+
      timestamp: number;
+
    }
+
  | {
+
      type: TimelineType.Review;
+
      inner: Review;
+
      timestamp: number;
+
    };
modified src/polyfills/canvas.js
@@ -1,7 +1,7 @@
// A simple mock for some HTMLCanvasElement functions to run in a node env.

HTMLCanvasElement.prototype.getContext = () => ({
-
  fillRect: () => null
+
  fillRect: () => null,
});

HTMLCanvasElement.prototype.toDataURL = () => null;
modified src/polyfills/enc-utils.js
@@ -4,12 +4,12 @@

"use strict";

-
import { Buffer } from 'buffer/';
-
import typedarrayToBuffer from 'typedarray-to-buffer';
+
import { Buffer } from "buffer/";
+
import typedarrayToBuffer from "typedarray-to-buffer";

-
const ENC_UTF8 = 'utf8';
-
const ENC_HEX = 'hex';
-
const STRING_ZERO = '0';
+
const ENC_UTF8 = "utf8";
+
const ENC_HEX = "hex";
+
const STRING_ZERO = "0";

export function bufferToArray(buf) {
  return new Uint8Array(buf);
@@ -41,11 +41,11 @@ export function hexToBuffer(hex) {
}

export function removeHexPrefix(hex) {
-
  return hex.replace(/^0x/, '');
+
  return hex.replace(/^0x/, "");
}

export function addHexPrefix(hex) {
-
  return hex.startsWith('0x') ? hex : `0x${hex}`;
+
  return hex.startsWith("0x") ? hex : `0x${hex}`;
}

export function arrayToBuffer(arr) {
@@ -90,12 +90,7 @@ export function concatArrays(...args) {
  return new Uint8Array([...result]);
}

-
export function padString(
-
  str,
-
  length,
-
  left,
-
  padding = STRING_ZERO
-
) {
+
export function padString(str, length, left, padding = STRING_ZERO) {
  const diff = length - str.length;
  let result = str;
  if (diff > 0) {
modified src/polyfills/events.ts
@@ -1,5 +1,5 @@
// This shim is used as a stand-in for the node "events" library, used by
// the "keccack" package depended on by @gnosis.pm/safe-core-sdk.
-
import events from 'events';
+
import events from "events";

export default events;
modified src/polyfills/fetch.js
@@ -1,7 +1,7 @@
/* eslint-disable no-undef */
-
import fetch from 'node-fetch';
+
import fetch from "node-fetch";

-
if (! globalThis.fetch) {
+
if (!globalThis.fetch) {
  globalThis.fetch = fetch;
  globalThis.Headers = fetch.Headers;
  globalThis.Request = fetch.Request;
modified src/polyfills/stream.ts
@@ -1,6 +1,6 @@
// This shim is used as a stand-in for the node "stream" library, used by
// the "keccack" package depended on by @gnosis.pm/safe-core-sdk.
-
import * as streams from '@stardazed/streams';
+
import * as streams from "@stardazed/streams";

// "Transform" is the old name for "TransformStream".
export const Transform = streams.TransformStream;
modified src/polyfills/typedarray-to-buffer.js
@@ -2,8 +2,8 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */

-
import isTypedArray from 'is-typedarray';
-
import { Buffer } from 'buffer/';
+
import isTypedArray from "is-typedarray";
+
import { Buffer } from "buffer/";

export default function typedarrayToBuffer(arr) {
  if (isTypedArray.strict(arr)) {
modified src/profile.ts
@@ -1,8 +1,14 @@
import type { EnsProfile } from "@app/base/registrations/registrar";
-
import type { BasicProfile } from '@datamodels/identity-profile-basic';
+
import type { BasicProfile } from "@datamodels/identity-profile-basic";
import {
-
  isAddress, formatCAIP10Address, formatIpfsFile, resolveEnsProfile,
-
  resolveIdxProfile, parseUsername, AddressType, identifyAddress
+
  isAddress,
+
  formatCAIP10Address,
+
  formatIpfsFile,
+
  resolveEnsProfile,
+
  resolveIdxProfile,
+
  parseUsername,
+
  AddressType,
+
  identifyAddress,
} from "@app/utils";
import type { Config } from "@app/config";
import { cached } from "@app/cache";
@@ -64,15 +70,23 @@ export class Profile {
  }

  get github(): string | undefined {
-
    if (this.profile?.ens?.github) return parseUsername(this.profile.ens.github);
-
    else if (this.profile?.idx?.affiliations) return this.profile.idx?.affiliations.find(item => item === "github");
-
    else return undefined;
+
    if (this.profile?.ens?.github) {
+
      return parseUsername(this.profile.ens.github);
+
    } else if (this.profile?.idx?.affiliations) {
+
      return this.profile.idx?.affiliations.find(item => item === "github");
+
    } else {
+
      return undefined;
+
    }
  }

  get twitter(): string | undefined {
-
    if (this.profile?.ens?.twitter) return parseUsername(this.profile.ens.twitter);
-
    else if (this.profile?.idx?.affiliations) return this.profile.idx.affiliations.find(item => item === "twitter");
-
    else return undefined;
+
    if (this.profile?.ens?.twitter) {
+
      return parseUsername(this.profile.ens.twitter);
+
    } else if (this.profile?.idx?.affiliations) {
+
      return this.profile.idx.affiliations.find(item => item === "twitter");
+
    } else {
+
      return undefined;
+
    }
  }

  get url(): string | undefined {
@@ -88,9 +102,13 @@ export class Profile {
  }

  get avatar(): string | undefined {
-
    if (this.profile?.ens?.avatar) return this.profile.ens.avatar;
-
    else if (this.profile?.idx?.image?.original?.src) return formatIpfsFile(this.profile.idx.image.original.src);
-
    else return undefined;
+
    if (this.profile?.ens?.avatar) {
+
      return this.profile.ens.avatar;
+
    } else if (this.profile?.idx?.image?.original?.src) {
+
      return formatIpfsFile(this.profile.idx.image.original.src);
+
    } else {
+
      return undefined;
+
    }
  }

  // We add null here to differentiate between a `undefined` and a invalid / null seed
@@ -104,7 +122,7 @@ export class Profile {
    if (addr) {
      // TODO: Workaround until caip package supports both CAIP10 formats.
      const [namespace, reference, address] = addr.split(":");
-
      const id = { "chainId": { namespace, reference }, address };
+
      const id = { chainId: { namespace, reference }, address };

      // Ethereum address.
      if (typeof id.chainId === "object" && id.chainId.namespace === "eip155") {
@@ -124,8 +142,15 @@ export class Profile {
  // Returns the corresponding registration form to edit a user profile.
  // We are not interested in a non-existant registry link, since we check before hand if the name exists.
  registry(config: Config): string {
-
    if (this.profile?.ens) return `/registrations/${this.profile.ens.name}`;
-
    else return `${config.ceramic.registry}${formatCAIP10Address(this.profile.address, "eip155", config.network.chainId)}`;
+
    if (this.profile?.ens) {
+
      return `/registrations/${this.profile.ens.name}`;
+
    } else {
+
      return `${config.ceramic.registry}${formatCAIP10Address(
+
        this.profile.address,
+
        "eip155",
+
        config.network.chainId,
+
      )}`;
+
    }
  }

  // Get confirmed anchors.
@@ -162,7 +187,10 @@ export class Profile {
    }
  }

-
  async confirmedProjectAnchors(urn: string, config: Config): Promise<string[]> {
+
  async confirmedProjectAnchors(
+
    urn: string,
+
    config: Config,
+
  ): Promise<string[]> {
    const storage = this.anchorsAccount || this.org?.address;

    if (storage) {
@@ -187,7 +215,7 @@ export class Profile {
  private static async lookupProfile(
    addressOrName: string,
    profileType: ProfileType,
-
    config: Config
+
    config: Config,
  ): Promise<IProfile> {
    let type = AddressType.EOA;
    let org: Org | null = null;
@@ -205,11 +233,10 @@ export class Profile {
          address: ens.address.toLowerCase(),
          type,
          ens: { ...ens, address: ens.address.toLowerCase() },
-
          org: org ?? undefined
+
          org: org ?? undefined,
        };
      }
      throw new MissingReverseRecord(`No address set for ${addressOrName}`);
-

    } else if (isAddress(addressOrName)) {
      const address = addressOrName.toLowerCase();

@@ -220,13 +247,14 @@ export class Profile {

      try {
        const idx = await resolveIdxProfile(
-
          formatCAIP10Address(address, "eip155", config.network.chainId), config
+
          formatCAIP10Address(address, "eip155", config.network.chainId),
+
          config,
        );
        return {
          address,
          type,
          idx: idx ?? undefined,
-
          org: org ?? undefined
+
          org: org ?? undefined,
        };
      } catch (e: any) {
        // Look for the No DID found for error by the resolveIdxProfile fn and send it to console.debug
@@ -239,12 +267,17 @@ export class Profile {
    throw new NotFoundError(`Not able to resolve profile for ${addressOrName}`);
  }

-
  static async getMulti(addressesOrNames: string[], config: Config): Promise<Profile[]> {
-
    const profilePromises = addressesOrNames.map(
-
      addressOrName => this.lookupProfile(addressOrName, ProfileType.Minimal, config)
+
  static async getMulti(
+
    addressesOrNames: string[],
+
    config: Config,
+
  ): Promise<Profile[]> {
+
    const profilePromises = addressesOrNames.map(addressOrName =>
+
      this.lookupProfile(addressOrName, ProfileType.Minimal, config),
    );
    const profiles = await Promise.all(profilePromises);
-
    return profiles.map(profile => { return new Profile(profile); });
+
    return profiles.map(profile => {
+
      return new Profile(profile);
+
    });
  }

  static async get(
@@ -252,7 +285,11 @@ export class Profile {
    profileType: ProfileType,
    config: Config,
  ): Promise<Profile> {
-
    const profile = await this.lookupProfile(addressOrName, profileType, config);
+
    const profile = await this.lookupProfile(
+
      addressOrName,
+
      profileType,
+
      config,
+
    );
    return new Profile(profile);
  }
}
@@ -261,6 +298,6 @@ export const getBalance = cached(
  async (address: string, config: Config) => {
    return await config.provider.getBalance(address);
  },
-
  (address) => address,
-
  { max: 1000 }
+
  address => address,
+
  { max: 1000 },
);
modified src/project.ts
@@ -1,24 +1,26 @@
-
import { navigate } from 'svelte-routing';
-
import { get, writable } from 'svelte/store';
-
import { type Host, Request } from '@app/api';
-
import type { Commit, CommitHeader, CommitsHistory } from '@app/commit';
-
import { isOid, isRadicleId } from '@app/utils';
-
import { Profile, ProfileType } from '@app/profile';
-
import { Seed } from '@app/base/seeds/Seed';
-
import type { Config } from '@app/config';
+
import { navigate } from "svelte-routing";
+
import { get, writable } from "svelte/store";
+
import { type Host, Request } from "@app/api";
+
import type { Commit, CommitHeader, CommitsHistory } from "@app/commit";
+
import { isOid, isRadicleId } from "@app/utils";
+
import { Profile, ProfileType } from "@app/profile";
+
import { Seed } from "@app/base/seeds/Seed";
+
import type { Config } from "@app/config";

export type Urn = string;
export type PeerId = string;
export type Branches = { [key: string]: string };

-
export type Delegate = {
-
  type: "indirect";
-
  urn: Urn;
-
  ids: PeerId[];
-
} | {
-
  type: "direct";
-
  id: PeerId;
-
};
+
export type Delegate =
+
  | {
+
      type: "indirect";
+
      urn: Urn;
+
      ids: PeerId[];
+
    }
+
  | {
+
      type: "direct";
+
      id: PeerId;
+
    };

export interface Anchor {
  confirmed: true;
@@ -46,7 +48,7 @@ export enum ProjectContent {
  Issues,
  Issue,
  Patches,
-
  Patch
+
  Patch,
}

export interface ProjectInfo {
@@ -157,7 +159,8 @@ export function browse(browse: BrowseTo): void {
}

export function path(opts: PathOptions): string {
-
  const { urn, profile, seed, peer, content, revision, path, issue, patch } = opts;
+
  const { urn, profile, seed, peer, content, revision, path, issue, patch } =
+
    opts;
  const result = [];

  if (profile) {
@@ -235,13 +238,21 @@ export function getOid(revision: string, branches?: Branches): string | null {
}

// Parses the path consisting of a revision (eg. branch or commit) and file path into a tuple [revision, file-path]
-
export function parseRoute(input: string, branches: Branches): { path?: string; revision?: string } {
-
  const branch = Object.entries(branches).find(([branchName,]) => input.startsWith(branchName));
+
export function parseRoute(
+
  input: string,
+
  branches: Branches,
+
): { path?: string; revision?: string } {
+
  const branch = Object.entries(branches).find(([branchName]) =>
+
    input.startsWith(branchName),
+
  );
  const commitPath = [input.slice(0, 40), input.slice(41)];
  const parsed: { path?: string; revision?: string } = {};

  if (branch) {
-
    const [rev, path] = [input.slice(0, branch[0].length), input.slice(branch[0].length + 1)];
+
    const [rev, path] = [
+
      input.slice(0, branch[0].length),
+
      input.slice(branch[0].length + 1),
+
    ];

    parsed.revision = rev;
    parsed.path = path ? path : "/";
@@ -271,7 +282,15 @@ export class Project implements ProjectInfo {
  patches?: number;
  issues?: number;

-
  constructor(urn: string, info: ProjectInfo, seed: Seed, peers: Peer[], branches: Branches, profile: Profile | null, anchors: string[]) {
+
  constructor(
+
    urn: string,
+
    info: ProjectInfo,
+
    seed: Seed,
+
    peers: Peer[],
+
    branches: Branches,
+
    profile: Profile | null,
+
    anchors: string[],
+
  ) {
    this.urn = urn;
    this.head = info.head;
    this.name = info.name;
@@ -294,7 +313,7 @@ export class Project implements ProjectInfo {
    const head = this.branches[this.defaultBranch];
    const commit = revision ? getOid(revision, this.branches) : head;

-
    if (! commit) {
+
    if (!commit) {
      throw new Error(`Revision ${revision} not found`);
    }
    const tree = await this.getTree(commit, "/");
@@ -307,7 +326,7 @@ export class Project implements ProjectInfo {

    return {
      ...info,
-
      ...info.meta // Nb. This is only needed while we are upgrading to the new http-api.
+
      ...info.meta, // Nb. This is only needed while we are upgrading to the new http-api.
    };
  }

@@ -315,11 +334,18 @@ export class Project implements ProjectInfo {
    return new Request("projects", host).get();
  }

-
  static async getDelegateProjects(delegate: string, host: Host): Promise<ProjectInfo[]> {
+
  static async getDelegateProjects(
+
    delegate: string,
+
    host: Host,
+
  ): Promise<ProjectInfo[]> {
    return new Request(`delegates/${delegate}/projects`, host).get();
  }

-
  static async getRemote(urn: string, peer: string, host: Host): Promise<Remote> {
+
  static async getRemote(
+
    urn: string,
+
    peer: string,
+
    host: Host,
+
  ): Promise<Remote> {
    return new Request(`projects/${urn}/remotes/${peer}`, host).get();
  }

@@ -337,15 +363,15 @@ export class Project implements ProjectInfo {
      perPage?: number;
      page?: number;
      verified?: boolean;
-
    }
+
    },
  ): Promise<CommitsHistory> {
    const params: Record<string, any> = {
-
      "parent": opts?.parent,
-
      "since": opts?.since,
-
      "until": opts?.until,
+
      parent: opts?.parent,
+
      since: opts?.since,
+
      until: opts?.until,
      "per-page": opts?.perPage,
-
      "page": opts?.page,
-
      "verified": opts?.verified
+
      page: opts?.page,
+
      verified: opts?.verified,
    };
    return new Request(`projects/${urn}/commits`, host).get(params);
  }
@@ -358,15 +384,18 @@ export class Project implements ProjectInfo {
  }

  async getCommit(commit: string): Promise<Commit> {
-
    return new Request(`projects/${this.urn}/commits/${commit}`, this.seed.api).get();
+
    return new Request(
+
      `projects/${this.urn}/commits/${commit}`,
+
      this.seed.api,
+
    ).get();
  }

-
  async getTree(
-
    commit: string,
-
    path: string,
-
  ): Promise<Tree> {
+
  async getTree(commit: string, path: string): Promise<Tree> {
    if (path === "/") path = "";
-
    return new Request(`projects/${this.urn}/tree/${commit}/${path}`, this.seed.api).get();
+
    return new Request(
+
      `projects/${this.urn}/tree/${commit}/${path}`,
+
      this.seed.api,
+
    ).get();
  }

  async getBlob(
@@ -374,13 +403,17 @@ export class Project implements ProjectInfo {
    path: string,
    options: { highlight: boolean },
  ): Promise<Blob> {
-
    return new Request(`projects/${this.urn}/blob/${commit}/${path}`, this.seed.api).get(options);
+
    return new Request(
+
      `projects/${this.urn}/blob/${commit}/${path}`,
+
      this.seed.api,
+
    ).get(options);
  }

-
  async getReadme(
-
    commit: string,
-
  ): Promise<Blob> {
-
    return new Request(`projects/${this.urn}/readme/${commit}`, this.seed.api).get();
+
  async getReadme(commit: string): Promise<Blob> {
+
    return new Request(
+
      `projects/${this.urn}/readme/${commit}`,
+
      this.seed.api,
+
    ).get();
  }

  navigateTo(browse: BrowseTo): void {
@@ -392,7 +425,7 @@ export class Project implements ProjectInfo {
    const options: PathOptions = {
      urn: this.urn,
      ...browser,
-
      ...browse
+
      ...browse,
    };

    if (this.profile) {
@@ -404,30 +437,44 @@ export class Project implements ProjectInfo {
    return path(options);
  }

-
  static async get(id: string, peer: string | null, profileName: string | null, seedHost: string | null, config: Config): Promise<Project> {
-
    const profile = profileName ? await Profile.get(profileName, ProfileType.Project, config) : null;
-
    const seed = profile ? profile.seed : seedHost ? await Seed.lookup(seedHost, config) : null;
+
  static async get(
+
    id: string,
+
    peer: string | null,
+
    profileName: string | null,
+
    seedHost: string | null,
+
    config: Config,
+
  ): Promise<Project> {
+
    const profile = profileName
+
      ? await Profile.get(profileName, ProfileType.Project, config)
+
      : null;
+
    const seed = profile
+
      ? profile.seed
+
      : seedHost
+
      ? await Seed.lookup(seedHost, config)
+
      : null;

    if (!profile && !seed) {
      throw new Error("Couldn't load project");
    }
-
    if (! seed?.valid) {
+
    if (!seed?.valid) {
      throw new Error("Couldn't load project: invalid seed");
    }

    const info = await Project.getInfo(id, seed.api);
    const urn = isRadicleId(id) ? id : info.urn;
-
    const anchors = profile ? await profile.confirmedProjectAnchors(urn, config) : [];
+
    const anchors = profile
+
      ? await profile.confirmedProjectAnchors(urn, config)
+
      : [];

    // Older versions of http-api don't include the URN.
-
    if (! info.urn) info.urn = urn;
+
    if (!info.urn) info.urn = urn;

    const peers: Peer[] = info.delegates
      ? await Project.getRemotes(urn, seed.api)
      : [];

    let remote: Remote = {
-
      heads: info.head ? { [info.defaultBranch]: info.head } : {}
+
      heads: info.head ? { [info.defaultBranch]: info.head } : {},
    };

    if (peer) {
@@ -441,18 +488,23 @@ export class Project implements ProjectInfo {
    return new Project(urn, info, seed, peers, remote.heads, profile, anchors);
  }

-
  static async getMulti(projs: { urn: Urn; seed: string }[]): Promise<{ info: ProjectInfo; seed: Host }[]> {
+
  static async getMulti(
+
    projs: { urn: Urn; seed: string }[],
+
  ): Promise<{ info: ProjectInfo; seed: Host }[]> {
    const promises = [];

    for (const proj of projs) {
      const seed = { host: proj.seed, port: null };
-
      promises.push(Project.getInfo(proj.urn, seed).then(info => {
-
        return { info, seed };
-
      }));
+
      promises.push(
+
        Project.getInfo(proj.urn, seed).then(info => {
+
          return { info, seed };
+
        }),
+
      );
    }
    const results = await Promise.allSettled(promises);
-
    const isFulfilled = <T>(input: PromiseSettledResult<T>):
-
      input is PromiseFulfilledResult<T> => input.status === 'fulfilled';
+
    const isFulfilled = <T>(
+
      input: PromiseSettledResult<T>,
+
    ): input is PromiseFulfilledResult<T> => input.status === "fulfilled";

    return results.filter(isFulfilled).map(r => r.value);
  }
modified src/session.ts
@@ -1,10 +1,13 @@
import { get, writable, derived } from "svelte/store";
import type { Readable } from "svelte/store";
-
import type { BigNumber } from 'ethers';
-
import type { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
+
import type { BigNumber } from "ethers";
+
import type {
+
  TransactionReceipt,
+
  TransactionResponse,
+
} from "@ethersproject/providers";
import { Config, getConfig } from "@app/config";
import { Unreachable, assert, assertEq } from "@app/error";
-
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
+
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
import type { WalletConnectSigner } from "./WalletConnectSigner";
import * as ethers from "ethers";
import type { SeedSession } from "./siwe";
@@ -12,27 +15,33 @@ import type { SeedSession } from "./siwe";
export enum Connection {
  Disconnected,
  Connecting,
-
  Connected
+
  Connected,
}

export type TxState =
-
    { state: 'signing' }
-
  | { state: 'pending'; hash: string }
-
  | { state: 'success'; hash: string; blockHash: string; blockNumber: number }
-
  | { state: 'fail'; hash: string; blockHash: string; blockNumber: number; error: string }
+
  | { state: "signing" }
+
  | { state: "pending"; hash: string }
+
  | { state: "success"; hash: string; blockHash: string; blockNumber: number }
+
  | {
+
      state: "fail";
+
      hash: string;
+
      blockHash: string;
+
      blockNumber: number;
+
      error: string;
+
    }
  | null;

-
export type Signer = ethers.Signer & TypedDataSigner | WalletConnectSigner;
+
export type Signer = (ethers.Signer & TypedDataSigner) | WalletConnectSigner;

// Defines the type of signer we are using in the current session.
// Allows us to guard certain functionality for a specific signer.
enum SignerType {
  WalletConnect,
-
  MetaMask
+
  MetaMask,
}

export type State =
-
    { connection: Connection.Disconnected }
+
  | { connection: Connection.Disconnected }
  | { connection: Connection.Connecting }
  | { connection: Connection.Connected; session: Session };

@@ -72,8 +81,17 @@ export const loadState = (initial: State): Store => {
      // Re-connect using previous session.
      if (config.metamask.connected) {
        const metamask = config.metamask.session;
-
        const tokenBalance: BigNumber = await config.token.balanceOf(metamask.address);
-
        const session = { address: metamask.address, signer, signerType: SignerType.MetaMask, siwe, tokenBalance, tx: null };
+
        const tokenBalance: BigNumber = await config.token.balanceOf(
+
          metamask.address,
+
        );
+
        const session = {
+
          address: metamask.address,
+
          signer,
+
          signerType: SignerType.MetaMask,
+
          siwe,
+
          tokenBalance,
+
          tx: null,
+
        };

        store.set({ connection: Connection.Connected, session });
        config.setSigner(signer);
@@ -83,10 +101,13 @@ export const loadState = (initial: State): Store => {

      const state = get(store);

-
      assertEq(state.connection, Connection.Disconnected || Connection.Connecting);
+
      assertEq(
+
        state.connection,
+
        Connection.Disconnected || Connection.Connecting,
+
      );
      store.set({ connection: Connection.Connecting });

-
      await window.ethereum.request({ method: 'eth_requestAccounts' });
+
      await window.ethereum.request({ method: "eth_requestAccounts" });
      const address = await signer.getAddress();

      config.setSigner(signer);
@@ -97,7 +118,14 @@ export const loadState = (initial: State): Store => {
        config.walletConnect.state.set({ state: "close" });

        const tokenBalance: BigNumber = await config.token.balanceOf(address);
-
        const session = { address, signer, signerType: SignerType.MetaMask, siwe, tokenBalance, tx: null };
+
        const session = {
+
          address,
+
          signer,
+
          signerType: SignerType.MetaMask,
+
          siwe,
+
          tokenBalance,
+
          tx: null,
+
        };

        store.set({
          connection: Connection.Connected,
@@ -120,9 +148,16 @@ export const loadState = (initial: State): Store => {

        const address = await signer.getAddress();
        const tokenBalance: BigNumber = await config.token.balanceOf(address);
-
        const session = { address, signer, signerType: SignerType.WalletConnect, siwe, tokenBalance, tx: null };
+
        const session = {
+
          address,
+
          signer,
+
          signerType: SignerType.WalletConnect,
+
          siwe,
+
          tokenBalance,
+
          tx: null,
+
        };
        const network = ethers.providers.getNetwork(
-
          signer.walletConnect.chainId
+
          signer.walletConnect.chainId,
        );

        // Instead of killing the WalletConnect session, we force the UI to change network
@@ -130,29 +165,39 @@ export const loadState = (initial: State): Store => {
          config.changeNetwork(network.chainId);
        }

-
        config.walletConnect.client.on("session_update", async (error, { params: [{ accounts, chainId }] }: { params: [{ accounts: [string]; chainId: number }] }) => {
-
          if (error) {
-
            throw error;
-
          }
-

-
          try {
-
            // We update config to reflect the new signer address.
-
            const signer = config.getWalletConnectSigner();
-
            changeAccounts(accounts[0], signer);
-

-
            // Check the current chainId, and request Metamask to change, or reload the window to get the correct chain.
-
            if (chainId !== config.network.chainId) {
-
              if (session.signerType === SignerType.MetaMask) {
-
                await window.ethereum.request({
-
                  method: 'wallet_switchEthereumChain',
-
                  params: [{ chainId: ethers.utils.hexValue(chainId) }]
-
                });
-
              } else {
-
                window.location.reload();
+
        config.walletConnect.client.on(
+
          "session_update",
+
          async (
+
            error,
+
            {
+
              params: [{ accounts, chainId }],
+
            }: { params: [{ accounts: [string]; chainId: number }] },
+
          ) => {
+
            if (error) {
+
              throw error;
+
            }
+

+
            try {
+
              // We update config to reflect the new signer address.
+
              const signer = config.getWalletConnectSigner();
+
              changeAccounts(accounts[0], signer);
+

+
              // Check the current chainId, and request Metamask to change, or reload the window to get the correct chain.
+
              if (chainId !== config.network.chainId) {
+
                if (session.signerType === SignerType.MetaMask) {
+
                  await window.ethereum.request({
+
                    method: "wallet_switchEthereumChain",
+
                    params: [{ chainId: ethers.utils.hexValue(chainId) }],
+
                  });
+
                } else {
+
                  window.location.reload();
+
                }
              }
+
            } catch (e) {
+
              console.error(e);
            }
-
          } catch (e) { console.error(e); }
-
        });
+
          },
+
        );

        store.set({ connection: Connection.Connected, session });
      } catch (e: any) {
@@ -216,7 +261,7 @@ export const loadState = (initial: State): Store => {
      store.update(s => {
        switch (s.connection) {
          case Connection.Connected:
-
            s.session.tx = { state: 'signing' };
+
            s.session.tx = { state: "signing" };
            return s;
          default:
            throw new Unreachable();
@@ -229,9 +274,9 @@ export const loadState = (initial: State): Store => {
        switch (s.connection) {
          case Connection.Connected:
            assert(s.session.tx !== null);
-
            assert(s.session.tx.state === 'signing');
+
            assert(s.session.tx.state === "signing");

-
            s.session.tx = { state: 'pending', hash: tx.hash };
+
            s.session.tx = { state: "pending", hash: tx.hash };
            return s;
          default:
            throw new Unreachable();
@@ -244,22 +289,22 @@ export const loadState = (initial: State): Store => {
        switch (s.connection) {
          case Connection.Connected:
            assert(s.session.tx !== null);
-
            assert(s.session.tx.state === 'pending');
+
            assert(s.session.tx.state === "pending");

            if (tx.status === 1) {
              s.session.tx = {
-
                state: 'success',
+
                state: "success",
                hash: s.session.tx.hash,
                blockHash: tx.blockHash,
-
                blockNumber: tx.blockNumber
+
                blockNumber: tx.blockNumber,
              };
            } else {
              s.session.tx = {
-
                state: 'fail',
+
                state: "fail",
                hash: s.session.tx.hash,
                blockHash: tx.blockHash,
                blockNumber: tx.blockNumber,
-
                error: "Failed"
+
                error: "Failed",
              };
            }
            return s;
@@ -283,7 +328,9 @@ export const loadState = (initial: State): Store => {
              s.session.signer = signer;
              // We only save the session to localStorage if we use a MetaMask signer
              // WalletConnect does their own session persistance.
-
              if (s.session.signerType === SignerType.MetaMask) saveMetamaskSession(s.session);
+
              if (s.session.signerType === SignerType.MetaMask) {
+
                saveMetamaskSession(s.session);
+
              }
            }
            return s;
          default:
@@ -304,7 +351,7 @@ export const session = derived(state, s => {
  return null;
});

-
window.ethereum?.on('chainChanged', () => {
+
window.ethereum?.on("chainChanged", () => {
  // We disconnect the wallet to avoid out of sync state
  // between the account address and IDX DIDs
  disconnectMetamask();
@@ -321,7 +368,10 @@ window.ethereum?.on("accountsChanged", async ([address]: string) => {
  }
});

-
export async function changeAccounts(address: string, signer: Signer): Promise<void> {
+
export async function changeAccounts(
+
  address: string,
+
  signer: Signer,
+
): Promise<void> {
  const config = await getConfig();
  state.setChangedAccount(address, signer);
  state.refreshBalance(config);
@@ -335,9 +385,11 @@ export function loadSeedSessions(): { [key: string]: SeedSession } {

    // We only keep the sessions that are still valid, and remove expired ones from `localStorage`.
    // For a session to be valid the expiration time has to be bigger or equal than the current time.
-
    const activeSessions = Object.fromEntries(Object.entries(siwe).filter(([, value]) => {
-
      return new Date(value.expirationTime) >= new Date();
-
    }));
+
    const activeSessions = Object.fromEntries(
+
      Object.entries(siwe).filter(([, value]) => {
+
        return new Date(value.expirationTime) >= new Date();
+
      }),
+
    );
    window.localStorage.setItem("siwe", JSON.stringify({ ...activeSessions }));

    return activeSessions;
@@ -346,7 +398,10 @@ export function loadSeedSessions(): { [key: string]: SeedSession } {
  return {};
}

-
export async function connectSeed(seedSession: { id: string; session: SeedSession }): Promise<void> {
+
export async function connectSeed(seedSession: {
+
  id: string;
+
  session: SeedSession;
+
}): Promise<void> {
  state.connectSeed(seedSession);
}

@@ -354,7 +409,11 @@ state.subscribe(s => {
  console.debug("session.state", s);
});

-
export async function approveSpender(spender: string, amount: BigNumber, config: Config): Promise<void> {
+
export async function approveSpender(
+
  spender: string,
+
  amount: BigNumber,
+
  config: Config,
+
): Promise<void> {
  assert(config.signer);

  const signer = config.signer;
@@ -369,7 +428,7 @@ export async function approveSpender(spender: string, amount: BigNumber, config:
}

export function disconnectMetamask(): void {
-
  window.localStorage.removeItem('metamask');
+
  window.localStorage.removeItem("metamask");
  window.location.reload();
}

@@ -385,5 +444,13 @@ function saveSeedSession(session: Session): void {
}

function saveMetamaskSession(session: Session): void {
-
  window.localStorage.setItem("metamask", JSON.stringify({ address: session.address, tokenBalance: null, tx: null, config: null }));
+
  window.localStorage.setItem(
+
    "metamask",
+
    JSON.stringify({
+
      address: session.address,
+
      tokenBalance: null,
+
      tx: null,
+
      config: null,
+
    }),
+
  );
}
modified src/siwe.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { SiweMessage } from "siwe";
-
import { Request, type Host } from '@app/api';
+
import { Request, type Host } from "@app/api";
import type { Config } from "@app/config";
import { connectSeed } from "@app/session";
import type { Seed } from "@app/base/seeds/Seed";
@@ -18,7 +18,12 @@ export interface SeedSession {
  resources: string[];
}

-
export function createSiweMessage(seed: Seed, address: string, nonce: string, config: Config): string {
+
export function createSiweMessage(
+
  seed: Seed,
+
  address: string,
+
  nonce: string,
+
  config: Config,
+
): string {
  const nextWeek = new Date();
  nextWeek.setDate(nextWeek.getDate() + 7);

@@ -28,21 +33,26 @@ export function createSiweMessage(seed: Seed, address: string, nonce: string, co
    statement: "It's a Radicle world!",
    uri: window.location.origin,
    nonce,
-
    version: '1',
+
    version: "1",
    expirationTime: nextWeek.toISOString(),
-
    chainId: config.network.chainId
+
    chainId: config.network.chainId,
  });

  return message.prepareMessage();
}

-
export async function createUnauthorizedSession(host: Host): Promise<{ nonce: string; id: string }> {
+
export async function createUnauthorizedSession(
+
  host: Host,
+
): Promise<{ nonce: string; id: string }> {
  return await new Request(`sessions`, host).post();
}

/// Signs the user into given seed and returns when successfull a session id
-
export async function signInWithEthereum(seed: Seed, config: Config): Promise<{ id: string } | null> {
-
  if (! config.signer) {
+
export async function signInWithEthereum(
+
  seed: Seed,
+
  config: Config,
+
): Promise<{ id: string } | null> {
+
  if (!config.signer) {
    return null;
  }

@@ -54,8 +64,10 @@ export async function signInWithEthereum(seed: Seed, config: Config): Promise<{
  const auth: {
    id: string;
    session: SeedSession;
-
  } = await new Request(`sessions/${result.id}`, seed.api)
-
    .put({ message, signature });
+
  } = await new Request(`sessions/${result.id}`, seed.api).put({
+
    message,
+
    signature,
+
  });

  connectSeed({ id: result.id, session: auth.session });

modified src/utils.test.ts
@@ -5,42 +5,50 @@ import * as utils from "./utils";

describe("Conversions", () => {
  test("toWei", () => {
-
    expect(
-
      utils.toWei("10")
-
    ).toEqual(BigNumber.from("10000000000000000000"));
+
    expect(utils.toWei("10")).toEqual(BigNumber.from("10000000000000000000"));
  });
});

describe("Format functions", () => {
  test.each([
-
    { cid: "Qm1234567890123456789012345678901234567890", expected: "https://ipfs.io/ipfs/Qm1234567890123456789012345678901234567890" },
-
    { cid: undefined, expected: undefined }
+
    {
+
      cid: "Qm1234567890123456789012345678901234567890",
+
      expected:
+
        "https://ipfs.io/ipfs/Qm1234567890123456789012345678901234567890",
+
    },
+
    { cid: undefined, expected: undefined },
  ])("formatIpfsFile $cid => $expected", ({ cid, expected }) => {
-
    expect(
-
      utils.formatIpfsFile(cid)
-
    ).toEqual(expected);
+
    expect(utils.formatIpfsFile(cid)).toEqual(expected);
  });

  test.each([
    { amount: "1000", digits: 2, expected: "10.0" },
    { amount: "10000000000000000000", expected: "10.0" },
  ])("formatBalance", ({ amount, digits, expected }) => {
-
    expect(utils.formatBalance(BigNumber.from(amount), digits)).toEqual(expected);
+
    expect(utils.formatBalance(BigNumber.from(amount), digits)).toEqual(
+
      expected,
+
    );
  });

  test("formatRadicleId", () => {
    expect(
      utils.formatRadicleId(
-
        new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86])
-
      ))
-
      .toEqual("rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy");
+
        new Uint8Array([
+
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156, 38, 115, 99, 61, 118, 237,
+
          10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86,
+
        ]),
+
      ),
+
    ).toEqual("rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy");
  });

  test("formatProjectHash", () => {
    expect(
      utils.formatProjectHash(
-
        new Uint8Array([17, 20, 69, 234, 87, 208, 172, 127, 33, 22, 110, 216, 52, 69, 104, 219, 88, 66, 50, 152, 115, 23])
-
      )
+
        new Uint8Array([
+
          17, 20, 69, 234, 87, 208, 172, 127, 33, 22, 110, 216, 52, 69, 104,
+
          219, 88, 66, 50, 152, 115, 23,
+
        ]),
+
      ),
    ).toEqual("45ea57d0ac7f21166ed8344568db584232987317");
  });

@@ -48,28 +56,28 @@ describe("Format functions", () => {
    { hash: "#L42", expected: 42 },
    { hash: "#ETH", expected: null },
  ])("formatLocationHash $hash => $expected", ({ hash, expected }) => {
-
    expect(
-
      utils.formatLocationHash(hash))
-
      .toEqual(expected);
+
    expect(utils.formatLocationHash(hash)).toEqual(expected);
  });

  test.each([
-
    { id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1", expected: "hydkkk…coygh1" }
+
    {
+
      id: "hydkkkf5ksbe5fuszdhpqhytu3q36gwagj874wxwpo5a8ti8coygh1",
+
      expected: "hydkkk…coygh1",
+
    },
  ])("formatSeedId $id => $expected", ({ id, expected }) => {
-
    expect(
-
      utils.formatSeedId(id))
-
      .toEqual(expected);
+
    expect(utils.formatSeedId(id)).toEqual(expected);
  });

  test("formatRadicleUrn", () => {
    expect(
-
      utils.formatRadicleUrn("rad:git:hnrkemobagsicpf9sr95o3g551otspcd84c9o"))
-
      .toEqual("rad:git:hnrkem…d84c9o");
+
      utils.formatRadicleUrn("rad:git:hnrkemobagsicpf9sr95o3g551otspcd84c9o"),
+
    ).toEqual("rad:git:hnrkem…d84c9o");
  });

  test("formatRadicleUrn throw when wrong URN", () => {
-
    expect(() => utils.formatRadicleUrn("hnrkemobagsicpf9sr95o3g551otspcd84c9o"))
-
      .toThrow();
+
    expect(() =>
+
      utils.formatRadicleUrn("hnrkemobagsicpf9sr95o3g551otspcd84c9o"),
+
    ).toThrow();
  });

  test("formatCAIP10Address", () => {
@@ -77,52 +85,53 @@ describe("Format functions", () => {
      utils.formatCAIP10Address(
        "0x1234567890123456789012345678901234567890",
        "eip155",
-
        1
-
      ))
-
      .toEqual("0x1234567890123456789012345678901234567890@eip155:1");
+
        1,
+
      ),
+
    ).toEqual("0x1234567890123456789012345678901234567890@eip155:1");
  });

  test("formatAddress", () => {
    expect(
-
      utils.formatAddress(
-
        "0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511",
-
      )
+
      utils.formatAddress("0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511"),
    ).toEqual("b5d8 – F511");

-
    expect(
-
      () => utils.formatAddress(
-
        "0x8f91813",
-
      )
-
    ).toThrowError('invalid address (argument="address", value="0x8f91813", code=INVALID_ARGUMENT, version=address/5.7.0)');
+
    expect(() => utils.formatAddress("0x8f91813")).toThrowError(
+
      'invalid address (argument="address", value="0x8f91813", code=INVALID_ARGUMENT, version=address/5.7.0)',
+
    );
  });

  test.each([
-
    { hash: "0x8f918133b56bb85c18ea192549503f0ea59e3beb1f88023f442656c660018e3a", expected: "0x8f91...8e3a" },
+
    {
+
      hash: "0x8f918133b56bb85c18ea192549503f0ea59e3beb1f88023f442656c660018e3a",
+
      expected: "0x8f91...8e3a",
+
    },
    { hash: "0x8f91813", expected: "0x8f91813" }, // If the string length is less than 10 characters the entire string is returned.
  ])("formatHash $hash => $expected", ({ hash, expected }) => {
-
    expect(
-
      utils.formatHash(hash)
-
    ).toEqual(expected);
+
    expect(utils.formatHash(hash)).toEqual(expected);
  });

  test.each([
    { commit: "a8a6a979a6261a2ec1ea85fc9a65a4a30aa22cc8", expected: "a8a6a97" },
-
    { commit: "a8a6a97", expected: "a8a6a97" }
+
    { commit: "a8a6a97", expected: "a8a6a97" },
  ])("formatCommit $commit => $expected", ({ commit, expected }) => {
-
    expect(
-
      utils.formatCommit(commit)
-
    ).toEqual(expected);
+
    expect(utils.formatCommit(commit)).toEqual(expected);
  });
});

describe("String Assertions", () => {
  test.each([
-
    { a: "0x1234567890123456789012345678901234567890", b: "0x1234567890123456789012345678901234567890", expected: true },
-
    { a: "0x1234567890123456789012345678901234567890", b: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", expected: false }
+
    {
+
      a: "0x1234567890123456789012345678901234567890",
+
      b: "0x1234567890123456789012345678901234567890",
+
      expected: true,
+
    },
+
    {
+
      a: "0x1234567890123456789012345678901234567890",
+
      b: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
      expected: false,
+
    },
  ])("isAddressEqual", ({ a, b, expected }) => {
-
    expect(
-
      utils.isAddressEqual(a, b))
-
      .toEqual(expected);
+
    expect(utils.isAddressEqual(a, b)).toEqual(expected);
  });

  test.each([
@@ -130,9 +139,7 @@ describe("String Assertions", () => {
    { domain: "0.0.0.0", expected: true }, // Pass as true since we are not in production
    { domain: "", expected: false },
  ])("isDomain $domain => $expected", ({ domain, expected }) => {
-
    expect(
-
      utils.isDomain(domain))
-
      .toEqual(expected);
+
    expect(utils.isDomain(domain)).toEqual(expected);
  });

  test.each([
@@ -141,36 +148,28 @@ describe("String Assertions", () => {
    { path: "README.markdown", expected: true },
    { path: "", expected: false },
  ])("isMarkdownPath $path => $expected", ({ path, expected }) => {
-
    expect(
-
      utils.isMarkdownPath(path))
-
      .toEqual(expected);
+
    expect(utils.isMarkdownPath(path)).toEqual(expected);
  });

  test.each([
    { id: "rad:git:hnrkemobagsicpf9sr95o3g551otspcd84c9o", expected: true },
    { id: "0x1234567890123456789012345678901234567890", expected: false },
  ])("isRadicleId $id => $expected", ({ id, expected }) => {
-
    expect(
-
      utils.isRadicleId(id))
-
      .toEqual(expected);
+
    expect(utils.isRadicleId(id)).toEqual(expected);
  });

  test.each([
    { id: "hnrkj4c35uoyceb3d1dsscx8qq55cikrd1aio", expected: true },
    { id: "0x1234567890123456789012345678901234567890", expected: false },
  ])("isPeerId $id => $expected", ({ id, expected }) => {
-
    expect(
-
      utils.isPeerId(id))
-
      .toEqual(expected);
+
    expect(utils.isPeerId(id)).toEqual(expected);
  });

  test.each([
    { oid: "a64ae9c6d572e0ad906faa9a4a7a8d43f113278c", expected: true },
-
    { oid: "a64ae9c", expected: false }
+
    { oid: "a64ae9c", expected: false },
  ])("isOid $oid => $expected", ({ oid, expected }) => {
-
    expect(
-
      utils.isOid(oid))
-
      .toEqual(expected);
+
    expect(utils.isOid(oid)).toEqual(expected);
  });

  test.each([
@@ -178,11 +177,7 @@ describe("String Assertions", () => {
    { address: "0x5E813e48a81977c6fdd565ed5097eb600c73c4f0", expected: false }, // If address is badly checksummed => false
    { address: "0x5e813e48a81977c6fdd565ed5097eb600c73c4f0", expected: true },
  ])("isAddress $address => $expected", ({ address, expected }) => {
-
    expect(
-
      utils.isAddress(
-
        address,
-
      ))
-
      .toBe(expected);
+
    expect(utils.isAddress(address)).toBe(expected);
  });

  test.each([
@@ -193,66 +188,88 @@ describe("String Assertions", () => {
    { url: "//app", expected: false },
    { url: "app", expected: false },
  ])("isUrl $url => $expected", ({ url, expected }) => {
-
    expect(
-
      utils.isUrl(
-
        url,
-
      ))
-
      .toBe(expected);
+
    expect(utils.isUrl(url)).toBe(expected);
  });

  test.each([
-
    { did: "did:3:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383", expected: true },
-
    { did: "did:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383", expected: false },
+
    {
+
      did: "did:3:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383",
+
      expected: true,
+
    },
+
    {
+
      did: "did:kjzl6cwe1jw1481xu9oyww9bhmueqr8f5uryk4xha9jzhj6vi063e0blpnil383",
+
      expected: false,
+
    },
  ])("isDid $did => $expected", ({ did, expected }) => {
-
    expect(
-
      utils.isDid(did))
-
      .toBe(expected);
+
    expect(utils.isDid(did)).toBe(expected);
  });
});

describe("Others", () => {
  test.each([
-
    { viewer: "https://gnosis-safe.io/app/#/safes", name: "", expected: "https://gnosis-safe.io/app/#/safes/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
-
    { viewer: null, name: "", expected: "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
    {
+
      viewer: "https://gnosis-safe.io/app/#/safes",
+
      name: "",
+
      expected:
+
        "https://gnosis-safe.io/app/#/safes/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
    },
+
    {
+
      viewer: null,
+
      name: "",
+
      expected:
+
        "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
    },
  ])("safeLink $viewer => $expected", ({ name, viewer, expected }) => {
    expect(
-
      utils.safeLink(
-
        "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
-
        {
-
          network: {
-
            name
-
          },
-
          safe: {
-
            viewer
-
          }
-
        } as Config
-
      ))
-
      .toEqual(expected);
+
      utils.safeLink("0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", {
+
        network: {
+
          name,
+
        },
+
        safe: {
+
          viewer,
+
        },
+
      } as Config),
+
    ).toEqual(expected);
  });

  test.each([
-
    { name: "rinkeby", expected: "https://rinkeby.etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
-
    { name: "", expected: "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0" },
+
    {
+
      name: "rinkeby",
+
      expected:
+
        "https://rinkeby.etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
    },
+
    {
+
      name: "",
+
      expected:
+
        "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
+
    },
  ])("explorerLink $name => $expected", ({ name, expected }) => {
    expect(
-
      utils.explorerLink(
-
        "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
-
        {
-
          network: {
-
            name,
-
          }
-
        } as Config
-
      ))
-
      .toEqual(expected);
+
      utils.explorerLink("0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", {
+
        network: {
+
          name,
+
        },
+
      } as Config),
+
    ).toEqual(expected);
  });

  test.each([
-
    { id: "rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy", expected: new Uint8Array([156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86]) },
-
    { id: "hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy", expected: new Uint8Array([156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59, 107, 76, 77, 86]) }
+
    {
+
      id: "rad:git:hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy",
+
      expected: new Uint8Array([
+
        156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59,
+
        107, 76, 77, 86,
+
      ]),
+
    },
+
    {
+
      id: "hnrkjajuucc6zp5eknt3s9xykqsrus44cjimy",
+
      expected: new Uint8Array([
+
        156, 38, 115, 99, 61, 118, 237, 10, 20, 115, 111, 188, 10, 117, 137, 59,
+
        107, 76, 77, 86,
+
      ]),
+
    },
  ])("decodeRadicleId", ({ id, expected }) => {
-
    expect(
-
      utils.decodeRadicleId(id))
-
      .toEqual(expected);
+
    expect(utils.decodeRadicleId(id)).toEqual(expected);
  });
});

@@ -262,45 +279,67 @@ describe("Parse Strings", () => {
    { label: "sebastinez", expected: "sebastinez" },
  ])("parseEnsLabel", ({ label, expected }) => {
    expect(
-
      utils.parseEnsLabel(
-
        label,
-
        {
-
          registrar: {
-
            address: "0x1234567890123456789012345678901234567890",
-
            domain: "radicle.eth"
-
          }
-
        } as Config
-
      )
+
      utils.parseEnsLabel(label, {
+
        registrar: {
+
          address: "0x1234567890123456789012345678901234567890",
+
          domain: "radicle.eth",
+
        },
+
      } as Config),
    ).toEqual(expected);
  });

  test.each([
    { input: "https://twitter.com/cloudhead", expected: "cloudhead" },
-
    { input: "sebastinez", expected: "sebastinez" }
+
    { input: "sebastinez", expected: "sebastinez" },
  ])("parseUsername", ({ input, expected }) => {
-
    expect(
-
      utils.parseUsername(input)
-
    ).toEqual(expected);
+
    expect(utils.parseUsername(input)).toEqual(expected);
  });
});

describe("Path Manipulation", () => {
  test.each([
-
    { imagePath: "/assets/images/tux.png", base: "/", origin: "https://app.radicle.xyz", expected: "assets/images/tux.png" },
-
    { imagePath: "assets/images/tux.png", base: "/", origin: "https://app.radicle.xyz", expected: "assets/images/tux.png" },
-
    { imagePath: "assets/images/tux.png", base: "/", origin: "http://localhost:3000", expected: "assets/images/tux.png" },
-
    { imagePath: "../tux.png", base: "/components/assets/README.md", origin: "http://localhost:3000", expected: "components/tux.png" },
-
    { imagePath: "../tux.png", base: "/components/assets/", origin: "http://localhost:3000", expected: "components/tux.png" },
-
    { imagePath: "../../tux.png", base: "/components/assets/images/README.md", origin: "http://localhost:3000", expected: "components/tux.png" },
-
  ])("canonicalize origin: $origin base: $base, path: $imagePath => $expected", ({ imagePath, base, expected, origin }) => {
-
    expect(
-
      utils.canonicalize(
-
        imagePath,
-
        base,
-
        origin
-
      )
-
    ).toEqual(expected);
-
  });
+
    {
+
      imagePath: "/assets/images/tux.png",
+
      base: "/",
+
      origin: "https://app.radicle.xyz",
+
      expected: "assets/images/tux.png",
+
    },
+
    {
+
      imagePath: "assets/images/tux.png",
+
      base: "/",
+
      origin: "https://app.radicle.xyz",
+
      expected: "assets/images/tux.png",
+
    },
+
    {
+
      imagePath: "assets/images/tux.png",
+
      base: "/",
+
      origin: "http://localhost:3000",
+
      expected: "assets/images/tux.png",
+
    },
+
    {
+
      imagePath: "../tux.png",
+
      base: "/components/assets/README.md",
+
      origin: "http://localhost:3000",
+
      expected: "components/tux.png",
+
    },
+
    {
+
      imagePath: "../tux.png",
+
      base: "/components/assets/",
+
      origin: "http://localhost:3000",
+
      expected: "components/tux.png",
+
    },
+
    {
+
      imagePath: "../../tux.png",
+
      base: "/components/assets/images/README.md",
+
      origin: "http://localhost:3000",
+
      expected: "components/tux.png",
+
    },
+
  ])(
+
    "canonicalize origin: $origin base: $base, path: $imagePath => $expected",
+
    ({ imagePath, base, expected, origin }) => {
+
      expect(utils.canonicalize(imagePath, base, origin)).toEqual(expected);
+
    },
+
  );
});

describe("Date Manipulation", () => {
@@ -309,24 +348,62 @@ describe("Date Manipulation", () => {
    { from: new Date("2022-01-01"), to: new Date("2022-01-02"), expected: 1 },
    { from: new Date("2022-01-01"), to: new Date("2022-01-01"), expected: 0 },
  ])("getDaysPassed expected: $expected ", ({ from, to, expected }) => {
-
    expect(
-
      utils.getDaysPassed(from, to)
-
    ).toEqual(expected);
+
    expect(utils.getDaysPassed(from, to)).toEqual(expected);
  });
  test.each([
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-01 12:00:00"), expected: "now" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-01 12:00:01"), expected: "1 second ago" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-01 12:01:01"), expected: "1 minute ago" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-01 13:01:01"), expected: "1 hour ago" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-02 13:01:01"), expected: "yesterday" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-01-04 13:01:01"), expected: "3 days ago" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-02-02 13:01:01"), expected: "last month" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2022-04-02 13:01:01"), expected: "3 months ago" },
-
    { from: new Date("2022-01-01 12:00:00"), to: new Date("2023-04-02 12:00:00"), expected: "Sat, 01 Jan 2022 12:00:00 GMT" },
-
    { from: new Date("2022-03-05 12:00:00"), to: new Date("2026-04-02 12:00:00"), expected: "Sat, 05 Mar 2022 12:00:00 GMT" },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-01 12:00:00"),
+
      expected: "now",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-01 12:00:01"),
+
      expected: "1 second ago",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-01 12:01:01"),
+
      expected: "1 minute ago",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-01 13:01:01"),
+
      expected: "1 hour ago",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-02 13:01:01"),
+
      expected: "yesterday",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-01-04 13:01:01"),
+
      expected: "3 days ago",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-02-02 13:01:01"),
+
      expected: "last month",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2022-04-02 13:01:01"),
+
      expected: "3 months ago",
+
    },
+
    {
+
      from: new Date("2022-01-01 12:00:00"),
+
      to: new Date("2023-04-02 12:00:00"),
+
      expected: "Sat, 01 Jan 2022 12:00:00 GMT",
+
    },
+
    {
+
      from: new Date("2022-03-05 12:00:00"),
+
      to: new Date("2026-04-02 12:00:00"),
+
      expected: "Sat, 05 Mar 2022 12:00:00 GMT",
+
    },
  ])("formatTimestamp expected: $expected", ({ from, to, expected }) => {
-
    expect(
-
      utils.formatTimestamp(from.getTime() / 1000, to.getTime())
-
    ).toEqual(expected);
+
    expect(utils.formatTimestamp(from.getTime() / 1000, to.getTime())).toEqual(
+
      expected,
+
    );
  });
});
modified src/utils.ts
@@ -2,17 +2,29 @@ import { ethers } from "ethers";
import type { RouteLocation } from "@app/index";
import md5 from "md5";
import { BigNumber } from "ethers";
-
import multibase from 'multibase';
-
import multihashes from 'multihashes';
-
import EthersSafe, { EthersAdapter, TransactionResult } from "@gnosis.pm/safe-core-sdk";
+
import multibase from "multibase";
+
import multihashes from "multihashes";
+
import EthersSafe, {
+
  EthersAdapter,
+
  TransactionResult,
+
} from "@gnosis.pm/safe-core-sdk";
import type { SafeSignature } from "@gnosis.pm/safe-core-sdk-types";
-
import type { Config } from '@app/config';
+
import type { Config } from "@app/config";
import config from "@app/config.json";
-
import { assert } from '@app/error';
-
import { EnsProfile, getAddress, getResolver } from "@app/base/registrations/registrar";
-
import { getAvatar, getSeed, getAnchorsAccount, getRegistration } from '@app/base/registrations/registrar';
-
import type { BasicProfile } from '@datamodels/identity-profile-basic';
-
import { ProfileType } from '@app/profile';
+
import { assert } from "@app/error";
+
import {
+
  EnsProfile,
+
  getAddress,
+
  getResolver,
+
} from "@app/base/registrations/registrar";
+
import {
+
  getAvatar,
+
  getSeed,
+
  getAnchorsAccount,
+
  getRegistration,
+
} from "@app/base/registrations/registrar";
+
import type { BasicProfile } from "@datamodels/identity-profile-basic";
+
import { ProfileType } from "@app/profile";
import { parseUnits } from "@ethersproject/units";
import { GetSafe } from "@app/base/orgs/Org";
import * as cache from "@app/cache";
@@ -53,12 +65,16 @@ export enum Status {
}

export type State =
-
    { status: Status.Signing }
+
  | { status: Status.Signing }
  | { status: Status.Pending }
  | { status: Status.Success }
  | { status: Status.Failed; error: string };

-
export async function isReverseRecordSet(address: string, domain: string, config: Config): Promise<boolean> {
+
export async function isReverseRecordSet(
+
  address: string,
+
  domain: string,
+
  config: Config,
+
): Promise<boolean> {
  const name = await lookupAddress(address, config);
  return name === domain;
}
@@ -67,11 +83,13 @@ export async function toClipboard(text: string): Promise<void> {
  return navigator.clipboard.writeText(text);
}

-
export function setOpenGraphMetaTag(data: { prop: string; content: string; attr?: string }[]): void {
+
export function setOpenGraphMetaTag(
+
  data: { prop: string; content: string; attr?: string }[],
+
): void {
  const elements = Array.from<HTMLElement>(document.querySelectorAll(`meta`));
  elements.forEach((element: any) => {
    const foundElement = data.find(data => {
-
      return data.prop === element.getAttribute(data.attr || 'property');
+
      return data.prop === element.getAttribute(data.attr || "property");
    });
    if (foundElement) element.content = foundElement.content;
  });
@@ -85,7 +103,11 @@ export function isAddressEqual(left: string, right: string): boolean {
  return left.toLowerCase() === right.toLowerCase();
}

-
export function formatSeedAddress(id: string, host: string, port: number): string {
+
export function formatSeedAddress(
+
  id: string,
+
  host: string,
+
  port: number,
+
): string {
  return `${id}@${host}:${port}`;
}

@@ -107,13 +129,13 @@ export function formatIssueId(id: string): string {
}

export function formatSeedId(id: string): string {
-
  return id.substring(0, 6)
-
    + '…'
-
    + id.substring(id.length - 6, id.length);
+
  return id.substring(0, 6) + "…" + id.substring(id.length - 6, id.length);
}

export function removePrefix(hash: string): string {
-
  if (! hash.startsWith("0x")) { return hash; }
+
  if (!hash.startsWith("0x")) {
+
    return hash;
+
  }

  return hash.substring(2);
}
@@ -121,16 +143,20 @@ export function removePrefix(hash: string): string {
export function formatRadicleUrn(id: string): string {
  assert(isRadicleId(id));

-
  return id.substring(0, 14)
-
    + '…'
-
    + id.substring(id.length - 6, id.length);
+
  return id.substring(0, 14) + "…" + id.substring(id.length - 6, id.length);
}

export function formatBalance(n: BigNumber, decimals?: number): string {
-
  return ethers.utils.commify(parseFloat(ethers.utils.formatUnits(n, decimals)).toFixed(2));
+
  return ethers.utils.commify(
+
    parseFloat(ethers.utils.formatUnits(n, decimals)).toFixed(2),
+
  );
}

-
export function formatCAIP10Address(address: string, protocol: string, impl: number): string {
+
export function formatCAIP10Address(
+
  address: string,
+
  protocol: string,
+
  impl: number,
+
): string {
  return `${address.toLowerCase()}@${protocol}:${impl.toString()}`;
}

@@ -138,9 +164,9 @@ export function formatCAIP10Address(address: string, protocol: string, impl: num
export function formatAddress(input: string): string {
  const addr = ethers.utils.getAddress(input).replace(/^0x/, "");

-
  return addr.substring(0, 4)
-
    + ' – '
-
    + addr.substring(addr.length - 4, addr.length);
+
  return (
+
    addr.substring(0, 4) + " – " + addr.substring(addr.length - 4, addr.length)
+
  );
}

export function formatIpfsFile(ipfs: string | undefined): string | undefined {
@@ -151,9 +177,9 @@ export function formatIpfsFile(ipfs: string | undefined): string | undefined {
// If the string is less than 10 characters the entire string is returned.
export function formatHash(hash: string): string {
  if (hash.length < 10) return hash;
-
  return hash.substring(0, 6)
-
    + '...'
-
    + hash.substring(hash.length - 4, hash.length);
+
  return (
+
    hash.substring(0, 6) + "..." + hash.substring(hash.length - 4, hash.length)
+
  );
}

export function formatCommit(oid: string): string {
@@ -181,10 +207,13 @@ export function parseEnsLabel(name: string, config: Config): string {
  return label;
}

-
export function clickOutside(node: HTMLElement, onEventFunction: () => void): any {
+
export function clickOutside(
+
  node: HTMLElement,
+
  onEventFunction: () => void,
+
): any {
  const handleClick = (event: any) => {
    const path = event.composedPath();
-
    if (! path.includes(node)) {
+
    if (!path.includes(node)) {
      onEventFunction();
    }
  };
@@ -193,7 +222,7 @@ export function clickOutside(node: HTMLElement, onEventFunction: () => void): an
  return {
    destroy() {
      document.removeEventListener("click", handleClick, true);
-
    }
+
    },
  };
}

@@ -201,13 +230,13 @@ export function clickOutside(node: HTMLElement, onEventFunction: () => void): an
// Returns `null` if unknown.
export function getImageMime(path: string): string | null {
  const mimes: Record<string, string> = {
-
    'apng': 'image/apng',
-
    'png': 'image/png',
-
    'svg': 'image/svg+xml',
-
    'gif': 'image/gif',
-
    'jpeg': 'image/jpeg',
-
    'jpg': 'image/jpeg',
-
    'webp': 'image/webp',
+
    apng: "image/apng",
+
    png: "image/png",
+
    svg: "image/svg+xml",
+
    gif: "image/gif",
+
    jpeg: "image/jpeg",
+
    jpg: "image/jpeg",
+
    webp: "image/webp",
  };
  const ext = path.split(".").pop();

@@ -221,7 +250,11 @@ export function getImageMime(path: string): string | null {

// Takes a path, eg. "../images/image.png", and a base from where to start resolving, e.g. "static/images/index.html".
// Returns the resolved path.
-
export function canonicalize(path: string, base: string, origin = document.location.origin): string {
+
export function canonicalize(
+
  path: string,
+
  base: string,
+
  origin = document.location.origin,
+
): string {
  path = path.replace(/^\//, ""); // Remove leading slash
  const finalPath = base
    .split("/")
@@ -248,19 +281,25 @@ export function unixTime(): number {
  return Math.floor(Date.now() / 1000);
}

-
export const formatTimestamp = (timestamp: number, current = new Date().getTime()): string => {
+
export const formatTimestamp = (
+
  timestamp: number,
+
  current = new Date().getTime(),
+
): string => {
  const units: Record<string, number> = {
    year: 24 * 60 * 60 * 1000 * 365,
-
    month: 24 * 60 * 60 * 1000 * 365 / 12,
+
    month: (24 * 60 * 60 * 1000 * 365) / 12,
    day: 24 * 60 * 60 * 1000,
    hour: 60 * 60 * 1000,
    minute: 60 * 1000,
-
    second: 1000
+
    second: 1000,
  };

  // Multiplying timestamp with 1000 to convert from seconds to milliseconds
  timestamp = timestamp * 1000;
-
  const rtf = new Intl.RelativeTimeFormat('en', { numeric: "auto", style: 'long' });
+
  const rtf = new Intl.RelativeTimeFormat("en", {
+
    numeric: "auto",
+
    style: "long",
+
  });
  const elapsed = current - timestamp;

  if (elapsed > units["year"]) {
@@ -270,9 +309,12 @@ export const formatTimestamp = (timestamp: number, current = new Date().getTime(
  }

  for (const u in units) {
-
    if (elapsed > units[u] || u === 'second') {
+
    if (elapsed > units[u] || u === "second") {
      // We convert the division result to a negative number to get "XX [unit] ago"
-
      return rtf.format(Math.round(elapsed / units[u]) * -1, u as Intl.RelativeTimeFormatUnit);
+
      return rtf.format(
+
        Math.round(elapsed / units[u]) * -1,
+
        u as Intl.RelativeTimeFormatUnit,
+
      );
    }
  }

@@ -316,7 +358,10 @@ export function isAddress(input: string): boolean {
}

// Get search parameters from location.
-
export function getSearchParam(key: string, location: RouteLocation): string | null {
+
export function getSearchParam(
+
  key: string,
+
  location: RouteLocation,
+
): string | null {
  const params = new URLSearchParams(location.search);
  return params.get(key);
}
@@ -342,17 +387,17 @@ export async function querySubgraphWithRetry(
  url: string,
  query: string,
  variables: Record<string, any>,
-
  retries = 3
+
  retries = 3,
): Promise<null | any> {
  const response = await fetch(url, {
-
    method: 'POST',
+
    method: "POST",
    headers: {
-
      'Content-Type': 'application/json',
+
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query,
      variables,
-
    })
+
    }),
  });
  const json = await response.json();

@@ -368,8 +413,9 @@ export async function querySubgraphWithRetry(

export const querySubgraph = cache.cached(
  querySubgraphWithRetry,
-
  (url: string, query: string, variables: Record<string, any>) => JSON.stringify({ url, query, variables }),
-
  { max: 500, ttl: 5 * 60 * 1000 } // Cache results for 5 minutes.
+
  (url: string, query: string, variables: Record<string, any>) =>
+
    JSON.stringify({ url, query, variables }),
+
  { max: 500, ttl: 5 * 60 * 1000 }, // Cache results for 5 minutes.
);

// Format a name.
@@ -397,9 +443,7 @@ export function parseRadicleId(urn: string): string {

// Get amount of days passed between two dates without including the end date
export function getDaysPassed(from: Date, to: Date): number {
-
  return Math.floor(
-
    (to.getTime() - from.getTime()) / (24 * 60 * 60 * 1000)
-
  );
+
  return Math.floor((to.getTime() - from.getTime()) / (24 * 60 * 60 * 1000));
}

// Decode a Radicle Id (URN).
@@ -414,11 +458,14 @@ export function decodeRadicleId(urn: string): Uint8Array {
// Create a project hash from a hash and format.
export function formatProjectHash(multihash: Uint8Array): string {
  const decoded = multihashes.decode(multihash);
-
  return ethers.utils.hexlify(decoded.digest).replace(/^0x/, '');
+
  return ethers.utils.hexlify(decoded.digest).replace(/^0x/, "");
}

// Identify an address by checking whether it's a contract or an externally-owned address.
-
export async function identifyAddress(address: string, config: Config): Promise<AddressType> {
+
export async function identifyAddress(
+
  address: string,
+
  config: Config,
+
): Promise<AddressType> {
  const safe = await isSafe(address, config);
  if (safe) {
    return AddressType.Safe;
@@ -437,7 +484,10 @@ export async function identifyAddress(address: string, config: Config): Promise<
}

// Resolve a label under the radicle domain.
-
export async function resolveLabel(label: string | undefined, config: Config): Promise<string | null> {
+
export async function resolveLabel(
+
  label: string | undefined,
+
  config: Config,
+
): Promise<string | null> {
  if (label) return config.provider.resolveName(label);
  return null;
}
@@ -452,18 +502,22 @@ export const resolveIdxProfile = cache.cached(
    }
  },
  (caip10: string) => caip10,
-
  { max: 500, ttl: 30 * 60 * 1000 } // Cache results for 30 minutes.
+
  { max: 500, ttl: 30 * 60 * 1000 }, // Cache results for 30 minutes.
);

// Resolves an ENS profile or return null
-
export async function resolveEnsProfile(addressOrName: string, profileType: ProfileType, config: Config): Promise<EnsProfile | null> {
+
export async function resolveEnsProfile(
+
  addressOrName: string,
+
  profileType: ProfileType,
+
  config: Config,
+
): Promise<EnsProfile | null> {
  const name = ethers.utils.isAddress(addressOrName)
    ? await lookupAddress(addressOrName, config)
    : addressOrName;

  if (name) {
    const resolver = await getResolver(name, config);
-
    if (! resolver) {
+
    if (!resolver) {
      return null;
    }

@@ -473,9 +527,7 @@ export async function resolveEnsProfile(addressOrName: string, profileType: Prof
        return registration.profile;
      }
    } else {
-
      const promises: [Promise<any>] = [
-
        getAvatar(name, config, resolver),
-
      ];
+
      const promises: [Promise<any>] = [getAvatar(name, config, resolver)];

      if (addressOrName === name) {
        promises.push(getAddress(resolver));
@@ -494,7 +546,9 @@ export async function resolveEnsProfile(addressOrName: string, profileType: Prof
      const [avatar, address, seed, anchorsAccount] =
        // Just checking for r.value equal null and casting to undefined,
        // since resolver functions return null.
-
        project.map(r => r.status === "fulfilled" && r.value ? r.value : null);
+
        project.map(r =>
+
          r.status === "fulfilled" && r.value ? r.value : null,
+
        );

      return {
        name,
@@ -509,43 +563,66 @@ export async function resolveEnsProfile(addressOrName: string, profileType: Prof
}

// Check whether a Gnosis Safe exists at an address.
-
export async function isSafe(address: string, config: Config): Promise<boolean> {
+
export async function isSafe(
+
  address: string,
+
  config: Config,
+
): Promise<boolean> {
  // For the subgraph we need to pass a lowercase address
-
  const query = await querySubgraph(config.orgs.subgraph, GetSafe, { addr: address.toLowerCase() });
+
  const query = await querySubgraph(config.orgs.subgraph, GetSafe, {
+
    addr: address.toLowerCase(),
+
  });

  return query.safe !== null ? true : false;
}

// Get a Gnosis Safe at an address.
-
export async function getSafe(address: string, config: Config): Promise<Safe | null> {
+
export async function getSafe(
+
  address: string,
+
  config: Config,
+
): Promise<Safe | null> {
  // For the subgraph we need to pass a lowercase address
-
  const query = await querySubgraph(config.orgs.subgraph, GetSafe, { addr: address.toLowerCase() });
+
  const query = await querySubgraph(config.orgs.subgraph, GetSafe, {
+
    addr: address.toLowerCase(),
+
  });

-
  if (! query?.safe) {
+
  if (!query?.safe) {
    return null;
  }

  return {
    address: query.safe.id,
    owners: query.safe.owners,
-
    threshold: query.safe.threshold
+
    threshold: query.safe.threshold,
  };
}

// Get token balances for an address.
-
export async function getTokens(address: string, config: Config): Promise<Array<Token>> {
-
  const userBalances = await getRpcMethod("alchemy_getTokenBalances", [address, "DEFAULT_TOKENS"], config);
-
  const balances = userBalances.tokenBalances.filter((token: any) => {
-
    // alchemy_getTokenBalances sometimes returns 0x and this does not work well with ethers.BigNumber
-
    if (token.tokenBalance !== "0x") {
-
      if (! BigNumber.from(token.tokenBalance).isZero()) {
-
        return token;
+
export async function getTokens(
+
  address: string,
+
  config: Config,
+
): Promise<Array<Token>> {
+
  const userBalances = await getRpcMethod(
+
    "alchemy_getTokenBalances",
+
    [address, "DEFAULT_TOKENS"],
+
    config,
+
  );
+
  const balances = userBalances.tokenBalances
+
    .filter((token: any) => {
+
      // alchemy_getTokenBalances sometimes returns 0x and this does not work well with ethers.BigNumber
+
      if (token.tokenBalance !== "0x") {
+
        if (!BigNumber.from(token.tokenBalance).isZero()) {
+
          return token;
+
        }
      }
-
    }
-
  }).map(async (token: any) => {
-
    const tokenMetaData = await getRpcMethod("alchemy_getTokenMetadata", [token.contractAddress], config);
-
    return { ...tokenMetaData, balance: BigNumber.from(token.tokenBalance) };
-
  });
+
    })
+
    .map(async (token: any) => {
+
      const tokenMetaData = await getRpcMethod(
+
        "alchemy_getTokenMetadata",
+
        [token.contractAddress],
+
        config,
+
      );
+
      return { ...tokenMetaData, balance: BigNumber.from(token.tokenBalance) };
+
    });

  return Promise.all(balances);
}
@@ -555,7 +632,7 @@ export const getRpcMethod = cache.cached(
    return await config.provider.send(method, props);
  },
  (method, props) => JSON.stringify([method, props]),
-
  { ttl: 2 * 60 * 1000, max: 1000 }
+
  { ttl: 2 * 60 * 1000, max: 1000 },
);

// Check whether the given path has a markdown file extension.
@@ -566,8 +643,10 @@ export function isMarkdownPath(path: string): boolean {
// Check whether the given input string is a domain, eg. `alt-clients.radicle.xyz.
// Also accepts in dev env 0.0.0.0 as domain
export function isDomain(input: string): boolean {
-
  return (/^[a-z][a-z0-9.-]+$/.test(input) && /\.[a-z]+$/.test(input))
-
    || (! import.meta.env.PROD && /^0.0.0.0$/.test(input));
+
  return (
+
    (/^[a-z][a-z0-9.-]+$/.test(input) && /\.[a-z]+$/.test(input)) ||
+
    (!import.meta.env.PROD && /^0.0.0.0$/.test(input))
+
  );
}

// Check whether the given address is a local host address.
@@ -587,7 +666,7 @@ export function gravatarURL(email: string): string {
export async function proposeSafeTransaction(
  safeTx: SafeTransaction,
  safeAddress: string,
-
  config: Config
+
  config: Config,
): Promise<void> {
  assert(config.signer);
  assert(config.safe.client);
@@ -597,11 +676,12 @@ export async function proposeSafeTransaction(
    signer: config.signer,
  });
  const safeSdk = await EthersSafe.create({
-
    ethAdapter, safeAddress,
+
    ethAdapter,
+
    safeAddress,
  });
  const estimation = await config.safe.client.estimateSafeTransaction(
    safeAddress,
-
    safeTx
+
    safeTx,
  );
  const transaction = await safeSdk.createTransaction({
    ...safeTx,
@@ -614,7 +694,7 @@ export async function proposeSafeTransaction(
    safeAddress,
    transaction.data,
    safeTxHash,
-
    signature
+
    signature,
  );
}

@@ -622,15 +702,17 @@ export async function proposeSafeTransaction(
export async function signSafeTransaction(
  safeAddress: string,
  safeTxHash: string,
-
  config: Config
+
  config: Config,
): Promise<SafeSignature> {
  assert(config.signer);

  const ethAdapter = new EthersAdapter({
-
    ethers, signer: config.signer
+
    ethers,
+
    signer: config.signer,
  });
  const safeSdk = await EthersSafe.create({
-
    ethAdapter, safeAddress
+
    ethAdapter,
+
    safeAddress,
  });
  return await safeSdk.signTransactionHash(safeTxHash);
}
@@ -639,16 +721,18 @@ export async function signSafeTransaction(
export async function executeSignedSafeTransaction(
  safeAddress: string,
  safeTxHash: string,
-
  config: Config
+
  config: Config,
): Promise<TransactionResult> {
  assert(config.signer);
  assert(config.safe.client);

  const ethAdapter = new EthersAdapter({
-
    ethers, signer: config.signer
+
    ethers,
+
    signer: config.signer,
  });
  const safeSdk = await EthersSafe.create({
-
    ethAdapter, safeAddress
+
    ethAdapter,
+
    safeAddress,
  });

  const signedTx = await config.safe.client.getTransaction(safeTxHash);
@@ -659,11 +743,14 @@ export async function executeSignedSafeTransaction(
  const safeTx = await safeSdk.createTransaction({
    ...signedTx,
    gasPrice: Number(signedTx.gasPrice),
-
    data: signedTx.data
+
    data: signedTx.data,
  });

  signedTx.confirmations.forEach(confirmation => {
-
    const signature = new EthSignSignature(confirmation.owner, confirmation.signature);
+
    const signature = new EthSignSignature(
+
      confirmation.owner,
+
      confirmation.signature,
+
    );
    safeTx.addSignature(signature);
  });

@@ -682,7 +769,7 @@ export class EthSignSignature {
    return this.data;
  }
  dynamicPart(): string {
-
    return '';
+
    return "";
  }
}

@@ -690,16 +777,16 @@ export const getCode = cache.cached(
  async (address: string, config: Config) => {
    return await config.provider.getCode(address);
  },
-
  (address) => address,
-
  { max: 1000 }
+
  address => address,
+
  { max: 1000 },
);

export const lookupAddress = cache.cached(
  async (address: string, config: Config) => {
    return await config.provider.lookupAddress(address);
  },
-
  (address) => address,
-
  { max: 1000 }
+
  address => address,
+
  { max: 1000 },
);

export const unreachable = (value: never): never => {
modified tsconfig.json
@@ -1,20 +1,11 @@
{
  "extends": "@tsconfig/svelte/tsconfig.json",
-
  "include": [
-
    "src"
-
  ],
-
  "exclude": [
-
    "node_modules/*"
-
  ],
+
  "include": ["src"],
+
  "exclude": ["node_modules/*"],
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
-
    "types": [
-
      "svelte",
-
      "vite/client",
-
      "cypress",
-
      "@testing-library/cypress"
-
    ],
+
    "types": ["svelte", "vite/client", "cypress", "@testing-library/cypress"],
    "sourceMap": true,
    "baseUrl": "./",
    "moduleResolution": "node",
modified vite.config.ts
@@ -1,22 +1,23 @@
///<reference types="vitest" />
-
import path from 'path';
-
import { UserConfig } from 'vite';
-
import { svelte } from '@sveltejs/vite-plugin-svelte';
+
import path from "path";
+
import { UserConfig } from "vite";
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { ViteDevServer } from "vite";
-
import IstanbulPlugin from 'vite-plugin-istanbul';
+
import IstanbulPlugin from "vite-plugin-istanbul";
import history from "connect-history-api-fallback";
import type { Request, Response } from "express-serve-static-core";

const config: UserConfig = {
  optimizeDeps: {
-
    exclude: ['svelte-routing', '@pedrouid/environment', '@pedrouid/iso-crypto']
+
    exclude: [
+
      "svelte-routing",
+
      "@pedrouid/environment",
+
      "@pedrouid/iso-crypto",
+
    ],
  },
  test: {
    deps: {
-
      inline: [
-
        "@ethersproject/signing-key",
-
        "@ethersproject/basex",
-
      ]
+
      inline: ["@ethersproject/signing-key", "@ethersproject/basex"],
    },
    environment: "happy-dom",
    include: ["**/*.test.ts"],
@@ -25,49 +26,51 @@ const config: UserConfig = {
      reporter: ["html"],
      all: true,
      excludeNodeModules: true,
-
      extension: [".svelte", ".ts", ".js"]
+
      extension: [".svelte", ".ts", ".js"],
    },
  },
  plugins: [
    svelte({
      hot: !process.env.VITEST,
      compilerOptions: {
-
        dev: process.env.NODE_ENV !== "production"
-
      }
+
        dev: process.env.NODE_ENV !== "production",
+
      },
    }),
    rewriteAll(),
    IstanbulPlugin({
      include: "src/**/*",
      exclude: ["node_modules"],
      extension: [".ts", ".svelte"],
-
      cypress: true
-
    })
+
      cypress: true,
+
    }),
  ],
  server: {
-
    port: 3000
+
    port: 3000,
  },
  resolve: {
    alias: {
      // This is needed for vite not to choke.
-
      "caip": path.resolve("./node_modules/caip/dist/umd/index.min.js"),
-
      '@public': path.resolve('./public'),
-
      '@app': path.resolve('./src'),
+
      caip: path.resolve("./node_modules/caip/dist/umd/index.min.js"),
+
      "@public": path.resolve("./public"),
+
      "@app": path.resolve("./src"),
      // Polyfill for Node.js 'stream' library.
-
      'stream': path.resolve('./src/polyfills/stream.ts'),
-
      'typedarray-to-buffer': path.resolve('./src/polyfills/typedarray-to-buffer.js'),
+
      stream: path.resolve("./src/polyfills/stream.ts"),
+
      "typedarray-to-buffer": path.resolve(
+
        "./src/polyfills/typedarray-to-buffer.js",
+
      ),
      // "Buffer" is not defined in the published package..
-
      '@walletconnect/encoding': path.resolve('./src/polyfills/enc-utils.js'),
-
      'enc-utils': path.resolve('./src/polyfills/enc-utils.js'),
+
      "@walletconnect/encoding": path.resolve("./src/polyfills/enc-utils.js"),
+
      "enc-utils": path.resolve("./src/polyfills/enc-utils.js"),
    },
  },
  define: {
    // eslint-disable-next-line @typescript-eslint/naming-convention
-
    'process.env': { READABLE_STREAM: 'disable' }
+
    "process.env": { READABLE_STREAM: "disable" },
  },
  build: {
-
    outDir: 'build',
-
    sourcemap: true
-
  }
+
    outDir: "build",
+
    sourcemap: true,
+
  },
};

// For Vitest to work we need to unset READABLE_STREAM.
@@ -79,18 +82,18 @@ export default config;

function rewriteAll() {
  return {
-
    name: 'rewrite-all',
+
    name: "rewrite-all",
    configureServer(server: ViteDevServer) {
      return () => {
        const handler = history({
          disableDotRule: true,
-
          rewrites: [{ from: /\/$/, to: () => "/index.html" }]
+
          rewrites: [{ from: /\/$/, to: () => "/index.html" }],
        });

        server.middlewares.use((req, res, next) => {
          handler(req as Request, res as Response, next);
        });
      };
-
    }
+
    },
  };
}