📟 Hypernet

Building a Hyperapp

What you make, what you make it with, and the parts some Hyperapps add.

A Hyperapp is a website, so building one is building a site. What makes it a Hyperapp is conformance to the specification: a Directory Page at the root, capability pages holding Actions, a Response Page for every submission, and pages that survive conversion to Markdown.

Any stack that serves semantic HTML works. Hyperkit is the shortest path: author each page once as JSX, and one source serves humans HTML and agents Markdown. The rest of the Hyper Stack is there when you want it: identity, files, and the shared stores.

A complete example

The GitHub Hyperapp fronts GitHub. This page builds its surface. The next two add the rest:

index.tsx

pages/ and api/ are this chapter. connection/ and trigger/ are the next two.

The root is a Directory Page. Every capability is a link away from it:

src/pages/directory/page.tsx
const DirectoryPage = () => (
  <Layout>
    <Directory
      title="GitHub"
      description="Browse repositories, pull requests, and issues. Read changes and reply. Create issues, PRs, reviews, and files. Open the repo list first, or jump in with ?repo=owner/name."
      pages={[
        { href: "/repos", label: "Repositories (all repos in scope)" },
        { href: "/repo?repo=owner/name", label: "One repository (overview, links to code, PRs, issues)" },
        { href: "/pr?repo=owner/name&number=1", label: "Read one PR (description, diff, comments)" },
        { href: "/issues?repo=owner/name", label: "Issues (open issues of a repo)" },
        { href: "/create-issue?repo=owner/name", label: "Create an issue" },
        // …the real app lists a dozen pages
      ]}
    />
  </Layout>
)

That source renders to one page in two representations:

GET /
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>GitHub</title>
    <meta name="description" content="Browse repositories, pull requests, and issues. Read changes and reply. Create issues, PRs, reviews, and files. Open the repo list first, or jump in with ?repo=owner/name." />
    <meta name="hyperapp:type" content="app" />
    <meta name="hyperapp:app" content="GitHub" />
    <meta name="hyperapp:origin" content="https://github.hyperapp.sh" />
  </head>
  <body>
    <h1>GitHub</h1>
    <p>Browse repositories, pull requests, and issues. Read changes and reply. Create issues, PRs, reviews, and files. Open the repo list first, or jump in with ?repo=owner/name.</p>
    <h2>Pages</h2>
    <ul>
      <li><a href="/repos">Repositories (all repos in scope)</a></li>
      <li><a href="/repo?repo=owner/name">One repository (overview, links to code, PRs, issues)</a></li>
      <li><a href="/pr?repo=owner/name&amp;number=1">Read one PR (description, diff, comments)</a></li>
      <li><a href="/issues?repo=owner/name">Issues (open issues of a repo)</a></li>
      <li><a href="/create-issue?repo=owner/name">Create an issue</a></li>
    </ul>
  </body>
</html>
GET / (Accept: text/markdown)
---
title: GitHub
description: Browse repositories, pull requests, and issues. Read changes and reply. Create issues, PRs, reviews, and files. Open the repo list first, or jump in with ?repo=owner/name.
hyperapp:type: app
hyperapp:app: GitHub
hyperapp:origin: https://github.hyperapp.sh
---

# GitHub

Browse repositories, pull requests, and issues. Read changes and reply. Create issues, PRs, reviews, and files. Open the repo list first, or jump in with ?repo=owner/name.

## Pages

- [Repositories (all repos in scope)](/repos)
- [One repository (overview, links to code, PRs, issues)](/repo?repo=owner/name)
- [Read one PR (description, diff, comments)](/pr?repo=owner/name&number=1)
- [Issues (open issues of a repo)](/issues?repo=owner/name)
- [Create an issue](/create-issue?repo=owner/name)

A capability page renders live state, and its links lead deeper. Reading the page is knowing the repository:

src/pages/repo/page.tsx
const RepoView = ({ repo }: { repo: Repo }) => (
  <Layout>
    <Page title={repo.full_name} description={repo.description}>
      <h2>Overview</h2>
      <ul>
        <li>Visibility: {repo.private ? "private" : "public"}</li>
        <li>Default branch: {repo.default_branch}</li>
        <li>Open issues: {repo.open_issues_count}</li>
      </ul>
      <h2>Links</h2>
      <ul>
        <li><a href={`/prs?repo=${repo.full_name}`}>Open pull requests</a></li>
        <li><a href={`/issues?repo=${repo.full_name}`}>Open issues</a></li>
      </ul>
    </Page>
  </Layout>
)

Rendered:

GET /repo?repo=agx-computer/hyperapps
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>agx-computer/hyperapps</title>
    <meta name="description" content="Hyperapps for the AGX platform." />
    <meta name="hyperapp:type" content="app" />
    <meta name="hyperapp:app" content="GitHub" />
    <meta name="hyperapp:origin" content="https://github.hyperapp.sh" />
  </head>
  <body>
    <h1>agx-computer/hyperapps</h1>
    <p>Hyperapps for the AGX platform.</p>
    <h2>Overview</h2>
    <ul>
      <li>Visibility: public</li>
      <li>Default branch: main</li>
      <li>Open issues: 3</li>
    </ul>
    <h2>Links</h2>
    <ul>
      <li><a href="/prs?repo=agx-computer/hyperapps">Open pull requests</a></li>
      <li><a href="/issues?repo=agx-computer/hyperapps">Open issues</a></li>
    </ul>
  </body>
</html>
GET /repo?repo=agx-computer/hyperapps (Accept: text/markdown)
---
title: agx-computer/hyperapps
description: Hyperapps for the AGX platform.
hyperapp:type: app
hyperapp:app: GitHub
hyperapp:origin: https://github.hyperapp.sh
---

# agx-computer/hyperapps

Hyperapps for the AGX platform.

## Overview

- Visibility: public
- Default branch: main
- Open issues: 3

## Links

- [Open pull requests](/prs?repo=agx-computer/hyperapps)
- [Open issues](/issues?repo=agx-computer/hyperapps)

Acting is a form. An Action declares its fields, a human sees a fieldset, an agent sees an Action block, and submitting either returns a Response Page:

src/components/forms.tsx
<Action name="Reply to this issue" target="/api/issue-comment" submit="Post comment">
  <Field name="repo" type="hidden" value={repo} />
  <Field name="number" type="hidden" value={String(number)} />
  <Field name="body" type="textarea" label="comment (markdown)" rows={6} required />
</Action>

Rendered:

GET /issue?repo=agx-computer/hyperapps&number=12
<form method="post" name="Reply to this issue" action="/api/issue-comment">
  <fieldset>
    <legend>Reply to this issue</legend>
    <input type="hidden" name="repo" value="agx-computer/hyperapps" />
    <input type="hidden" name="number" value="12" />
    <p>
      <label for="body">comment (markdown)</label><br />
      <textarea id="body" name="body" rows="6" required></textarea>
    </p>
    <button type="submit">Post comment</button>
  </fieldset>
</form>
GET /issue?repo=agx-computer/hyperapps&number=12 (Accept: text/markdown)
## Action: Reply to this issue

- name: `Reply to this issue`
- method: POST
- target: `/api/issue-comment`

### Inputs

| name | type | required | value |
| --- | --- | --- | --- |
| repo | hidden | no | agx-computer/hyperapps |
| number | hidden | no | 12 |
| body | textarea | yes |  |

Browsing public repositories needs nothing more than these pages. But posting that comment acts on GitHub as someone, and a private repository will not even answer a read. That someone is the next page: adding a connection. And when the repository should call back (a push lands, an issue opens), that is adding a trigger.

On this page