diff --git a/.gitignore b/.gitignore
index 7d170716..e81c4154 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
# Dependencies
/node_modules
-package-lock.json
# Production
/build
diff --git a/CLAUDE.md b/CLAUDE.md
index ce2ffe03..2eedb209 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -57,13 +57,9 @@ Each product section typically contains:
### Sidebar Configuration
-Sidebars are defined in `sidebars.js` with four main sections:
-- `defraSidebar` - Auto-generated from `docs/defradb/`
-- `sourcehubSidebar` - Auto-generated from `docs/sourcehub/` + API link
-- `orbisSidebar` - Auto-generated from `docs/orbis/`
-- `lensvmSidebar` - Auto-generated from `docs/lensvm/`
-
-The changelog/release notes sidebar items are automatically reversed (newest first) via the `reverseSidebarChangelog()` function in `docusaurus.config.js:185-194`.
+Sidebars come from two files:
+- `docs/sidebars/defra.js` - `defraSidebar`, written by hand, with one auto-generated block for `docs/defradb/references/`
+- `sidebars.js` - `sourcehubSidebar` (auto-generated + API link), `orbisSidebar` and `lensvmSidebar` (auto-generated)
### API Documentation
diff --git a/docs/defradb/install/deploy-config.md b/docs/defradb/install/deploy-config.md
index 0840bd11..49edac15 100644
--- a/docs/defradb/install/deploy-config.md
+++ b/docs/defradb/install/deploy-config.md
@@ -44,7 +44,7 @@ To enable TLS with keys located in custom paths, start the instance providing th
defradb start --pubkeypath ~/.defradb/certs/pubkey.crt --privkeypath ~/.defradb/certs/privkey.key
```
-:::tip generate a self-signed certificate
+:::tip Generate a self-signed certificate
```shell
mkdir -p ~/.defradb/certs
openssl ecparam -genkey -name secp384r1 -out ~/.defradb/certs/server.key
@@ -60,7 +60,7 @@ The `defradb` CLI commands don't support connection to instances with TLS enable
By default, DefraDB starts with its P2P features active. For information on how to set up P2P, see [Synchronize documents across multiple nodes](/p2p/index.md).
-:::note disable p2p
+:::note Disable P2P
Disable P2P on an instance by starting it with the `--no-p2p` flag.
```bash
diff --git a/docs/defradb/p2p/pub-sub.md b/docs/defradb/p2p/pub-sub.md
index bb4667bb..7fc5b410 100644
--- a/docs/defradb/p2p/pub-sub.md
+++ b/docs/defradb/p2p/pub-sub.md
@@ -111,7 +111,7 @@ Note how you don't specify *to what peer's* collection you subscribe to: Node2 w
When a document update is submitted to Node1, Node2 receives the update and will then broadcast the message further to any peers connected to it. This chatty architecture allows updates to travel across the P2P network wide and far, regardless of whether two specific peers are connected or not and any node's connectivity status at any one moment.
-:::info instance shutdown
+:::info Instance shutdown
An instance's list of peers is cleared on shutdown, so you will need to reconnect peers when restarting it. Pub-Sub subscriptions are instead retained across restarts.
:::
diff --git a/docs/sidebars/styleguide.js b/docs/sidebars/styleguide.js
new file mode 100644
index 00000000..e7f54702
--- /dev/null
+++ b/docs/sidebars/styleguide.js
@@ -0,0 +1,11 @@
+// Sidebar for the internal styleguide docs instance. Kept separate from
+// sidebars.js so the styleguide pages don't get picked up by the autogenerated
+// sidebars the product instances share.
+export default {
+ styleguideSidebar: [
+ {
+ type: 'autogenerated',
+ dirName: '.',
+ },
+ ],
+};
diff --git a/docs/styleguide/_reference-table.module.scss b/docs/styleguide/_reference-table.module.scss
new file mode 100644
index 00000000..bd7a7fe3
--- /dev/null
+++ b/docs/styleguide/_reference-table.module.scss
@@ -0,0 +1,23 @@
+// Styling for the metastring reference table in components.mdx, kept as a CSS
+// module beside the page rather than a partial in src/css: the styleguide is
+// internal, and a table that reads as reference material isn't a pattern the
+// product docs should inherit. The leading underscore keeps Docusaurus from
+// treating the file as a doc page.
+
+.optionsTable {
+ // Denser rows than the site default, which is set for prose-length cells:
+ // --ifm-table-cell-padding is 0.75rem 1rem in variables.scss. Set on the
+ // wrapper so th and td both follow it, and _tables.scss keeps zeroing the
+ // outer edges of the first and last columns.
+ --ifm-table-cell-padding: 0.375rem 1rem;
+
+ // Short option names against long descriptions: both a step down from body
+ // copy, the descriptions further still, since they're the bulk of the table.
+ table {
+ font-size: 0.875rem;
+ }
+
+ td:last-child {
+ font-size: 0.6875rem; // 11px
+ }
+}
diff --git a/docs/styleguide/colors.mdx b/docs/styleguide/colors.mdx
new file mode 100644
index 00000000..7d5b6c8f
--- /dev/null
+++ b/docs/styleguide/colors.mdx
@@ -0,0 +1,303 @@
+---
+title: Design tokens
+sidebar_position: 3
+description: Every custom property declared in variables.scss, rendered as swatches.
+---
+
+
+
+
+
+{/* Styled inline rather than with classes: this page is internal, and anything it puts in a stylesheet ships to every product page too. */}
+
+export const Swatch = ({ name, from }) => (
+
+);
+
+Every colour token declared in `src/css/variables.scss`. Each swatch shows the token's real value, so switching the theme updates the whole page. Spacing, type and sizing tokens are in the table at the end.
+
+Some tokens are aliases: they point at another token rather than holding a colour themselves. When that's the case, the token it points at is listed underneath, after a `↳`.
+
+A token can point at different things in each theme. Those are written `light / dark`. A `—` on one side means the token is not an alias in that theme; it holds a value of its own, either an Infima default we haven't changed or a one-off colour with no brand token behind it.
+
+## Brand
+
+`--src-amber` is the LensVM product accent, reused as the semantic warning colour, a deliberate departure from the brand's "keep accents isolated to their contexts" guidance.
+
+
+
+## Brand color scales
+
+The green and grey ramps from the Source brand system, using the brand's own step numbers.
+
+`--src-green-400` is the same colour as `--src-primary`. In light mode, `--ifm-color-primary` and its shades all resolve through `--src-green-*`.
+
+Every grey in the site resolves back to this grey ramp. The mapping onto Infima's `--ifm-color-gray-*` happens in `variables.scss`. `--src-gray-50` has no Infima slot and is currently unused.
+
+
+
+
+
+## Interaction overlays
+
+These are translucent, so the swatches below show the page background through them.
+
+The sidebar menu and the tab bar share them. They are kept separate from Infima's `--ifm-hover-overlay`, which stays at 5% and is used by breadcrumbs, dropdowns, pagination and pills.
+
+
+
+## Infima primary
+
+`--ifm-color-primary` is `--src-green-500` in light and `--src-highlight` in dark.
+
+Light mode uses the darker step because the brand green itself, `--src-primary`, reaches only 2.8:1 on white. Text needs 4.5:1.
+
+The `-dark` through `-lightest` variants are each a step on the brand's green scale, not a ramp generated from the base colour. Infima uses `-dark` and `-darker` for the hover and active states on `.button--primary`.
+
+
+
+## Surfaces and text
+
+
+
+## Infima gray ramp
+
+A straight alias onto the brand neutrals, identical in both themes; this is the single mapping that the emphasis scale below is built from.
+
+
+
+## Emphasis scale
+
+The ramp used for borders and fills, aliased onto the grey ramp above.
+
+It is **inverted** in dark mode, so each step points at the opposite end of the grey ramp: `emphasis-0` is `gray-0` in light and `gray-1000` in dark. `emphasis-500` is the midpoint and lands on `gray-500` either way. `emphasis-200` is the step the partials use most.
+
+
+
+## Cards
+
+Declared globally but only consumed by `src/pages/index.module.scss`, so these apply to the homepage rather than to docs.
+
+
+
+## Alerts and admonitions
+
+Each admonition variant is drawn with one of Infima's alert colour families, so the families below are what `:::note` and friends actually resolve through:
+
+| Admonition | Alert family |
+| --- | --- |
+| `:::note` | `--secondary` |
+| `:::tip` | `--success` |
+| `:::info` | `--info` |
+| `:::warning` and `:::caution` | `--warning` |
+| `:::danger` | `--danger` |
+
+Info uses tip's fill. Warning and danger take their accent from a brand colour, but keep Infima's own contrast pairs, which differ per theme.
+
+
+
+## Code blocks
+
+
+
+## Syntax tokens
+
+
+
+## Feedback widget
+
+pushfeedback's own variables, overridden in `variables.scss`. See `src/css/_feedback.scss` for the `::part()` rules that reach into its shadow DOM.
+
+
+
+## Non-color tokens
+
+| Token | Value | Used for |
+| --- | --- | --- |
+| `--root-wrapper-width` | `110rem` | Max width of navbar, footer, and main wrapper |
+| `--doc-sidebar-width` | `18.75rem` | Doc sidebar width |
+| `--h1-base-font-size` | `2.25rem` | Shared `h1` size token |
+| `--ifm-h4-font-size` | `1.313rem` | Heading level 4 |
+| `--ifm-font-size-base` | `1rem` | Body copy |
+| `--ifm-font-weight-bold` | `800` | Bold text |
+| `--ifm-leading-desktop` | `1.75` | Line height multiplier |
+| `--ifm-navbar-height` | `80px` | Navbar height |
+| `--ifm-global-radius` | `0rem` | Global corner radius |
+| `--ifm-code-border-radius` | `0.375rem` | Inline code, code blocks, admonitions |
+| `--ifm-alert-border-left-width` | `1px` | Admonition border (matches the other sides) |
+| `--ifm-container-width-xl` | `1400px` | Wide container breakpoint |
+
+## Fonts
+
+
+
+ Funnel Display for headings
+
+ --ifm-heading-font-family
+
+
+
+
+ Inter for body copy
+
+ --ifm-font-family-base
+
+
+
+
+ Monospace for code
+
+ --ifm-font-family-monospace
+
diff --git a/docs/styleguide/components.mdx b/docs/styleguide/components.mdx
new file mode 100644
index 00000000..2c677913
--- /dev/null
+++ b/docs/styleguide/components.mdx
@@ -0,0 +1,318 @@
+---
+title: Components
+sidebar_position: 2
+description: Admonitions, details, tabs, and every code block variant.
+---
+
+
+
+
+
+import styles from "./_reference-table.module.scss";
+
+## Admonitions
+
+All five Infima variants. Styled by `_admonitions.scss`, which lays the icon out in its own column, drops the default type label, and gives each variant a full rounded border toned halfway to its background with `color-mix`. The icons are Lucide replacements from `src/theme/Admonition/Icon/`.
+
+::::demo
+:::note
+A note admonition. Contains **bold text**, `inline code`, and a [link](https://github.com/sourcenetwork) to check inherited colors; `.alert` sets its own `--ifm-link-color`.
+:::
+
+:::tip
+A tip admonition.
+:::
+
+:::info
+An info admonition. This is the variant Docusaurus reuses for ``. See below, where `_details.scss` neutralises the inherited green accent.
+:::
+
+:::warning
+A warning admonition.
+:::
+
+:::danger
+A danger admonition.
+:::
+
+:::note[Custom title]
+An admonition with a custom title instead of the variant name.
+:::
+::::
+
+## Details / accordion
+
+Rendered by the `Details` theme component, which hardcodes `alert alert--info` onto every block, so these inherit admonition styling. The stable selector is `details.alert`; both classes in the DOM are hashed CSS modules.
+
+:::demo
+
+A collapsed details block
+
+Content revealed on expand. The chevron in the summary is a masked SVG colored by `--docusaurus-details-decoration-color`; the divider above this text follows `--details-border-color` instead, so it matches the box.
+
+- A list inside details
+- To check nested spacing
+
+
+
+
+A details block that starts open
+
+This one carries the `open` attribute, so it renders expanded. Note the arrow rotation, driven by `--docusaurus-details-transition`.
+
+```graphql
+query {
+ Book {
+ title
+ }
+}
+```
+
+
+:::
+
+## Tabs
+
+Registered globally in `src/theme/MDXComponents.tsx`. The left border and label weight come from the `.tabs-container` rule in `_admonitions.scss`.
+
+:::demo
+
+
+
+```bash
+defradb client collection create --name Book
+```
+
+
+
+
+```bash
+curl -X POST http://localhost:9181/api/v0/collections
+```
+
+
+
+
+```graphql
+mutation {
+ create_Book(input: { title: "Dune" }) {
+ _docID
+ }
+}
+```
+
+
+
+:::
+
+## Code blocks
+
+The wrapper at `src/theme/CodeBlock/index.tsx` parses the bare-word flags off a code block's metastring; the `key="value"` pairs are Docusaurus's own. Every option:
+
+
+
+| Option | Effect |
+| --- | --- |
+| `title="…"` | Captions the block. Quoted pairs are stripped before flags are matched, so a title can contain a flag word. |
+| `{2-3}` | Highlights a line range. Prefer the magic comments below; the numbers go stale as the example is edited. |
+| `showLineNumbers` | Numbers the gutter. |
+| `valid` | Frames the block as a correct example: `.codeblock-state-valid`, tinted with `--codeblock-valid-border`. |
+| `invalid` | Frames it as an incorrect one: `.codeblock-state-invalid`. Wins if both flags are present. |
+| `result` | Marks the block as a query's output: `.codeblock-result`, which closes the gap to the block above it, and opts it into collapsing past 12 lines. |
+| `collapse` | Forces the collapse UI on at any length. |
+| `noCollapse` | Forces it off, including for a long `result`. |
+| `expanded` | Starts a collapsing block open. No effect on one that doesn't collapse. |
+
+
+
+Each variant below is shown with the code block that produced it.
+
+### Plain, and with a title
+
+A bare code block, then the same one with `title="…"` on its metastring.
+
+:::demo
+```go
+func main() {
+ fmt.Println("hello")
+}
+```
+
+```go title="main.go"
+func main() { fmt.Println("hello")
+}
+```
+:::
+
+### Highlighted lines
+
+Magic comments, configured under `prism.magicComments` in `docusaurus.config.js`. Preferred over the `{2-3}` range metastring, whose line numbers go stale as soon as the example is edited. The marker follows the block's own language: `//` here, `#` for GraphQL and shell. Markers are stripped from the rendered output, so unlike the variants above they can't be shown in a literal code block.
+
+`// highlight-next-line` marks the line below it; `// highlight-start` and `// highlight-end` mark a chunk:
+
+:::demo
+```go
+func main() {
+ // highlight-next-line
+ client := NewClient()
+ fmt.Println("hello")
+}
+```
+
+```go
+func main() {
+ // highlight-start
+ client := NewClient()
+ defer client.Close()
+ // highlight-end
+ fmt.Println("hello")
+}
+```
+:::
+
+### Valid and invalid examples
+
+The bare-word `valid` and `invalid` flags tint the block's border; see `--codeblock-valid-border` and `--codeblock-invalid-border`.
+
+:::demo
+```graphql title="Correct" valid
+mutation {
+ create_Book(input: { title: "Dune" }) {
+ _docID
+ }
+}
+```
+
+```graphql title="Incorrect" invalid
+mutation {
+ create_Book(title: "Dune") {
+ _docID
+ }
+}
+```
+:::
+
+### Query followed by a result
+
+The `.theme-code-block:has(+ .codeblock-result)` rule squashes the gap between the pair.
+
+:::demo
+```graphql
+query {
+ Book {
+ title
+ }
+}
+```
+
+```json title="Result" result
+{
+ "data": {
+ "Book": [{ "title": "Dune" }]
+ }
+}
+```
+:::
+
+### Collapsing
+
+Whether a block collapses is decided in `src/theme/CodeBlock/Content/index.tsx`: `collapse` forces it on, `noCollapse` forces it off, and with neither, a block flagged `result` collapses itself once it reaches `MAX_LINES + LINE_TOLERANCE`, or 12 lines. `expanded` only matters on a block that collapses, and starts it open.
+
+`collapse`, and `collapse expanded`:
+
+:::demo
+```json title="A long response" collapse
+{
+ "data": {
+ "Book": [
+ { "title": "Dune", "author": "Frank Herbert", "year": 1965 },
+ { "title": "Neuromancer", "author": "William Gibson", "year": 1984 },
+ { "title": "Snow Crash", "author": "Neal Stephenson", "year": 1992 },
+ { "title": "Hyperion", "author": "Dan Simmons", "year": 1989 },
+ { "title": "Blindsight", "author": "Peter Watts", "year": 2006 }
+ ]
+ }
+}
+```
+
+```json title="Expanded by default" collapse expanded
+{
+ "data": {
+ "Book": [{ "title": "Dune" }]
+ }
+}
+```
+:::
+
+A `result` block past the threshold, collapsing with no flag asking it to, beside the same block opting out with `noCollapse`:
+
+:::demo
+```json title="Result" result
+{
+ "data": {
+ "Book": [
+ { "title": "Dune", "author": "Frank Herbert", "year": 1965 },
+ { "title": "Neuromancer", "author": "William Gibson", "year": 1984 },
+ { "title": "Snow Crash", "author": "Neal Stephenson", "year": 1992 },
+ { "title": "Hyperion", "author": "Dan Simmons", "year": 1989 },
+ { "title": "Blindsight", "author": "Peter Watts", "year": 2006 },
+ { "title": "Solaris", "author": "Stanisław Lem", "year": 1961 },
+ { "title": "Roadside Picnic", "author": "Arkady Strugatsky", "year": 1972 }
+ ]
+ }
+}
+```
+
+```json title="Result" result noCollapse
+{
+ "data": {
+ "Book": [
+ { "title": "Dune", "author": "Frank Herbert", "year": 1965 },
+ { "title": "Neuromancer", "author": "William Gibson", "year": 1984 },
+ { "title": "Snow Crash", "author": "Neal Stephenson", "year": 1992 },
+ { "title": "Hyperion", "author": "Dan Simmons", "year": 1989 },
+ { "title": "Blindsight", "author": "Peter Watts", "year": 2006 },
+ { "title": "Solaris", "author": "Stanisław Lem", "year": 1961 },
+ { "title": "Roadside Picnic", "author": "Arkady Strugatsky", "year": 1972 }
+ ]
+ }
+}
+```
+:::
+
+### Syntax highlighting per language
+
+Token colors come from the `--code-token-*` variables and `src/code-theme/code-theme.js`.
+
+:::demo
+```javascript
+// A comment
+const greeting = "hello";
+export function greet(name = "world") {
+ return `${greeting}, ${name}! ${1 + 2}`;
+}
+```
+
+```python
+# A comment
+def greet(name: str = "world") -> str:
+ return f"hello, {name}"
+```
+
+```yaml
+# A comment
+service:
+ name: defradb
+ port: 9181
+ enabled: true
+```
+
+```sql
+SELECT title, author FROM books WHERE year > 1980 ORDER BY year;
+```
+
+```diff
+- const old = true;
++ const new = false;
+```
+:::
diff --git a/docs/styleguide/index.mdx b/docs/styleguide/index.mdx
new file mode 100644
index 00000000..b89381fa
--- /dev/null
+++ b/docs/styleguide/index.mdx
@@ -0,0 +1,134 @@
+---
+title: Typography
+slug: /
+sidebar_position: 1
+description: Every text element the docs theme renders, in one place.
+---
+
+
+
+
+
+An unlisted page for reviewing site styles. It isn't linked from the navbar or any product sidebar; reach it at `/styleguide`. Everything below is rendered by the real doc layout, so the surrounding chrome (navbar, sidebar, breadcrumbs, table of contents, pagination, "Edit this page", feedback widget) is on display here too.
+
+## Headings
+
+Sized by `--ifm-h1-font-size` … `--ifm-h4-font-size` in `variables.scss`, with `--h1-base-font-size` as the shared h1 token. The page title above is the `h1`.
+
+:::demo
+## Heading level 2
+
+### Heading level 3
+
+#### Heading level 4
+
+##### Heading level 5
+
+###### Heading level 6
+:::
+
+## Body copy
+
+:::demo
+Body text uses `--ifm-font-family-base` (Inter) at `--ifm-font-size-base`, with line height from `--ifm-leading-desktop`. This paragraph exists to show how a longer run of prose sets, wraps, and breathes between block elements, long enough to wrap onto several lines at the site's content width so leading and measure are both visible.
+
+Text can be **bold** (`--ifm-font-weight-bold`), *italic*, ***both at once***, ~~struck through~~, or marked as `inline code`, which picks up `--ifm-code-background`, `--ifm-code-font-size`, and `--ifm-code-border-radius`.
+
+You can also use Cmd + K for keyboard keys, H2O for subscript, and x2 for superscript.
+:::
+
+## Links
+
+:::demo
+An [internal link to another styleguide page](./components.mdx) sits inline in a sentence. An [external link](https://github.com/sourcenetwork) gets the arrow icon appended by the `.theme-doc-markdown a[target="_blank"]::after` rule in `_markdown.scss`, which masks `/img/icon-external-link.svg` and tints it with `currentColor`.
+:::
+
+## Lists
+
+Unordered, ordered with nesting, a task list, and a list containing a code block; the last of which the `li div.theme-code-block` rule in `_code-blocks.scss` reins in.
+
+:::demo
+- First item
+- Second item, with enough text that it wraps to a second line and shows how the hanging indent behaves against the marker
+- Third item
+ - Nested item
+ - Another nested item
+ - Third level
+
+1. First step
+2. Second step
+ 1. Nested step
+ 2. Another nested step
+3. Third step
+
+- [x] Completed item
+- [ ] Incomplete item
+
+1. Run the command:
+
+ ```bash
+ defradb start
+ ```
+
+2. Then check the result.
+:::
+
+## Blockquote
+
+:::demo
+> A blockquote holds a pulled-out passage. It can run to several sentences, and should show its left border, padding, and background treatment clearly.
+>
+> Attribution line
+:::
+
+## Horizontal rule
+
+Colored by `--ifm-hr-background-color`, which differs between light and dark.
+
+---
+
+## Tables
+
+:::demo
+| Column | Type | Default | Description |
+| --- | --- | --- | --- |
+| `name` | `string` | — | The field name, which is required |
+| `type` | `string` | `String` | The scalar or object type |
+| `index` | `boolean` | `false` | Whether to index the field |
+| `unique` | `boolean` | `false` | Whether values must be unique |
+:::
+
+Left, center, and right alignment:
+
+:::demo
+| Left | Center | Right |
+| :--- | :----: | ----: |
+| a | b | c |
+| longer cell | longer cell | longer cell |
+:::
+
+## Images
+
+:::demo
+
+:::
+
+## Side-by-side utility
+
+The `.sidebyside-*` classes in `_markdown.scss` only engage above 800px.
+
+:::demo
+
+
+**Left column.** This content floats left at 49% width on wide viewports and stacks on narrow ones.
+
+
+
+
+
+**Right column.** Its pair, floated right, also at 49%.
+
+