~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

The pages, the harness and the docs site are formatted by oxfmt

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:web/oxfmt
.claude/hooks/format.mjsnew+34−0
@@ -0,0 +1,34 @@
// Claude Code's PostToolUse hook: formats each file an agent writes as
// `just fmt` would, so what is committed passes the format checks. Rust
// by rustfmt; the pages', the harness's and the docs site's code by oxfmt,
// which skips what .oxfmtrc.json ignores. Anything else, or a file a
// formatter cannot parse yet, is left as written: the checks still catch
// what is pushed.
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { relative, resolve } from 'node:path';
const root = resolve(import.meta.dirname, '../..');
const input = JSON.parse(readFileSync(0, 'utf8'));
const file = input.tool_input?.file_path;
if (!file || !existsSync(file)) process.exit(0);
const path = relative(root, resolve(file));
const run = (cmd, args) => {
try {
execFileSync(cmd, args, { cwd: root, stdio: 'ignore' });
} catch {
// Left as written.
}
};
if (path.endsWith('.rs')) {
run('rustup', ['run', 'stable', 'rustfmt', '--edition', '2024', path]);
} else if (
/^(web|harness|docs-site)\//.test(path) &&
!path.includes('node_modules/') &&
/\.(svelte|ts|js|mjs|cjs|css|json)$/.test(path)
) {
const oxfmt = resolve(root, 'web/node_modules/.bin/oxfmt');
if (existsSync(oxfmt)) run(oxfmt, ['--no-error-on-unmatched-pattern', path]);
}
.claude/settings.jsonnew+15−0
@@ -0,0 +1,15 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/format.mjs\""
}
]
}
]
}
}
.knotless/workflows/web.yml+11−8
@@ -1,23 +1,26 @@
# The web pages' checks, as `just lint` runs them: svelte-check, which
# fails on any warning, that each component uses only its own layer
# and those below (harness/layers.mjs), and that every theme's text meets
# WCAG AA contrast on what it is drawn on (harness/contrast.mjs). The
# packages are kept between runs and installed again only when the
# The web pages' checks, as `just lint` runs them: that the pages, the
# harness and the docs site are formatted as `just fmt` leaves them (oxfmt),
# svelte-check, which fails on any warning, that each component uses only
# its own layer and those below (harness/layers.mjs), and that every theme's
# text meets WCAG AA contrast on what it is drawn on (harness/contrast.mjs).
# The packages are kept between runs and installed again only when the
# lockfile changes.
image: node:24.22.1
# Every push to main, and pulls into it or a feature/ branch, at their
# heads merged with their base, that change the pages, the layers' check
# or this file. A pull changing none passes at once, noted as skipped.
# heads merged with their base, that change the pages, the harness, the
# docs site, the format's settings or this file. A pull changing none passes at once, noted as skipped.
on:
push: {refs: [refs/heads/main]}
pull:
base: [refs/heads/main, refs/heads/feature/*]
paths: [web/**, lexicons/**, harness/layers.mjs, harness/contrast.mjs, .knotless/workflows/web.yml, .knotless/uses.lock]
paths: [web/**, lexicons/**, harness/**, docs-site/**, .oxfmtrc.json, .knotless/workflows/web.yml, .knotless/uses.lock]
cache: [web/node_modules]
steps:
- uses: chadtmiller.com/steps/npm-ci@1
name: packages
with: {dir: web}
- name: format
run: web/node_modules/.bin/oxfmt --check web harness docs-site
- name: layers
run: node harness/layers.mjs
- name: contrast
docs/development.md+9−1
@@ -34,7 +34,8 @@ just setup # npm install in harness/ and web/, llvm-tools for coverage
just test # every test
just test-spaces # against the alpha: KNOTLESS_DEV_ENV names its dev-env
just cov # both runs side by side, failing under 100% lines or functions
just lint # rustfmt, clippy and svelte-check
just fmt # rustfmt and oxfmt
just lint # rustfmt, oxfmt, clippy and svelte-check
just lex # lint the lexicons, and check they only grow from main's
just bench 50 # the performance table, at 50 ms RTT
just bench-cards # what a social card costs to draw, with the PNGs
@@ -77,6 +78,13 @@ knotless-serve.
the root gets `data-ready` once mounted. Screenshots wait for both.
- Components are layered as atoms, molecules, organisms and templates; a
component uses its own layer and those below, and `just lint` checks it.
- The pages, the harness and the docs site are formatted by
[oxfmt](https://oxc.rs/docs/guide/usage/formatter), set in `.oxfmtrc.json`:
tabs in `web/`, and two spaces and no semicolons in the other two. Markdown,
YAML, the recorded fixtures and the lexicons are left as they are. `just fmt`
formats them, the `web` check fails on what it would change, and
`.claude/settings.json` has Claude Code format each file it writes. oxfmt comes
from `web/node_modules`, and may need a second run to settle.
The queries are listed in the [API reference](reference/api.md#web-data).
docs-site/src/content.config.ts+5−5
@@ -1,8 +1,8 @@
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
import { defineCollection } from 'astro:content'
import { docsLoader } from '@astrojs/starlight/loaders'
import { docsSchema } from '@astrojs/starlight/schema'
// The pages content.mjs copies from docs/.
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() })
};
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
}
docs-site/src/theme.css+129−124
@@ -8,213 +8,218 @@
:root,
:root[data-theme='light'] {
--sl-color-white: var(--fg);
--sl-color-gray-1: var(--fg);
--sl-color-gray-2: var(--fg);
--sl-color-gray-3: var(--mute);
--sl-color-gray-4: var(--faint);
--sl-color-gray-5: var(--line);
--sl-color-gray-6: var(--soft);
--sl-color-gray-7: var(--panel);
--sl-color-black: var(--bg);
--sl-color-accent-low: var(--accent-soft);
--sl-color-accent: var(--primary);
--sl-color-accent-high: var(--accent);
--sl-color-bg-nav: var(--bg);
--sl-color-bg-sidebar: var(--bg);
--sl-color-hairline: var(--line);
--sl-color-hairline-light: var(--line);
--sl-color-hairline-shade: var(--line);
--sl-font: var(--font-sans);
--sl-font-mono: var(--font-mono);
--sl-line-height: var(--leading-prose);
/* Type on the pages' prose scale, not Starlight's larger one. */
--sl-text-body: var(--text-md);
--sl-text-h1: var(--text-3xl);
--sl-text-h2: var(--text-xl);
--sl-text-h3: var(--text-lg);
--sl-text-h4: var(--text-md);
--sl-text-h5: var(--text-md);
--sl-text-h6: var(--text-md);
--sl-color-white: var(--fg);
--sl-color-gray-1: var(--fg);
--sl-color-gray-2: var(--fg);
--sl-color-gray-3: var(--mute);
--sl-color-gray-4: var(--faint);
--sl-color-gray-5: var(--line);
--sl-color-gray-6: var(--soft);
--sl-color-gray-7: var(--panel);
--sl-color-black: var(--bg);
--sl-color-accent-low: var(--accent-soft);
--sl-color-accent: var(--primary);
--sl-color-accent-high: var(--accent);
--sl-color-bg-nav: var(--bg);
--sl-color-bg-sidebar: var(--bg);
--sl-color-hairline: var(--line);
--sl-color-hairline-light: var(--line);
--sl-color-hairline-shade: var(--line);
--sl-font: var(--font-sans);
--sl-font-mono: var(--font-mono);
--sl-line-height: var(--leading-prose);
/* Type on the pages' prose scale, not Starlight's larger one. */
--sl-text-body: var(--text-md);
--sl-text-h1: var(--text-3xl);
--sl-text-h2: var(--text-xl);
--sl-text-h3: var(--text-lg);
--sl-text-h4: var(--text-md);
--sl-text-h5: var(--text-md);
--sl-text-h6: var(--text-md);
}
h1, h2, h3, h4, h5, h6 {
font-weight: var(--weight-semibold);
letter-spacing: var(--tracking-tight);
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: var(--weight-semibold);
letter-spacing: var(--tracking-tight);
}
/* A section's heading is ruled off, as in the pages' prose. */
.sl-markdown-content .level-h2 {
padding-bottom: var(--space-1-5);
border-bottom: var(--border);
padding-bottom: var(--space-1-5);
border-bottom: var(--border);
}
/* The header: the pages' brand, in their mono. */
.brand {
display: flex;
align-items: baseline;
gap: var(--space-2);
font: var(--weight-semibold) var(--text-base) var(--font-mono);
color: var(--fg);
text-decoration: none;
white-space: nowrap;
display: flex;
align-items: baseline;
gap: var(--space-2);
font: var(--weight-semibold) var(--text-base) var(--font-mono);
color: var(--fg);
text-decoration: none;
white-space: nowrap;
}
.brand.section {
font-weight: var(--weight-normal);
color: var(--mute);
font-weight: var(--weight-normal);
color: var(--mute);
}
/* Links underlined quietly, as the pages' prose has them. */
.sl-markdown-content a:not(:where(.not-content *)) {
color: var(--fg);
text-decoration-color: var(--link-line);
text-underline-offset: var(--underline-offset);
color: var(--fg);
text-decoration-color: var(--link-line);
text-underline-offset: var(--underline-offset);
}
.sl-markdown-content a:not(:where(.not-content *)):hover {
color: var(--fg);
text-decoration-color: currentColor;
color: var(--fg);
text-decoration-color: currentColor;
}
/* Sidebar: the page one is on marked as the pages mark the tab one is on. */
.sidebar-content a[aria-current='page'] {
background: var(--accent-soft);
color: var(--fg);
font-weight: var(--weight-semibold);
background: var(--accent-soft);
color: var(--fg);
font-weight: var(--weight-semibold);
}
/* Diagrams take the page's colours, as the repository pages' do. */
.sl-markdown-content .diagram {
line-height: normal;
overflow-x: auto;
--pele-bg: var(--bg);
--pele-fg: var(--fg);
--pele-muted: var(--mute);
--pele-line: var(--mute);
--pele-surface: var(--soft);
--pele-surface-alt: var(--panel);
--pele-border: var(--faint);
--pele-accent: var(--primary);
--pele-critical: var(--rem);
--pele-error: var(--rem);
--pele-font: var(--font-sans);
--pele-font-mono: var(--font-mono);
line-height: normal;
overflow-x: auto;
--pele-bg: var(--bg);
--pele-fg: var(--fg);
--pele-muted: var(--mute);
--pele-line: var(--mute);
--pele-surface: var(--soft);
--pele-surface-alt: var(--panel);
--pele-border: var(--faint);
--pele-accent: var(--primary);
--pele-critical: var(--rem);
--pele-error: var(--rem);
--pele-font: var(--font-sans);
--pele-font-mono: var(--font-mono);
}
/* Reference: a table of names and what each does, drawn as a list of
them (reference.mjs), each name on a line of its own. */
.sl-markdown-content .ref-list {
margin-top: var(--space-4);
border-top: var(--border);
margin-top: var(--space-4);
border-top: var(--border);
}
.sl-markdown-content .ref-list dt {
margin-top: 0;
padding-top: var(--space-4);
font-weight: var(--weight-normal);
margin-top: 0;
padding-top: var(--space-4);
font-weight: var(--weight-normal);
}
.sl-markdown-content .ref-list dt code {
background: none;
padding: 0;
font-size: var(--text-md);
font-weight: var(--weight-semibold);
/* A signature breaks between its words, never inside a flag. */
overflow-wrap: normal;
word-break: normal;
background: none;
padding: 0;
font-size: var(--text-md);
font-weight: var(--weight-semibold);
/* A signature breaks between its words, never inside a flag. */
overflow-wrap: normal;
word-break: normal;
}
.sl-markdown-content .ref-list dd {
margin: var(--space-1) 0 0;
padding-bottom: var(--space-4);
border-bottom: var(--border);
margin: var(--space-1) 0 0;
padding-bottom: var(--space-4);
border-bottom: var(--border);
}
.sl-markdown-content .ref-list dd > * + * {
margin-top: var(--space-2);
margin-top: var(--space-2);
}
.sl-markdown-content .ref-meta {
font-size: var(--text-sm);
color: var(--mute);
font-size: var(--text-sm);
color: var(--mute);
}
.ref-label {
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: var(--faint);
margin-right: var(--space-1);
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: var(--faint);
margin-right: var(--space-1);
}
/* An HTTP method, as a badge before its route. */
.method {
display: inline-block;
padding: 0 var(--space-1-5);
border-radius: var(--radius-sm);
font: var(--weight-semibold) var(--text-2xs) / 1.6 var(--font-mono);
letter-spacing: 0.02em;
vertical-align: 0.15em;
background: var(--accent-soft);
color: var(--accent);
display: inline-block;
padding: 0 var(--space-1-5);
border-radius: var(--radius-sm);
font: var(--weight-semibold) var(--text-2xs) / 1.6 var(--font-mono);
letter-spacing: 0.02em;
vertical-align: 0.15em;
background: var(--accent-soft);
color: var(--accent);
}
.method-post {
background: var(--soft);
color: var(--green);
background: var(--soft);
color: var(--green);
}
.method-ws {
color: var(--purple);
color: var(--purple);
}
/* A method's lexicon, folded away under it. */
.sl-markdown-content .lexicon {
margin-top: var(--space-2);
border: var(--border);
border-radius: var(--radius-md);
font-size: var(--text-sm);
margin-top: var(--space-2);
border: var(--border);
border-radius: var(--radius-md);
font-size: var(--text-sm);
}
.sl-markdown-content .lexicon summary {
padding: var(--space-1-5) var(--space-3);
color: var(--mute);
cursor: pointer;
padding: var(--space-1-5) var(--space-3);
color: var(--mute);
cursor: pointer;
}
.sl-markdown-content .lexicon[open] summary {
border-bottom: var(--border);
border-bottom: var(--border);
}
.sl-markdown-content .lexicon > :not(summary) {
margin: var(--space-3);
margin: var(--space-3);
}
.sl-markdown-content .lex-title {
font-weight: var(--weight-semibold);
color: var(--fg);
margin-bottom: var(--space-1) !important;
font-weight: var(--weight-semibold);
color: var(--fg);
margin-bottom: var(--space-1) !important;
}
.sl-markdown-content .lex-fields {
display: table;
table-layout: fixed;
width: calc(100% - 2 * var(--space-3));
font-size: var(--text-sm);
display: table;
table-layout: fixed;
width: calc(100% - 2 * var(--space-3));
font-size: var(--text-sm);
}
.sl-markdown-content .lex-fields td {
vertical-align: top;
vertical-align: top;
}
.lex-type {
display: block;
margin-top: var(--space-0-5);
color: var(--mute);
font-size: var(--text-xs);
display: block;
margin-top: var(--space-0-5);
color: var(--mute);
font-size: var(--text-xs);
}
.sl-markdown-content .lex-fields td:first-child {
width: 40%;
width: 40%;
}
.sl-markdown-content .ref-list dt .word {
white-space: nowrap;
white-space: nowrap;
}
.sl-markdown-content .lex-source {
color: var(--mute);
color: var(--mute);
}
/* The header's link to the repository, quiet as the pages' header links
are, and no divider after it: Starlight's sits before the theme
picker, which the docs leave out. */
.social-icons::after {
display: none;
display: none;
}
.social-icons a {
color: var(--mute);
color: var(--mute);
}
.social-icons a:hover {
color: var(--fg);
color: var(--fg);
}
docs-site/astro.config.mjs+12−2
@@ -13,14 +13,24 @@ const page = (label, slug) => ({ label, slug })
export default defineConfig({
site: 'https://knotless.dev',
base,
markdown: { processor: unified({ remarkPlugins: [[remarkLinks, { base }], remarkMermaid, remarkReference] }) },
markdown: {
processor: unified({
remarkPlugins: [[remarkLinks, { base }], remarkMermaid, remarkReference],
}),
},
integrations: [
starlight({
title: 'knotless docs',
description: 'Git repositories stored in atproto accounts: guides, reference and design.',
customCss: ['./src/theme.css'],
components: { SiteTitle: './src/SiteTitle.astro', ThemeSelect: './src/ThemeSelect.astro' },
social: [{ icon: 'seti:git', label: 'The repository', href: 'https://knotless.dev/chadtmiller.com/knotless' }],
social: [
{
icon: 'seti:git',
label: 'The repository',
href: 'https://knotless.dev/chadtmiller.com/knotless',
},
],
lastUpdated: false,
// Code as the pages show it: a plain block, GitHub's colours.
expressiveCode: {
docs-site/package.json+15−15
@@ -1,17 +1,17 @@
{
"name": "knotless-docs",
"private": true,
"type": "module",
"scripts": {
"content": "node content.mjs",
"dev": "node content.mjs && astro dev",
"build": "node content.mjs && astro build --force",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/markdown-remark": "7.3.5",
"@astrojs/starlight": "0.42.7",
"astro": "7.4.3",
"pele": "1.8.0"
}
"name": "knotless-docs",
"private": true,
"type": "module",
"scripts": {
"content": "node content.mjs",
"dev": "node content.mjs && astro dev",
"build": "node content.mjs && astro build --force",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/markdown-remark": "7.3.5",
"@astrojs/starlight": "0.42.7",
"astro": "7.4.3",
"pele": "1.8.0"
}
}
docs-site/plugins.mjs+6−2
@@ -23,7 +23,10 @@ function resolve(page, url, base) {
const target = posix.normalize(posix.join('docs', posix.dirname(page), path))
const hash = anchor ? `#${anchor}` : ''
if (target.startsWith('docs/') && target.endsWith('.md') && target !== 'docs/design.md') {
const slug = target.slice('docs/'.length).replace(/\.md$/, '').replace(/(^|\/)README$/, '')
const slug = target
.slice('docs/'.length)
.replace(/\.md$/, '')
.replace(/(^|\/)README$/, '')
return `${base}/${slug ? `${slug.toLowerCase()}/` : ''}${hash}`
}
if (target === 'docs/design.md') return `${base}/design/${hash}`
@@ -37,7 +40,8 @@ export function remarkLinks({ base }) {
return (tree, file) => {
const page = relative(content, file.path).replace(/(^|\/)index\.md$/, '$1README.md')
walk(tree, (node) => {
if (node.type === 'link' || node.type === 'definition') node.url = resolve(page, node.url, base)
if (node.type === 'link' || node.type === 'definition')
node.url = resolve(page, node.url, base)
})
}
}
docs-site/reference.mjs+86−18
@@ -26,9 +26,16 @@ const lexicons = new Map(
const METHOD = { query: 'GET', procedure: 'POST', subscription: 'WS' }
const text = (node) =>
node.type === 'text' || node.type === 'inlineCode' ? node.value : (node.children ?? []).map(text).join('')
node.type === 'text' || node.type === 'inlineCode'
? node.value
: (node.children ?? []).map(text).join('')
const escape = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
const escape = (s) =>
String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
/** `GET /refs`, `POST dev.knotless.x`, `dev.knotless.x?q=`: its method, and the NSID it names, if any. */
function route(code) {
@@ -52,7 +59,8 @@ function type(field) {
break
case 'integer':
bits.push('integer')
if (field.minimum !== undefined && field.maximum !== undefined) bits.push(`${field.minimum} to ${field.maximum}`)
if (field.minimum !== undefined && field.maximum !== undefined)
bits.push(`${field.minimum} to ${field.maximum}`)
break
case 'array':
bits.push(`array of ${type(field.items)}`)
@@ -62,7 +70,9 @@ function type(field) {
bits.push(field.ref.replace(/^#/, '').replace(/^dev\.knotless\./, ''))
break
case 'union':
bits.push(`one of ${field.refs.map((r) => r.replace(/^#/, '').replace(/^dev\.knotless\./, '')).join(', ')}`)
bits.push(
`one of ${field.refs.map((r) => r.replace(/^#/, '').replace(/^dev\.knotless\./, '')).join(', ')}`,
)
break
default:
bits.push(field.type)
@@ -99,7 +109,9 @@ function lexicon(nsid) {
fields('Parameters', main.parameters) +
fields('Input', main.input?.schema) +
fields('Output', main.output?.schema) +
(main.errors?.length ? `<p class="lex-title">Errors</p><p>${main.errors.map((e) => `<code>${escape(e.name)}</code>`).join(', ')}</p>` : '')
(main.errors?.length
? `<p class="lex-title">Errors</p><p>${main.errors.map((e) => `<code>${escape(e.name)}</code>`).join(', ')}</p>`
: '')
const source = `${REPO}/blob/main/lexicons/dev/knotless/${nsid.split('.').pop()}.json`
return (
`<details class="lexicon"><summary>Lexicon</summary>` +
@@ -110,12 +122,20 @@ function lexicon(nsid) {
}
/** A heading's anchor as GitHub, and harness/docs.mjs, make it. */
const slug = (s) => s.trim().toLowerCase().replace(/[^\p{L}\p{N}_\- ]/gu, '').replaceAll(' ', '-')
const slug = (s) =>
s
.trim()
.toLowerCase()
.replace(/[^\p{L}\p{N}_\- ]/gu, '')
.replaceAll(' ', '-')
const badge = (method) =>
method && {
type: 'methodBadge',
data: { hName: 'span', hProperties: { className: ['method', `method-${method.toLowerCase()}`] } },
data: {
hName: 'span',
hProperties: { className: ['method', `method-${method.toLowerCase()}`] },
},
children: [{ type: 'text', value: method }],
}
const gap = { type: 'text', value: ' ' }
@@ -123,7 +143,10 @@ const gap = { type: 'text', value: ' ' }
/** A command or route as code that breaks only between its words: a browser would break inside `--flag` after its hyphens. */
const signature = (code) => ({
type: 'html',
value: `<code>${code.split(/(\s+)/).map((w) => (/\s/.test(w) ? w : `<span class="word">${escape(w)}</span>`)).join('')}</code>`,
value: `<code>${code
.split(/(\s+)/)
.map((w) => (/\s/.test(w) ? w : `<span class="word">${escape(w)}</span>`))
.join('')}</code>`,
})
/** A cell's code, if the cell starts with some. */
@@ -148,19 +171,49 @@ function asList(table) {
const { method, nsid } = route(lead(first))
const [code, ...others] = first.children
const shown = [signature(method ? code.value.replace(/^[A-Z]+\s+/, '') : code.value), ...others]
const term = { type: 'refTerm', data: { hName: 'dt' }, children: method ? [badge(method), gap, ...shown] : shown }
const term = {
type: 'refTerm',
data: { hName: 'dt' },
children: method ? [badge(method), gap, ...shown] : shown,
}
const middle = rest.slice(0, -1).flatMap((cell, i) =>
text(cell).trim()
? [{ type: 'refMeta', data: { hName: 'p', hProperties: { className: ['ref-meta'] } }, children: [{ type: 'html', value: `<span class="ref-label">${escape(heads[i + 1])}</span> ` }, ...cell.children] }]
? [
{
type: 'refMeta',
data: { hName: 'p', hProperties: { className: ['ref-meta'] } },
children: [
{
type: 'html',
value: `<span class="ref-label">${escape(heads[i + 1])}</span> `,
},
...cell.children,
],
},
]
: [],
)
// The last column is the description; labelled too when others are, so each says what it is.
const label = rest.length > 1 && heads.at(-1) ? [{ type: 'html', value: `<span class="ref-label">${escape(heads.at(-1))}</span> ` }] : []
const description = { type: 'paragraph', children: [...label, ...(rest.at(-1)?.children ?? [])] }
const detail = { type: 'refDetail', data: { hName: 'dd' }, children: [...middle, description, ...(nsid ? [{ type: 'html', value: lexicon(nsid) }] : [])] }
const label =
rest.length > 1 && heads.at(-1)
? [{ type: 'html', value: `<span class="ref-label">${escape(heads.at(-1))}</span> ` }]
: []
const description = {
type: 'paragraph',
children: [...label, ...(rest.at(-1)?.children ?? [])],
}
const detail = {
type: 'refDetail',
data: { hName: 'dd' },
children: [...middle, description, ...(nsid ? [{ type: 'html', value: lexicon(nsid) }] : [])],
}
return [term, detail]
})
return { type: 'refList', data: { hName: 'dl', hProperties: { className: ['ref-list'] } }, children: items }
return {
type: 'refList',
data: { hName: 'dl', hProperties: { className: ['ref-list'] } },
children: items,
}
}
export function remarkReference() {
@@ -168,18 +221,33 @@ export function remarkReference() {
const visit = (parent) => {
parent.children?.forEach((node, i) => {
if (node.type === 'table' && definitional(node)) parent.children[i] = asList(node)
else if (node.type === 'heading' && node.children.length === 1 && node.children[0].type === 'inlineCode') {
else if (
node.type === 'heading' &&
node.children.length === 1 &&
node.children[0].type === 'inlineCode'
) {
// `### \`GET /healthz\``, `### \`dev.knotless.listRepos\``: the method a badge before it. The
// anchor is fixed first, from the heading as written, so links to it still land.
const code = node.children[0]
const { method, nsid } = route(code.value)
if (!method) return
node.data = { ...node.data, hProperties: { ...node.data?.hProperties, id: slug(code.value) } }
node.children = [badge(method), gap, { ...code, value: code.value.replace(/^[A-Z]+\s+/, '') }]
node.data = {
...node.data,
hProperties: { ...node.data?.hProperties, id: slug(code.value) },
}
node.children = [
badge(method),
gap,
{ ...code, value: code.value.replace(/^[A-Z]+\s+/, '') },
]
if (nsid) {
// At the end of its section, after what is said of it.
let end = i + 1
while (end < parent.children.length && !(parent.children[end].type === 'heading' && parent.children[end].depth <= node.depth)) end++
while (
end < parent.children.length &&
!(parent.children[end].type === 'heading' && parent.children[end].depth <= node.depth)
)
end++
parent.children.splice(end, 0, { type: 'html', value: lexicon(nsid) })
}
} else visit(node)
harness/e2e/flows.spec.mjs+349−96
445 lines changed: not loaded until asked for.
harness/e2e/pages.mjs+17−5
harness/e2e/private.spec.mjs+87−24
harness/e2e/setup.mjs+24−3
harness/shots/compare.mjs+41−13
harness/shots/fixtures.mjs+176−36
harness/shots/look.mjs+69−21
harness/shots/outputs.mjs+15−3
harness/shots/playwright.config.mjs+18−3
harness/shots/setup.mjs+15−4
harness/shots/views.spec.mjs+60−7
harness/shots/world.mjs+604−128
732 lines changed: not loaded until asked for.
harness/answers.mjs+18−5
harness/authorize.mjs+6−3
harness/changed.mjs+32−9
harness/contrast.mjs+9−4
harness/cov.mjs+196−59
harness/docs.mjs+12−4
harness/files-bench.mjs+29−8
harness/layers.mjs+6−2
harness/lexicons.mjs+72−29
harness/package.json+1−1
harness/pds.mjs+39−24
harness/plans.mjs+7−2
harness/quarantine.json+20−5
harness/queue-stats.mjs+107−48
harness/scenarios.mjs+39−23
harness/star-fixture.mjs+17−3
web/src/lib/atoms/ArchivedTag.svelte+3−1
web/src/lib/atoms/Avatar.svelte+19−2
web/src/lib/atoms/Bars.svelte+4−1
web/src/lib/atoms/HalfTag.svelte+3−1
web/src/lib/atoms/Label.svelte+3−1
web/src/lib/atoms/Meta.svelte+17−2
web/src/lib/atoms/More.svelte+5−1
web/src/lib/atoms/Pin.svelte+19−6
web/src/lib/atoms/PrivateTag.svelte+2−1
web/src/lib/atoms/ReactionPill.svelte+21−2
web/src/lib/atoms/Said.svelte+5−1
web/src/lib/atoms/Sha.svelte+3−1
web/src/lib/atoms/Spark.svelte+6−2
web/src/lib/molecules/ActorMenu.svelte+15−3
web/src/lib/molecules/AddMenu.svelte+23−4
web/src/lib/molecules/AssigneesFact.svelte+40−5
web/src/lib/molecules/BranchMenu.svelte+56−8
web/src/lib/molecules/CheckMark.svelte+14−2
web/src/lib/molecules/CloneMenu.svelte+9−3
web/src/lib/molecules/Command.svelte+4−1
web/src/lib/molecules/CommitAuthor.svelte+16−2
web/src/lib/molecules/CommitList.svelte+7−1
web/src/lib/molecules/Committed.svelte+20−3
web/src/lib/molecules/Crowd.svelte+16−2
web/src/lib/molecules/Crumbs.svelte+7−1
web/src/lib/molecules/Delegate.svelte+14−2
web/src/lib/molecules/Dialog.svelte+31−3
web/src/lib/molecules/FeedItem.svelte+117−19
web/src/lib/molecules/FileViews.svelte+7−1
web/src/lib/molecules/FilterMenu.svelte+16−2
web/src/lib/molecules/FollowButton.svelte+18−3
web/src/lib/molecules/ForkButton.svelte+34−5
web/src/lib/molecules/HandleInput.svelte+44−5
web/src/lib/molecules/ItemRow.svelte+21−3
web/src/lib/molecules/LabelPicker.svelte+29−3
web/src/lib/molecules/Labels.svelte+3−1
web/src/lib/molecules/LangEdge.svelte+17−3
web/src/lib/molecules/LineQuote.svelte+5−1
web/src/lib/molecules/LinkRows.svelte+27−5
web/src/lib/molecules/MarkdownField.svelte+60−11
web/src/lib/molecules/MentionList.svelte+25−2
web/src/lib/molecules/MinutesNote.svelte+3−1
web/src/lib/molecules/NameCheck.svelte+15−3
web/src/lib/molecules/NotificationItem.svelte+92−13
web/src/lib/molecules/PullTabs.svelte+27−4
web/src/lib/molecules/ReactionBar.svelte+40−6
web/src/lib/molecules/RefCounts.svelte+16−3
web/src/lib/molecules/ReleaseName.svelte+12−3
web/src/lib/molecules/RepoLinks.svelte+3−1
web/src/lib/molecules/RepoMenu.svelte+3−1
web/src/lib/molecules/RepoPeople.svelte+14−2
web/src/lib/molecules/RepoSite.svelte+14−3
web/src/lib/molecules/RepoSwitch.svelte+22−4
web/src/lib/molecules/RepoTopics.svelte+3−1
web/src/lib/molecules/RerunMenu.svelte+29−5
web/src/lib/molecules/ReviewersFact.svelte+88−12
web/src/lib/molecules/RunImages.svelte+6−1
web/src/lib/molecules/SiteLink.svelte+3−1
web/src/lib/molecules/StarButton.svelte+16−3
web/src/lib/molecules/SubscribeButton.svelte+26−5
web/src/lib/molecules/TabLink.svelte+10−3
web/src/lib/molecules/ThemeCard.svelte+22−5
web/src/lib/molecules/Verdict.svelte+13−2
web/src/lib/molecules/WatchMenu.svelte+22−3
web/src/lib/organisms/ActivityRollup.svelte+30−7
web/src/lib/organisms/ActivityStrip.svelte+12−3
web/src/lib/organisms/ActorList.svelte+6−1
web/src/lib/organisms/AgentChat.svelte+24−6
web/src/lib/organisms/AskedFor.svelte+6−1
web/src/lib/organisms/BlameView.svelte+35−6
web/src/lib/organisms/BlockList.svelte+43−9
web/src/lib/organisms/CheckList.svelte+31−5
web/src/lib/organisms/CodeResults.svelte+32−5
web/src/lib/organisms/CodeView.svelte+22−3
web/src/lib/organisms/Comment.svelte+30−3
web/src/lib/organisms/CommitEvent.svelte+8−1
web/src/lib/organisms/Composer.svelte+13−3
web/src/lib/organisms/DangerZone.svelte+88−14
web/src/lib/organisms/DeleteData.svelte+57−13
web/src/lib/organisms/Diff.svelte+5−1
web/src/lib/organisms/DiffFile.svelte+62−9
web/src/lib/organisms/EjectedList.svelte+7−1
web/src/lib/organisms/EmailSettings.svelte+33−7
web/src/lib/organisms/Feed.svelte+11−3
web/src/lib/organisms/FileFinder.svelte+68−11
web/src/lib/organisms/FileList.svelte+63−10
web/src/lib/organisms/FileTree.svelte+21−3
web/src/lib/organisms/GrantNote.svelte+6−2
web/src/lib/organisms/ItemFacts.svelte+14−2
web/src/lib/organisms/ItemList.svelte+52−8
web/src/lib/organisms/LandedList.svelte+8−2
web/src/lib/organisms/LineComposer.svelte+38−5
web/src/lib/organisms/LineThread.svelte+29−5
web/src/lib/organisms/LoginDialog.svelte+50−7
web/src/lib/organisms/MemberList.svelte+25−3
web/src/lib/organisms/MergeBox.svelte+336−105
441 lines changed: not loaded until asked for.
web/src/lib/organisms/MergeChecks.svelte+60−9
web/src/lib/organisms/NewRepoForm.svelte+58−7
web/src/lib/organisms/NotificationList.svelte+10−2
web/src/lib/organisms/NotificationSettings.svelte+51−10
web/src/lib/organisms/OpenFacts.svelte+22−4
web/src/lib/organisms/OpenForm.svelte+54−5
web/src/lib/organisms/OrgDangerZone.svelte+35−15
web/src/lib/organisms/OrgMembers.svelte+229−37
web/src/lib/organisms/OwnerCards.svelte+10−1
web/src/lib/organisms/ProfileCard.svelte+38−7
web/src/lib/organisms/ProfileForm.svelte+63−14
web/src/lib/organisms/ProfileRepos.svelte+61−14
web/src/lib/organisms/PullDiff.svelte+34−4
web/src/lib/organisms/PullStack.svelte+16−5
web/src/lib/organisms/QueueLine.svelte+38−7
web/src/lib/organisms/RefList.svelte+30−3
web/src/lib/organisms/ReleaseCard.svelte+17−4
web/src/lib/organisms/ReleaseList.svelte+30−4
web/src/lib/organisms/RepoCards.svelte+15−2
web/src/lib/organisms/RepoFacts.svelte+15−3
web/src/lib/organisms/RepoHeader.svelte+83−12
web/src/lib/organisms/RepoList.svelte+28−3
web/src/lib/organisms/ReportDialog.svelte+6−1
web/src/lib/organisms/ReportList.svelte+6−3
web/src/lib/organisms/Reviewers.svelte+88−13
web/src/lib/organisms/ReviewMenu.svelte+34−4
web/src/lib/organisms/RunList.svelte+39−9
web/src/lib/organisms/RunnerList.svelte+41−7
web/src/lib/organisms/RunnerMinutes.svelte+13−2
web/src/lib/organisms/RunWorkflow.svelte+49−11
web/src/lib/organisms/SearchPalette.svelte+96−17
web/src/lib/organisms/SecretList.svelte+118−16
web/src/lib/organisms/SettingsAccess.svelte+167−21
web/src/lib/organisms/SettingsBranches.svelte+265−33
web/src/lib/organisms/SettingsGeneral.svelte+82−10
web/src/lib/organisms/SettingsLabels.svelte+84−13
web/src/lib/organisms/SettingsStorage.svelte+14−4
web/src/lib/organisms/SiteFooter.svelte+3−1
web/src/lib/organisms/SiteHeader.svelte+44−11
web/src/lib/organisms/StackScope.svelte+11−3
web/src/lib/organisms/StartOrgDialog.svelte+30−8
web/src/lib/organisms/StepList.svelte+10−2
web/src/lib/organisms/StepSheet.svelte+38−10
web/src/lib/organisms/TakedownForm.svelte+45−8
web/src/lib/organisms/ThemeBrowser.svelte+42−6
web/src/lib/organisms/ThemeEditor.svelte+121−25
web/src/lib/organisms/ThemeSettings.svelte+220−59
web/src/lib/organisms/Toolbar.svelte+27−2
web/src/lib/organisms/TransferOffer.svelte+9−3
web/src/lib/organisms/UsageDays.svelte+27−4
web/src/lib/organisms/WriteSettings.svelte+4−1
web/src/lib/templates/Listing.svelte+44−4
web/src/lib/templates/ProfilePage.svelte+46−6
web/src/lib/templates/RepoPage.svelte+43−5
web/src/lib/templates/RepoSettings.svelte+23−5
web/src/lib/templates/SettingsPage.svelte+40−7
web/src/lib/activity.ts+35−8
web/src/lib/badges.ts+62−15
web/src/lib/contrast.json+25−5
web/src/lib/contrast.ts+18−6
web/src/lib/data.ts+48−11
web/src/lib/diffs.ts+7−2
web/src/lib/feed.ts+23−4
web/src/lib/format.ts+75−17
web/src/lib/highlight.ts+14−3
web/src/lib/icons.ts+149−3
web/src/lib/live.ts+3−2
web/src/lib/menus.ts+6−2
web/src/lib/notifications.svelte.ts+39−10
web/src/lib/pages.svelte.ts+10−2
web/src/lib/preferences.ts+10−4
web/src/lib/releases.ts+18−4
web/src/lib/sealed.ts+18−5
web/src/lib/steps.ts+8−4
web/src/lib/tasks.ts+20−2
web/src/lib/theme.ts+19−7
web/src/lib/typeahead.ts+3−1
web/src/routes/[authority]/[repo]/[kind=item]/[did]/[rkey]/files/+page.svelte+123−21
web/src/routes/[authority]/[repo]/[kind=item]/[did]/[rkey]/files/+page.ts+5−1
web/src/routes/[authority]/[repo]/[kind=item]/[did]/[rkey]/+page.svelte+391−64
455 lines changed: not loaded until asked for.
web/src/routes/[authority]/[repo]/[kind=item]/[did]/[rkey]/+page.ts+4−1
web/src/routes/[authority]/[repo]/blame/[...rest]/+page.svelte+30−5
web/src/routes/[authority]/[repo]/blame/[...rest]/+page.ts+5−1
web/src/routes/[authority]/[repo]/blob/[...rest]/+page.svelte+21−4
web/src/routes/[authority]/[repo]/blob/[...rest]/+page.ts+5−1
web/src/routes/[authority]/[repo]/branches/+page.svelte+8−1
web/src/routes/[authority]/[repo]/branches/+page.ts+5−1
web/src/routes/[authority]/[repo]/check/[did]/[rkey]/+page.svelte+71−12
web/src/routes/[authority]/[repo]/check/[did]/[rkey]/+page.ts+3−1
web/src/routes/[authority]/[repo]/checks/+page.svelte+38−6
web/src/routes/[authority]/[repo]/checks/+page.ts+5−1
web/src/routes/[authority]/[repo]/commit/[rev]/+page.svelte+11−2
web/src/routes/[authority]/[repo]/commit/[rev]/+page.ts+3−1
web/src/routes/[authority]/[repo]/commits/[...rest]/+page.svelte+21−3
web/src/routes/[authority]/[repo]/commits/[...rest]/+page.ts+9−1
web/src/routes/[authority]/[repo]/issues/new/+page.svelte+14−2
web/src/routes/[authority]/[repo]/issues/new/+page.ts+5−1
web/src/routes/[authority]/[repo]/issues/+page.svelte+8−1
web/src/routes/[authority]/[repo]/issues/+page.ts+5−1
web/src/routes/[authority]/[repo]/pulls/new/+page.svelte+84−16
web/src/routes/[authority]/[repo]/pulls/new/+page.ts+20−3
web/src/routes/[authority]/[repo]/pulls/+page.svelte+9−1
web/src/routes/[authority]/[repo]/pulls/+page.ts+5−1
web/src/routes/[authority]/[repo]/queue/+page.svelte+6−1
web/src/routes/[authority]/[repo]/releases/+page.svelte+7−1
web/src/routes/[authority]/[repo]/releases/+page.ts+5−1
web/src/routes/[authority]/[repo]/settings/access/+page.svelte+9−1
web/src/routes/[authority]/[repo]/settings/access/+page.ts+4−1
web/src/routes/[authority]/[repo]/settings/actions/+page.svelte+11−2
web/src/routes/[authority]/[repo]/settings/actions/+page.ts+17−3
web/src/routes/[authority]/[repo]/settings/branches/+page.svelte+8−1
web/src/routes/[authority]/[repo]/settings/branches/+page.ts+4−1
web/src/routes/[authority]/[repo]/settings/labels/+page.ts+4−1
web/src/routes/[authority]/[repo]/settings/+page.svelte+16−2
web/src/routes/[authority]/[repo]/settings/+page.ts+7−2
web/src/routes/[authority]/[repo]/tags/+page.svelte+8−1
web/src/routes/[authority]/[repo]/tags/+page.ts+4−1
web/src/routes/[authority]/[repo]/tree/[...rest]/+page.svelte+8−1
web/src/routes/[authority]/[repo]/tree/[...rest]/+page.ts+6−1
web/src/routes/[authority]/[repo]/+page.svelte+13−2
web/src/routes/[authority]/[repo]/+page.ts+6−1
web/src/routes/[authority]/+page.svelte+56−11
web/src/routes/[authority]/+page.ts+40−6
web/src/routes/admin/+page.svelte+9−2
web/src/routes/admin/+page.ts+8−1
web/src/routes/guidelines/+page.svelte+79−17
web/src/routes/new/+page.svelte+12−3
web/src/routes/notifications/+page.svelte+19−4
web/src/routes/notifications/+page.ts+6−1
web/src/routes/privacy/+page.svelte+131−23
web/src/routes/search/+page.svelte+93−22
web/src/routes/search/+page.ts+34−7
web/src/routes/settings/appearance/+page.svelte+9−1
web/src/routes/settings/appearance/+page.ts+23−35
web/src/routes/settings/notifications/+page.ts+6−1
web/src/routes/settings/organization/+page.svelte+3−1
web/src/routes/settings/+layout.svelte+22−5
web/src/routes/settings/+layout.ts+6−1
web/src/routes/start/+page.svelte+28−7
web/src/routes/stats/+page.svelte+47−10
web/src/routes/steps/[authority]/[repo]/[step]/+page.svelte+17−4
web/src/routes/steps/+page.svelte+28−6
web/src/routes/terms/+page.svelte+126−24
web/src/routes/+error.svelte+5−1
web/src/routes/+layout.ts+8−2
web/src/routes/+page.svelte+25−5
web/src/routes/+page.ts+4−1
web/src/app.css+7616−1525
A large diff, not shown.
web/src/env.ts+2−1
web/src/hooks.server.ts+19−3
web/src/tokens.css+9−3
web/package-lock.json+414−0
414 lines changed: not loaded until asked for.
web/package.json+5−4
web/vite.config.ts+9−1
.oxfmtrc.jsonnew+27−0
AGENTS.md+4−2
Justfile+10−0
README.md+2−1