Skip to content

Commit e3c6700

Browse files
committed
feat: add copy raw and view raw for docs pages
1 parent 08f5cf0 commit e3c6700

49 files changed

Lines changed: 3853 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/CopyRawButton.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { useState } from 'react';
2+
import { useRouter } from 'next/router';
3+
4+
function getRawUrl(currentPath) {
5+
if (currentPath === '/') return '/raw/index.md';
6+
return `/raw${currentPath}.md`;
7+
}
8+
9+
export function CopyRawButton() {
10+
const [copied, setCopied] = useState(false);
11+
const [copyError, setCopyError] = useState(false);
12+
const router = useRouter();
13+
const currentPath = router.asPath.split('#')[0].split('?')[0] || '/';
14+
const rawUrl = getRawUrl(currentPath);
15+
16+
const handleCopy = async () => {
17+
try {
18+
const response = await fetch(rawUrl);
19+
if (!response.ok) throw new Error('Failed to load raw markdown');
20+
21+
const raw = await response.text();
22+
23+
try {
24+
await navigator.clipboard.writeText(raw);
25+
} catch {
26+
const textarea = document.createElement('textarea');
27+
textarea.value = raw;
28+
document.body.appendChild(textarea);
29+
textarea.select();
30+
document.execCommand('copy');
31+
document.body.removeChild(textarea);
32+
}
33+
34+
setCopyError(false);
35+
setCopied(true);
36+
setTimeout(() => setCopied(false), 2000);
37+
} catch {
38+
setCopyError(true);
39+
setCopied(false);
40+
setTimeout(() => setCopyError(false), 2000);
41+
}
42+
};
43+
44+
return (
45+
<div className="page-tools" aria-label="Page tools">
46+
<button
47+
className="page-tools-button"
48+
onClick={handleCopy}
49+
type="button"
50+
>
51+
{copied ? 'Copied' : copyError ? 'Copy failed' : 'Copy raw'}
52+
</button>
53+
<a
54+
className="page-tools-link"
55+
href={rawUrl}
56+
target="_blank"
57+
rel="noopener noreferrer"
58+
>
59+
View raw
60+
</a>
61+
</div>
62+
);
63+
}

components/Layout.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { LastUpdated } from './LastUpdated';
99
import { Breadcrumbs } from './Breadcrumbs';
1010
import { ThemeToggle } from './ThemeToggle';
1111
import { SearchDialog } from './SearchDialog';
12+
import { CopyRawButton } from './CopyRawButton';
1213

1314
const SITE_URL = 'https://docs.hypercerts.org';
1415
const SITE_NAME = 'Hypercerts Documentation';
@@ -192,6 +193,7 @@ export default function Layout({ children, frontmatter }) {
192193

193194
<main className="layout-content" id="main-content">
194195
<Breadcrumbs />
196+
{frontmatter && <CopyRawButton />}
195197
<LastUpdated />
196198
<article>{children}</article>
197199

lib/generate-raw-pages.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const {
2+
readdirSync,
3+
statSync,
4+
readFileSync,
5+
writeFileSync,
6+
rmSync,
7+
mkdirSync,
8+
} = require('fs');
9+
const { dirname, join, relative } = require('path');
10+
11+
const PAGES_DIR = join(__dirname, '..', 'pages');
12+
const OUTPUT_DIR = join(__dirname, '..', 'public', 'raw');
13+
14+
function walkDir(dir) {
15+
const results = [];
16+
for (const entry of readdirSync(dir)) {
17+
const full = join(dir, entry);
18+
if (statSync(full).isDirectory()) {
19+
results.push(...walkDir(full));
20+
} else if (full.endsWith('.md')) {
21+
results.push(full);
22+
}
23+
}
24+
return results;
25+
}
26+
27+
function getRawOutputPath(filePath) {
28+
const rel = relative(PAGES_DIR, filePath).replace(/\.md$/, '');
29+
const route = rel === 'index' ? '/index' : `/${rel.replace(/\/index$/, '')}`;
30+
const outputRel = route === '/index' ? 'index.md' : `${route.slice(1)}.md`;
31+
return join(OUTPUT_DIR, outputRel);
32+
}
33+
34+
rmSync(OUTPUT_DIR, { recursive: true, force: true });
35+
mkdirSync(OUTPUT_DIR, { recursive: true });
36+
37+
const files = walkDir(PAGES_DIR);
38+
39+
for (const file of files) {
40+
const outputPath = getRawOutputPath(file);
41+
mkdirSync(dirname(outputPath), { recursive: true });
42+
writeFileSync(outputPath, readFileSync(file, 'utf-8'));
43+
}
44+
45+
console.log(`Generated raw markdown files for ${files.length} pages`);

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"private": true,
55
"description": "Hypercerts Protocol Documentation",
66
"scripts": {
7-
"dev": "node lib/generate-search-index.js && node lib/generate-last-updated.js && node lib/generate-sitemap.js && next dev --webpack",
8-
"build": "node lib/generate-search-index.js && node lib/generate-last-updated.js && node lib/generate-sitemap.js && next build --webpack",
7+
"dev": "node lib/generate-search-index.js && node lib/generate-last-updated.js && node lib/generate-sitemap.js && node lib/generate-raw-pages.js && next dev --webpack",
8+
"build": "node lib/generate-search-index.js && node lib/generate-last-updated.js && node lib/generate-sitemap.js && node lib/generate-raw-pages.js && next build --webpack",
99
"start": "next start"
1010
},
1111
"dependencies": {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
title: Account & Identity Setup
3+
description: Understand your identity, configure custom domains, and manage credentials.
4+
---
5+
6+
# Account & Identity Setup
7+
8+
If you followed the [Quickstart](/getting-started/quickstart), you already have an account. This page explains what that account gives you and how to configure it — custom domain handles for organizations, app passwords for scripts, shared repositories for teams, and account recovery.
9+
10+
---
11+
12+
## Create an account
13+
14+
Sign up at [certified.app](https://certified.app/). You'll get:
15+
16+
- **Low-friction sign-in** — Sign in with just your email and a code. No passwords or protocol knowledge required.
17+
- **A DID** — Your permanent, portable identifier (e.g., `did:plc:z72i7hdynmk6r22z27h6tvur`). It never changes, even if you switch servers or handles.
18+
- **A Repository** — Your own collection on the certified PDS, where your hypercerts, evaluations, and other records are stored. You own this data and you can migrate it between servers.
19+
- **An (embedded) wallet** — Add your existing EVM wallet or get a new one.
20+
- **Ecosystem access** — Your identity works across every Hypercerts application.
21+
22+
### Why Certified?
23+
24+
The Hypercerts Protocol is built on AT Protocol — the same decentralized data layer that powers Bluesky. But most Hypercerts users are not Bluesky users. They are researchers, land stewards, open-source maintainers, funders, and evaluators. Asking them to "sign in with Bluesky" to use a funding platform would be confusing — it ties a funding tool to a social media brand. This is no knock on Bluesky — it's a great platform, just not the right entry point for a funding tool.
25+
26+
Certified is a neutral identity provider that isn't tied to any single application. You create an account and immediately have an identity that works across the entire ecosystem — no knowledge of Bluesky, ATProto, or decentralized protocols required.
27+
28+
{% callout type="note" %}
29+
Hypercerts is fully interoperable with the AT Protocol ecosystem. If you already have a Bluesky account or any other ATProto identity, log in with your existing handle (e.g., `alice.bsky.social`) and use all Hypercerts applications — no additional account needed.
30+
{% /callout %}
31+
32+
---
33+
34+
## Your DID
35+
36+
Your DID is your permanent identity. It looks like `did:plc:z72i7hdynmk6r22z27h6tvur` and is resolved via the [PLC directory](https://plc.directory), which maps it to your current PDS, public keys, and handle.
37+
38+
Every record you create carries your DID as the author. If you change PDS providers, your DID stays the same — other applications continue to recognize you and your data migrates with you.
39+
40+
---
41+
42+
## Handles (your public username) and domain verification
43+
44+
Handles are not needed to log in to the Hypercerts ecosystem, but every user has one. They serve as human-readable names for publicly addressing others and for interacting with other applications in the AT Protocol ecosystem that haven't implemented email-based login with Certified. Your handle is a human-readable name like `alice.certified.app`. Unlike your DID, your handle can change — it's a pointer to your DID, not your identity itself.
45+
46+
**Organizations should use custom domain handles.** A handle like `numpy.org` proves organizational identity — anyone can verify that the DID behind `numpy.org` is controlled by whoever controls the domain.
47+
48+
To set up a custom handle, add a DNS TXT record or host a file at `https://your-domain.com/.well-known/atproto-did`. See the [AT Protocol handle documentation](https://atproto.com/specs/handle) for details.
49+
50+
{% callout type="note" %}
51+
If you sign up using your email on certified.app you will initially be given a random handle like `1lasdk.certified.app`. You can change your handle by going to your profile settings and clicking on "Change handle" on [certified.app](https://certified.app).
52+
{% /callout %}
53+
---
54+
55+
## Organization accounts
56+
57+
For teams with multiple contributors, create a dedicated organizational account on a PDS. The organization gets its own DID and repository. Team members can write to the organization's repository using app passwords or OAuth scoped to the organizational account. This is useful for open-source projects, research labs, and organizations where many people contribute to the same body of work.
58+
59+
{% callout type="note" %}
60+
To set up an organizational account, create an account at [certified.app](https://certified.app) with the organization's email. Use a [custom domain handle](#handles-your-public-username-and-domain-verification) (e.g., `numpy.org`) to prove organizational identity.
61+
{% /callout %}
62+
63+
---
64+
65+
## Authentication
66+
67+
### OAuth (for applications)
68+
69+
Applications authenticate users via AT Protocol OAuth. The AT Protocol client libraries handle the full OAuth flow — authorization, token management, and session restoration. Users authorize your app through their PDS and never share credentials with your application. See the [Quickstart](/getting-started/quickstart) for the authentication setup.
70+
71+
### OAuth (for ePDS)
72+
The ePDS (extended PDS) adds email/passwordless login on top of the standard PDS, without modifying the underlying AT Protocol PDS code. When a user authenticates, the ePDS Auth Service handles the OTP flow and then issues a standard AT Protocol authorization code back to your app.
73+
74+
You can integrate ePDS with [`@atproto/oauth-client-node`](https://github.com/bluesky-social/atproto/tree/main/packages/oauth/oauth-client-node). If your app already has an email field, start OAuth normally and add `login_hint=<email>` to the authorization URL before redirecting the user. If your app just has a sign-in button, redirect the user normally and let ePDS collect the email itself. You can also optionally add `epds_handle_mode` to the authorization URL to control how new users get handles.
75+
76+
{% callout type="note" %}
77+
See [ePDS (extended PDS)](/architecture/epds) for integration examples, handle mode configuration, and client metadata options. A ready-made skill that implements the full ePDS OAuth flow is available at `.agents/skills/epds-login/SKILL.md` in the [ePDS repository](https://github.com/hypercerts-org/ePDS).
78+
{% /callout %}
79+
80+
81+
### App passwords (for scripts and CLI)
82+
83+
For scripts, CLI tools, and server-side automation, use app passwords instead of your main password. App passwords are scoped credentials that can be revoked independently.
84+
85+
Create one in your account settings at [certified.app](https://certified.app). Give it a descriptive name (e.g., "CI/CD pipeline" or "Local development"). If a credential is compromised, revoke just that app password — your main account stays secure. The [Hypercerts CLI](/tools/hypercerts-cli) uses app passwords for authentication.
86+
87+
{% callout type="warning" %}
88+
Never commit app passwords to version control. Use environment variables or a secrets manager.
89+
{% /callout %}
90+
91+
---
92+
93+
## The app.certified namespace
94+
95+
Beyond identity, Certified contributes shared data schemas to the AT Protocol ecosystem. You'll see `app.certified.*` in some lexicon names — for example, [`app.certified.location`](/lexicons/certified-lexicons/location) defines how geographic locations are represented. These are general-purpose schemas available to any application on AT Protocol, not just Hypercerts.
96+
97+
---
98+
99+
## Next steps
100+
101+
- [Quickstart](/getting-started/quickstart) — build a complete hypercert with contributions, attachments, and measurements
102+
- [Working with Evaluations](/getting-started/working-with-evaluations) — create evaluations of other people's work

0 commit comments

Comments
 (0)