~ 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

Videos of a pull's way from push to deploy, and of the pages in use

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:marketing/video
marketing/video/change/sample.jsonnew+310−0
@@ -0,0 +1,310 @@
{
"host": "knotless.dev",
"repo": "chadtmiller.com/knotless",
"pull": {
"uri": "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/dev.knotless.pull/3mzth2v6xpbk7",
"url": "https://knotless.dev/chadtmiller.com/knotless/pull/did:plc:bcgltzqazw5tb6k2g3ttenbj/3mzth2v6xpbk7",
"title": "A profile's private repositories show their pushes to those who may read them",
"author": "chadtmiller.com",
"branch": "serve/private-activity",
"base": "main",
"opened": "2026-11-08T22:20:45.478Z",
"state": "merged"
},
"diff": {
"files": [
{
"path": "crates/knotless-serve/src/lib.rs",
"added": 4,
"removed": 4
},
{
"path": "crates/knotless-serve/src/web/actor.rs",
"added": 30,
"removed": 3
},
{
"path": "crates/knotless-serve/tests/gateway.rs",
"added": 16,
"removed": 9
},
{
"path": "docs/design/access.md",
"added": 1,
"removed": 1
},
{
"path": "docs/reference/api.md",
"added": 5,
"removed": 1
},
{
"path": "lexicons/dev/knotless/getActorActivity.json",
"added": 1,
"removed": 1
}
],
"excerpt": {
"path": "crates/knotless-serve/src/web/actor.rs",
"lines": [
{
"mark": " ",
"n": 373,
"text": "}"
},
{
"mark": " ",
"n": 374,
"text": ""
},
{
"mark": "+",
"n": 375,
"text": "/// The pushes to `did`'s private repositories that `viewer` may read"
},
{
"mark": "+",
"n": 376,
"text": "/// ([`private_of`]), as this server's index of their spaces counts them."
},
{
"mark": "+",
"n": 377,
"text": "fn private_pushes("
},
{
"mark": "+",
"n": 378,
"text": " app: &Arc<App>,"
},
{
"mark": "+",
"n": 379,
"text": " did: &str,"
},
{
"mark": "+",
"n": 380,
"text": " viewer: Option<String>,"
},
{
"mark": "+",
"n": 381,
"text": " since: Option<&str>,"
},
{
"mark": "+",
"n": 382,
"text": " tz: &str,"
},
{
"mark": "+",
"n": 383,
"text": ") -> rusqlite::Result<Vec<crate::index::RepoPushes>> {"
},
{
"mark": "+",
"n": 384,
"text": " let shown = private_of(app, did, viewer).unwrap_or_default();"
},
{
"mark": "+",
"n": 385,
"text": " let names: HashSet<String> = shown.into_iter().map(|(name, _, _)| name).collect();"
},
{
"mark": "+",
"n": 386,
"text": " let Some(index) = app.spaces.as_ref().filter(|_| !names.is_empty()) else {"
}
]
}
},
"pushes": [
{
"commit": "65f5493b372769d9b5c4b6d07d4038af49109cda",
"at": "2026-11-08T22:20:52.622Z",
"checks": [
{
"workflow": "docs",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:20:52.622Z",
"finished": "2026-11-08T22:21:08.682Z"
},
{
"workflow": "check",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:20:52.941Z",
"finished": "2026-11-08T22:21:15.894Z"
},
{
"workflow": "web",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:20:52.931Z",
"finished": "2026-11-08T22:21:15.955Z"
},
{
"workflow": "lexicons",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:21:14.293Z",
"finished": "2026-11-08T22:21:23.684Z"
},
{
"workflow": "ios",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:21:13.329Z",
"finished": "2026-11-08T22:22:16.371Z"
},
{
"workflow": "shots",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:21:22.499Z",
"finished": "2026-11-08T22:22:16.685Z"
},
{
"workflow": "e2e",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:20:52.623Z",
"finished": "2026-11-08T22:25:49.817Z"
},
{
"workflow": "test",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:22:13.073Z",
"finished": "2026-11-08T22:29:08.956Z"
}
]
},
{
"commit": "d5a939294d78ef674a3ed1f988b34129feea48b3",
"at": "2026-11-08T22:29:14.102Z",
"checks": [
{
"workflow": "docs",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:14.102Z",
"finished": "2026-11-08T22:29:25.385Z"
},
{
"workflow": "check",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:14.417Z",
"finished": "2026-11-08T22:29:33.042Z"
},
{
"workflow": "lexicons",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:31.445Z",
"finished": "2026-11-08T22:29:35.737Z"
},
{
"workflow": "ios",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:23.795Z",
"finished": "2026-11-08T22:30:05.990Z"
},
{
"workflow": "shots",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:35.268Z",
"finished": "2026-11-08T22:30:12.063Z"
},
{
"workflow": "web",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:30:10.093Z",
"finished": "2026-11-08T22:30:38.109Z"
},
{
"workflow": "e2e",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:29:14.102Z",
"finished": "2026-11-08T22:33:25.681Z"
},
{
"workflow": "test",
"status": "success",
"skipped": false,
"started": "2026-11-08T22:30:03.015Z",
"finished": "2026-11-08T22:37:10.101Z"
}
]
}
],
"reviews": [
{
"by": "review.knotless.dev",
"verdict": "changes",
"at": "2026-11-08T22:22:05.245Z",
"commit": "65f5493b372769d9b5c4b6d07d4038af49109cda",
"said": "The activity change is sound. getActorActivity now sits behind the viewer middleware and adds only the private repositories private_of allows. The new test covers the member, no one, and an outsider.",
"line": {
"path": ".knotless/workflows/deploy.yml",
"line": 104,
"said": "Dropping chadtmiller.com/group-host here depends on an invite that was never left for the existing share."
}
},
{
"by": "review.knotless.dev",
"verdict": "approve",
"at": "2026-11-08T22:29:30.940Z",
"commit": "d5a939294d78ef674a3ed1f988b34129feea48b3",
"said": "The unrelated commit is gone. pull_diff now shows only the activity change, with the matching docs and lexicon text, so the title and body describe the pull.",
"line": null
}
],
"queued": "2026-11-08T22:37:24.182Z",
"landed": {
"commit": "9382c0bbb9f6ad694a8a39deaffc2ebc706c6ac5",
"at": "2026-11-08T22:37:31Z",
"batch": true
},
"deploy": {
"workflow": "deploy",
"status": "success",
"started": "2026-11-08T22:38:12.898Z",
"finished": "2026-11-08T22:46:23.896Z",
"steps": [
{
"name": "tools",
"ms": 1139,
"status": "success"
},
{
"name": "build",
"ms": 433042,
"status": "success"
},
{
"name": "deploy",
"ms": 40955,
"status": "success"
},
{
"name": "image",
"ms": 4894,
"status": "success"
},
{
"name": "runner host",
"ms": 6291,
"status": "success"
}
]
}
}
marketing/video/change/story.mjsnew+276−0
@@ -0,0 +1,276 @@
// A pull's way from its first push to the deploy that shipped it, read
// from the appview through `knotless-mcp --read-only`, as the Change video
// tells it: each push and its checks, each review, the queue, the commit it
// landed as and the deploy of that commit. Written to out/stories/<rkey>.json.
//
// node change/story.mjs https://knotless.dev/<handle>/<name>/pull/<did>/<rkey>
// node change/story.mjs <handle>/<name> <pull at-uri> [--deploy <workflow>]
//
// Nothing here is made up: a stage the pull never reached (unreviewed, merged
// by hand, never deployed) is left out, and the video skips it.
import { spawn } from 'node:child_process';
import { mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const host = new URL(process.env.KNOTLESS_APPVIEW || 'https://knotless.dev').host;
const args = process.argv.slice(2);
const flag = (name, otherwise) => {
const i = args.indexOf(name);
if (i < 0) return otherwise;
const [, value] = args.splice(i, 2);
return value;
};
const deployWorkflow = flag('--deploy', 'deploy');
const [repo, uri] = (() => {
if (args[0]?.startsWith('http')) {
const [handle, name, kind, did, rkey] = new URL(args[0]).pathname.split('/').filter(Boolean);
if (kind !== 'pull') throw new Error(`not a pull's page: ${args[0]}`);
return [`${handle}/${name}`, `at://${did}/dev.knotless.pull/${rkey}`];
}
if (args.length !== 2) {
console.error('usage: story.mjs <pull page url> | <handle>/<name> <pull at-uri>');
process.exit(2);
}
return args;
})();
/// The tools of a read-only knotless-mcp, called in turn over its stdio.
const mcp = () => {
const child = spawn('knotless-mcp', ['--read-only'], { stdio: ['pipe', 'pipe', 'inherit'] });
const waiting = new Map();
let id = 0;
let buf = '';
child.stdout.on('data', (d) => {
buf += d;
for (let i = buf.indexOf('\n'); i >= 0; i = buf.indexOf('\n')) {
const line = buf.slice(0, i);
buf = buf.slice(i + 1);
if (!line.trim()) continue;
const m = JSON.parse(line);
waiting.get(m.id)?.(m);
waiting.delete(m.id);
}
});
const ask = (method, params) =>
new Promise((resolve, reject) => {
const n = ++id;
waiting.set(n, (m) => (m.error ? reject(new Error(m.error.message)) : resolve(m.result)));
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: n, method, params })}\n`);
});
const ready = ask('initialize', {
protocolVersion: '2025-06-18',
capabilities: {},
clientInfo: { name: 'knotless-video', version: '0' }
}).then(() =>
child.stdin.write(
`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`
)
);
return {
/// The tool's text, as JSON when `json`.
call: async (name, args, json = true) => {
await ready;
const r = await ask('tools/call', { name, arguments: args });
const text = r.content.find((c) => c.type === 'text')?.text ?? '';
if (r.isError) throw new Error(`${name}: ${text}`);
return json ? JSON.parse(text) : text;
},
close: () => child.kill()
};
};
/// The diff's files, each with its lines, from pull_diff's numbered form:
/// `old new | ±text` under each `diff --git` and `@@` head.
const parseDiff = (text) => {
const files = [];
let file;
let hunk;
for (const line of text.split('\n')) {
if (line.startsWith('diff --git ')) {
file = { path: line.split(' b/').pop(), added: 0, removed: 0, hunks: [] };
files.push(file);
continue;
}
if (!file) continue;
if (line.startsWith('@@')) {
hunk = { head: line, lines: [] };
file.hunks.push(hunk);
continue;
}
const m = /^\s*(\d*)\s+(\d*) \| ([ +-])(.*)$/.exec(line);
if (!m || !hunk) continue;
const [, old, now, mark, body] = m;
if (mark === '+') file.added++;
if (mark === '-') file.removed++;
hunk.lines.push({ mark, n: Number(now || old), text: body });
}
return files;
};
// What a reader of the video learns least from: words about the change,
// rather than the change, and then its tests.
const prose = /(^docs\/|\.md$|^\.changeset\/|CHANGELOG|\.lock$|package-lock\.json$|fixtures)/;
const tests = /(\/tests?\/|_test\.|\.test\.|\.spec\.)/;
/// The stretch of the diff that says most: of the code (not its tests, if
/// it has any other), the hunk adding the most lines, cut to `most` lines
/// from just before its first change.
const excerpt = (files, most = 14) => {
const code = files.filter((f) => !prose.test(f.path));
const source = code.filter((f) => !tests.test(f.path));
const pool = source.length ? source : code.length ? code : files;
const pick = pool
.flatMap((f) => f.hunks.map((h) => ({ path: f.path, h })))
.map((x) => ({ ...x, added: x.h.lines.filter((l) => l.mark === '+').length }))
.sort((a, b) => b.added - a.added)[0];
if (!pick) return null;
const lines = pick.h.lines;
const first = lines.findIndex((l) => l.mark !== ' ');
const start = Math.max(0, Math.min(first - 2, lines.length - most));
return {
path: pick.path,
lines: lines.slice(start, start + most).map((l) => ({
...l,
text: l.text.replace(/\t/g, ' ').trimEnd()
}))
};
};
/// A body's opening sentences, as many whole ones as fit in `most`
/// characters (the first cut short if it alone does not), as plain text.
const firstSaid = (body, most = 200) => {
const plain = body
.replace(/```[\s\S]*?```/g, '')
.replace(/\*\*|`|^#+\s*|^>\s*/gm, '')
.replace(/\s+/g, ' ')
.trim();
const sentences = plain.split(/(?<=[.!?])\s+/);
let said = '';
for (const s of sentences) {
if (`${said} ${s}`.trim().length > most) break;
said = `${said} ${s}`.trim();
}
if (said) return said;
return `${plain.slice(0, most - 1).trimEnd()}…`;
};
const at = (t) => new Date(t).getTime();
const tools = mcp();
try {
const shown = await tools.call('pull_show', { repo, uri });
const pull = shown.item;
const files = parseDiff(await tools.call('pull_diff', { repo, uri }, false));
const runs = await tools.call('run_list', { repo, pull: uri }).catch(() => []);
// A push is the commit the pull's checks ran at, first seen when the first
// of them started; its checks are the latest of each workflow there.
const byCommit = new Map();
for (const r of runs) {
const list = byCommit.get(r.commit) ?? [];
if (!list.some((s) => s.workflow === r.workflow)) list.push(r);
byCommit.set(r.commit, list);
}
const pushes = [...byCommit.entries()]
.map(([commit, checks]) => ({
commit,
at: checks.map((c) => c.startedAt).sort()[0],
checks: checks
.map((c) => ({
workflow: c.workflow,
status: c.status,
skipped: Boolean(c.skipped),
started: c.startedAt,
finished: c.finishedAt ?? null
}))
.sort((a, b) => at(a.finished ?? a.started) - at(b.finished ?? b.started))
}))
.sort((a, b) => at(a.at) - at(b.at));
// Each review, with the first of the line comments it left.
const reviews = shown.comments
.filter((c) => c.verdict)
.map((c) => {
const line = shown.comments.find((l) => l.review === c.uri && l.line);
return {
by: c.handle,
verdict: c.verdict,
at: c.createdAt,
commit: c.commit,
said: firstSaid(c.body),
line: line
? { path: line.line.path, line: line.line.line, said: firstSaid(line.body, 140) }
: null
};
});
// Landed: the commit on the base the merge made, dated when it was made.
const merged = pull.merged ?? null;
const landedCommit = merged && shown.compare?.commits?.find((c) => c.sha === merged);
const landed = merged
? {
commit: merged,
at: landedCommit?.committer?.date ?? null,
batch: Boolean(pull.mergedBatch)
}
: null;
// Deployed: the deploy workflow's run at the landed commit, on the base.
let deploy = null;
if (merged) {
const onBase = await tools
.call('run_list', { repo, ref: pull.base, commit: merged, workflow: deployWorkflow })
.catch(() => []);
const run = onBase[0];
if (run)
deploy = {
workflow: run.workflow,
status: run.status,
started: run.startedAt,
finished: run.finishedAt ?? null,
steps: run.steps
.filter((s) => s.durationMs >= 1000)
.map((s) => ({ name: s.name, ms: s.durationMs, status: s.status }))
};
}
const story = {
host,
repo,
pull: {
uri,
url: `https://${host}/${repo}/pull/${uri.split('/')[2]}/${uri.split('/').pop()}`,
title: pull.title,
author: pull.handle,
branch: pull.head.branch,
base: pull.base,
opened: pull.createdAt,
state: pull.state
},
diff: {
files: files.map(({ path, added, removed }) => ({ path, added, removed })),
excerpt: excerpt(files)
},
pushes,
reviews,
queued: pull.queue?.createdAt ?? null,
landed,
deploy
};
const out = join(root, 'out/stories');
mkdirSync(out, { recursive: true });
const file = join(out, `${uri.split('/').pop()}.json`);
writeFileSync(file, `${JSON.stringify(story, null, 1)}\n`);
const span = deploy?.finished ?? landed?.at ?? pushes.at(-1)?.at;
const minutes = span ? Math.round((at(span) - at(pull.createdAt)) / 60000) : 0;
console.log(
`${file}: ${files.length} files, ${pushes.length} pushes, ${reviews.length} reviews, ` +
`${landed ? 'landed' : 'not landed'}, ${deploy ? 'deployed' : 'no deploy'}; ${minutes} min`
);
} finally {
tools.close();
}
marketing/video/shoot/scenes/review.mjsnew+60−0
@@ -0,0 +1,60 @@
// alice reviews bob's pull into tally: a comment on a line it adds, the
// review approved, then the pull merged from its page, squashed into one
// commit on her protected main.
import { film } from '../camera.mjs';
export default {
as: 'alice',
start: (world) => `${world.views['pull-open']}/files`,
film: async ({ page, world, size, take }) => {
const pull = world.base + world.views['pull-open'];
const shot = await film(take, page, size);
try {
await shot.open(`${pull}/files`);
await shot.hold(900);
shot.mark('files');
// The line the pull adds that reads `--json`, with the button that comments on it.
const row = page
.locator('tr', { has: page.locator('button[aria-label^="Comment on line"]') })
.filter({ hasText: '"--json"' })
.first();
await shot.point(row);
await shot.hold(300);
await shot.click(row.locator('button[aria-label^="Comment on line"]'));
const box = page.getByRole('textbox', { name: /^Comment on line/ });
await shot.type(box, 'Could this say `--format json`, to leave room for others?', {
delay: 40
});
await shot.hold(300);
await shot.click(page.getByRole('button', { name: 'Start a review' }));
await page.locator('.pending-line').waitFor();
shot.mark('commented');
await shot.hold(700);
await shot.click(page.locator('.review-menu > summary'));
shot.mark('finish');
const menu = page.locator('.review-menu .menu');
await shot.type(menu.getByPlaceholder('Leave a comment'), 'Looks good with that.', {
delay: 45
});
await shot.click(menu.getByText('Approve', { exact: true }));
await shot.hold(250);
await shot.click(menu.getByRole('button', { name: 'Approve', exact: true }));
await page.locator('.review-menu .menu').waitFor({ state: 'hidden' });
shot.mark('approved');
await shot.hold(900);
await shot.open(pull);
await shot.hold(700);
const box2 = page.locator('.merge-box');
await box2.scrollIntoViewIfNeeded();
shot.mark('merge');
await shot.click(box2.getByRole('button', { name: 'Merge pull' }));
await page.locator('.event', { hasText: 'Merged as' }).waitFor({ timeout: 30_000 });
await page.locator('.event', { hasText: 'Merged as' }).scrollIntoViewIfNeeded();
shot.mark('merged');
await shot.hold(2200);
} finally {
await shot.cut();
}
}
};
marketing/video/shoot/scenes/star.mjsnew+22−0
@@ -0,0 +1,22 @@
// bob finds alice's notes and stars it: the count goes up as he clicks.
import { film } from '../camera.mjs';
export default {
as: 'bob',
start: () => '/alice.test/notes',
film: async ({ page, world, size, take }) => {
const shot = await film(take, page, size);
try {
await shot.open(`${world.base}/alice.test/notes`);
await shot.hold(1000);
shot.mark('star');
await shot.click(page.getByRole('button', { name: 'Star', exact: true }));
await page.getByRole('button', { name: 'Starred' }).waitFor();
shot.mark('starred');
await shot.hold(1800);
} finally {
await shot.cut();
}
}
};
marketing/video/shoot/camera.mjsnew+259−0
@@ -0,0 +1,259 @@
// Someone at the pages, filmed: a browser logged in as them, Chrome's own
// screencast saving each frame it paints, and a cursor and keyboard moving
// at a person's pace. A take is a folder of frames under public/footage
// with a take.json of when each was painted, the marks the scene set, and
// where each click landed, which the video plays back (video/Footage.tsx).
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright-core';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
export const SIZES = {
wide: { viewport: { width: 1280, height: 800 }, deviceScaleFactor: 1.5 },
phone: {
viewport: { width: 390, height: 844 },
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
}
};
/// A browser for the world: its gateway's name resolved here, its
/// certificate taken, drawn dark as the videos are.
export async function launch(size) {
const browser = await chromium.launch({
headless: true,
// Without it a headless screencast sends frames at 1x, whatever the
// context's deviceScaleFactor.
args: [
`--force-device-scale-factor=${SIZES[size].deviceScaleFactor}`,
'--host-resolver-rules=MAP gateway.knotless.internal 127.0.0.1'
]
});
const context = await browser.newContext({
...SIZES[size],
ignoreHTTPSErrors: true,
colorScheme: 'dark',
locale: 'en-US'
});
return { browser, context };
}
/// Until the page's script has taken it over, as the root says once mounted.
export const ready = (page) => page.locator('html[data-ready]').waitFor({ state: 'attached' });
/// Logs `who` in from the header's dialog, through their PDS's page: done
/// before filming, so the take starts logged in.
export async function logIn(page, world, who) {
await page.getByRole('button', { name: 'Log in' }).click();
const dialog = page.getByRole('dialog', { name: 'Log in with your Atmosphere account' });
await dialog.getByLabel('Handle').fill(who.handle);
const back = page.url();
await dialog.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL((u) => u.origin === new URL(world.pds).origin);
await page.getByRole('textbox', { name: 'Password' }).fill(who.password);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('button', { name: /^(accept|authorize|allow)$/i }).click();
await page.waitForURL(back);
await ready(page);
await page.getByLabel('Your account').waitFor();
}
/// Film `page` as `take`, at `size`: frames into public/footage/<take>.
export async function film(take, page, size) {
const dir = join(root, 'public/footage', take);
rmSync(dir, { recursive: true, force: true });
mkdirSync(dir, { recursive: true });
await page.addInitScript(CURSOR);
await page.addInitScript(`(${LIVE})()`);
// A phone is touched, not pointed at: no cursor, just the taps the video draws.
if (size === 'phone') await page.addInitScript(HIDE_CURSOR);
const { width, height } = SIZES[size].viewport;
const scale = SIZES[size].deviceScaleFactor;
const cdp = await page.context().newCDPSession(page);
const frames = [];
const marks = {};
const clicks = [];
let t0 = 0;
let n = 0;
// Written as they come: a sync write here held up the scene's own moves.
const writing = [];
cdp.on('Page.screencastFrame', (e) => {
void cdp.send('Page.screencastFrameAck', { sessionId: e.sessionId }).catch(() => {});
const t = e.metadata.timestamp ?? Date.now() / 1000;
if (!t0) t0 = t;
const f = `${String(n++).padStart(5, '0')}.jpg`;
frames.push({ t: +(t - t0).toFixed(3), f });
writing.push(writeFile(join(dir, f), Buffer.from(e.data, 'base64')));
});
await cdp.send('Page.startScreencast', {
format: 'jpeg',
quality: 92,
maxWidth: width * scale,
maxHeight: height * scale,
everyNthFrame: 1
});
const now = () => (t0 ? Date.now() / 1000 - t0 : 0);
const at = { x: width / 2, y: height / 2 };
const cursor = (x, y, hand) =>
page.evaluate(([cx, cy, h]) => window.__cursor?.(cx, cy, h), [x, y, hand]).catch(() => {});
/// To `target`, the cursor a hand as it nears it when it is to be clicked.
async function moveTo(target, hand = false) {
await target.scrollIntoViewIfNeeded().catch(() => {});
const box = await target.boundingBox();
if (!box) throw new Error(`nothing to point at: ${target}`);
const x = box.x + Math.min(box.width / 2, 40 + Math.random() * 10);
const y = box.y + box.height / 2;
const steps = 24;
for (let i = 1; i <= steps; i++) {
const k = i / steps;
const e = k < 0.5 ? 2 * k * k : 1 - (-2 * k + 2) ** 2 / 2;
const px = at.x + (x - at.x) * e;
const py = at.y + (y - at.y) * e;
await page.mouse.move(px, py);
await cursor(px, py, hand && k > 0.6);
await page.waitForTimeout(16);
}
at.x = x;
at.y = y;
}
const press = async () => {
clicks.push({ t: +now().toFixed(3), x: Math.round(at.x), y: Math.round(at.y) });
await page.evaluate(() => window.__press?.()).catch(() => {});
};
return {
page,
/// Go to `url`, and wait for the page to be ready.
open: async (url) => {
await page.goto(url);
await ready(page);
await cursor(at.x, at.y, false);
},
/// Note where the scene is, for the edit to cut on.
mark: (name) => {
marks[name] = +now().toFixed(3);
},
/// Move the cursor to something, without clicking.
point: (target) => moveTo(target),
click: async (target, { pause = 180 } = {}) => {
await moveTo(target, true);
await page.waitForTimeout(pause);
await press();
await target.click();
await cursor(at.x, at.y, false);
await page.waitForTimeout(250);
},
/// Click into a field and type, a key at a time.
type: async (target, text, { delay = 55 } = {}) => {
await moveTo(target, true);
await press();
await target.click();
// Off what is about to be typed, as anyone moves the mouse aside.
const box = await target.boundingBox();
if (box) {
at.x = Math.min(box.x + box.width - 24, width - 30);
at.y = box.y + box.height - 10;
await cursor(at.x, at.y, false);
}
await page.waitForTimeout(200);
await target.pressSequentially(text, { delay });
},
hold: (ms) => page.waitForTimeout(ms),
/// Scroll the window, smoothly, by `dy`.
scroll: async (dy) => {
const steps = Math.max(10, Math.round(Math.abs(dy) / 12));
for (let i = 0; i < steps; i++) {
await page.mouse.wheel(0, dy / steps);
await page.waitForTimeout(16);
}
},
cut: async () => {
await cdp.send('Page.stopScreencast').catch(() => {});
await Promise.all(writing);
writeFileSync(
join(dir, 'take.json'),
JSON.stringify({ size, width, height, scale, frames, marks, clicks }, null, 1)
);
console.log(
` ${take}: ${frames.length} frames, ${now().toFixed(1)} s, marks ${JSON.stringify(marks)}`
);
}
};
}
// The world's own address, as knotless.dev's would read, in the text a page
// shows (never its scripts).
const LIVE = `() => {
if (window.__live) return;
window.__live = true;
const at = /gateway\\.knotless\\.internal(:\\d+)?/g;
const live = (n) => {
if (n.nodeType === 3) {
const p = n.parentElement?.tagName;
if (p !== 'SCRIPT' && p !== 'STYLE' && at.test(n.data)) n.data = n.data.replace(at, 'knotless.dev');
} else n.childNodes.forEach(live);
};
const start = () => {
live(document);
new MutationObserver((ms) => ms.forEach((m) => (m.type === 'characterData' ? live(m.target) : m.addedNodes.forEach(live)))).observe(document, { childList: true, subtree: true, characterData: true });
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
else start();
}`;
const HIDE_CURSOR = `document.addEventListener('DOMContentLoaded', () => {
const s = document.createElement('style');
s.textContent = '#film-cursor svg{display:none!important}';
document.head.append(s);
});`;
// A cursor drawn into the page, in its top layer over any dialog, since a
// screencast does not show the real one: moved by the camera (moveTo). An
// arrow, or a hand on its way to a click, which presses in as it clicks; the
// video draws the click itself. A pixel blinking in a corner keeps the
// screencast painting while a page is still.
const CURSOR = `(() => {
if (window !== window.top) return;
const draw = () => {
if (document.getElementById('film-cursor')) return;
const c = document.createElement('div');
c.id = 'film-cursor';
c.setAttribute('popover', 'manual');
c.innerHTML = '<svg class="arrow" width="26" height="26" viewBox="0 0 24 24"><path d="M4 2.5 19 13l-6.6 1.2L16 21.3l-2.9 1.3-3.5-7.2L4 19.6z" fill="#fff" stroke="#111" stroke-width="1.6" stroke-linejoin="round"/></svg><svg class="hand" width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="#111" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15V4a2 2 0 0 1 4 0v5.5V9a2 2 0 0 1 4 0v1a2 2 0 0 1 3 1z" fill="#fff"/><path d="M11 9.5V4M15 10V9M18 11v-1"/></svg>';
c.style.cssText = 'position:fixed;left:0;top:0;z-index:2147483647;pointer-events:none;transform:translate(640px,400px);will-change:transform';
const s = document.createElement('style');
s.textContent = '#film-cursor{inset:auto;margin:0;border:0;padding:0;background:none;overflow:visible;width:0;height:0}#film-cursor svg{position:absolute;max-width:none;max-height:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.5));transform-origin:30% 10%}#film-cursor .arrow{left:-5px;top:-3px}#film-cursor .hand{left:-10px;top:-3px;display:none}#film-cursor.hand .arrow{display:none}#film-cursor.hand .hand{display:block}#film-cursor.press svg{animation:film-press .3s ease-out}@keyframes film-press{40%{transform:scale(.82)}100%{transform:scale(1)}}#film-tick{position:fixed;right:0;bottom:0;width:1px;height:1px;z-index:2147483647;pointer-events:none;animation:film-tick 1s steps(2) infinite}@keyframes film-tick{0%{background:rgba(128,128,128,.01)}100%{background:rgba(128,128,128,.02)}}';
const tick = document.createElement('div');
tick.id = 'film-tick';
document.documentElement.append(s, c, tick);
const last = sessionStorage.getItem('film-cursor');
if (last) c.style.transform = last;
};
window.__cursor = (x, y, hand) => {
draw();
const c = document.getElementById('film-cursor');
c.classList.toggle('hand', !!hand);
try {
if (c.matches(':popover-open')) c.hidePopover();
c.showPopover();
} catch {}
c.style.transform = 'translate(' + x + 'px,' + y + 'px)';
sessionStorage.setItem('film-cursor', c.style.transform);
};
window.__press = () => {
const c = document.getElementById('film-cursor');
if (!c) return;
c.classList.remove('press');
void c.offsetWidth;
c.classList.add('press');
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', draw);
else draw();
})();`;
marketing/video/shoot/shoot.mjsnew+45−0
@@ -0,0 +1,45 @@
// `node shoot/shoot.mjs <scene>... [--phone]`: the e2e world seeded and
// served as a gateway (harness/e2e/setup.mjs), then each scene filmed over
// it in turn, logged in as its person first. Each scene, in ./scenes, films
// its own takes into public/footage. Needs `cargo build --bins` and web/build,
// as `just e2e` does.
import { launch, logIn, ready } from './camera.mjs';
import review from './scenes/review.mjs';
import star from './scenes/star.mjs';
const SCENES = { review, star };
const args = process.argv.slice(2);
const size = args.includes('--phone') ? 'phone' : 'wide';
const names = args.filter((a) => !a.startsWith('--'));
const unknown = names.filter((n) => !SCENES[n]);
if (!names.length || unknown.length) {
console.error(`which scenes? ${Object.keys(SCENES).join(', ')}`);
process.exit(2);
}
// The e2e world's own ports, apart from `shots-up`'s, as `just e2e` takes.
process.env.E2E ??= '1';
const { default: setup } = await import('../../../harness/e2e/setup.mjs');
const teardown = await setup();
const world = JSON.parse(process.env.E2E_WORLD);
const who = Object.fromEntries(world.accounts.map((a) => [a.handle.split('.')[0], a]));
try {
for (const name of names) {
const scene = SCENES[name];
const { browser, context } = await launch(size);
try {
const page = await context.newPage();
await page.goto(world.base + scene.start(world));
await ready(page);
await logIn(page, world, who[scene.as]);
await scene.film({ page, world, size, take: `${name}-${size}` });
} finally {
await browser.close();
}
}
} finally {
teardown();
}
marketing/video/video/change/Checks.tsxnew+153−0
@@ -0,0 +1,153 @@
// A push's checks, run as a time-lapse: each workflow turning from running
// to passed (or failed) when, in the run's own time, it did, with a clock of
// the minutes gone since the push.
import { interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
import { look } from '../look.ts';
import { Code, Icon, risen, useRise, useStage } from '../kit.tsx';
import { ms, short, took, type Push } from '../story.ts';
const LEAD = 14;
const TAIL = 34;
const clock = (span: number) => {
const s = Math.max(0, Math.round(span / 1000));
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
};
export function Checks({ push, again }: { push: Push; again: boolean }) {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const { inner, wide } = useStage();
const start = ms(push.at);
const end = Math.max(start + 1000, ...push.checks.map((c) => ms(c.finished ?? c.started)));
const run = durationInFrames - LEAD - TAIL;
// The run's time at this frame, and the frame a time in the run shows at.
const now = interpolate(frame, [LEAD, LEAD + run], [start, end], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const head = useRise(0);
const passed = push.checks.filter((c) => c.finished && ms(c.finished) <= now);
const width = Math.min(inner, 1100);
const row = push.checks.length > 8 ? 52 : 64;
return (
<div style={{ width, display: 'flex', flexDirection: 'column', gap: 22 }}>
<div
style={{
display: 'flex',
alignItems: 'baseline',
justifyContent: 'space-between',
gap: 24,
...risen(head)
}}
>
<div style={{ fontSize: wide ? 46 : 40, fontWeight: 600, letterSpacing: '-0.02em' }}>
{again ? 'Pushed again, checked again' : 'Every push is checked'}
</div>
<div
style={{
fontFamily: look.mono,
fontSize: 34,
color: look.yellow,
fontVariantNumeric: 'tabular-nums'
}}
>
+{clock(now - start)}
</div>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 14,
fontSize: 22,
color: look.mute,
...risen(head)
}}
>
at <Code>{short(push.commit)}</Code>
<span>
· {passed.length} of {push.checks.length} done · time-lapse
</span>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
border: `1px solid ${look.line}`,
borderRadius: 14,
background: look.panel,
overflow: 'hidden'
}}
>
{push.checks.map((c, i) => (
<CheckRow
key={c.workflow}
check={c}
now={now}
delay={6 + i * 2}
height={row}
last={i === push.checks.length - 1}
/>
))}
</div>
</div>
);
}
function CheckRow({
check,
now,
delay,
height,
last
}: {
check: Push['checks'][number];
now: number;
delay: number;
height: number;
last: boolean;
}) {
const frame = useCurrentFrame();
const p = useRise(delay, 14);
const done = check.finished !== null && ms(check.finished) <= now;
const failed = done && !['success'].includes(check.status);
const icon = !done
? { name: 'spin' as const, color: look.yellow }
: check.skipped
? { name: 'skip' as const, color: look.faint }
: failed
? { name: 'x' as const, color: look.red }
: { name: 'check' as const, color: look.green };
return (
<div
style={{
height,
display: 'flex',
alignItems: 'center',
gap: 18,
padding: '0 26px',
borderBottom: last ? 'none' : `1px solid ${look.line}`,
fontSize: 26,
...risen(p, 8)
}}
>
<Icon name={icon.name} color={icon.color} size={30} turn={frame * 12} />
<span style={{ flex: 1, fontWeight: 600 }}>{check.workflow}</span>
<span
style={{
fontFamily: look.mono,
fontSize: 22,
color: done ? look.mute : look.yellow,
fontVariantNumeric: 'tabular-nums'
}}
>
{done
? check.skipped
? 'skipped'
: took(ms(check.finished!) - ms(check.started))
: 'running'}
</span>
</div>
);
}
marketing/video/video/change/Deploy.tsxnew+100−0
@@ -0,0 +1,100 @@
// The deploy of the landed commit: its steps filling in turn, each as long
// as it took (in a time-lapse), then the change live.
import { interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
import { look } from '../look.ts';
import { Icon, risen, useRise, useStage } from '../kit.tsx';
import { ms, took, type Story } from '../story.ts';
const LEAD = 16;
const TAIL = 40;
export function Deploy({ deploy }: { deploy: NonNullable<Story['deploy']> }) {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
const { inner, wide } = useStage();
const head = useRise(0);
const width = Math.min(inner, 1100);
const total = deploy.steps.reduce((s, x) => s + x.ms, 0) || 1;
// The share of the deploy done at this frame.
const done = interpolate(frame, [LEAD, durationInFrames - TAIL], [0, total], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const live = useRise(durationInFrames - TAIL + 4, 16);
const passed = deploy.status === 'success';
let before = 0;
return (
<div style={{ width, display: 'flex', flexDirection: 'column', gap: 26 }}>
<div
style={{
fontSize: wide ? 46 : 40,
fontWeight: 600,
letterSpacing: '-0.02em',
...risen(head)
}}
>
Then deployed
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{deploy.steps.map((s, i) => {
const share = Math.min(1, Math.max(0, (done - before) / s.ms));
before += s.ms;
return (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 18, fontSize: 25 }}>
<span style={{ width: wide ? 220 : 170, color: share > 0 ? look.fg : look.faint }}>
{s.name}
</span>
<div
style={{
flex: 1,
height: 16,
borderRadius: 8,
background: look.soft,
overflow: 'hidden'
}}
>
<div
style={{
width: `${share * 100}%`,
height: '100%',
background: share >= 1 ? look.green : look.yellow
}}
/>
</div>
<span
style={{
width: 110,
textAlign: 'right',
fontFamily: look.mono,
fontSize: 21,
color: look.mute,
opacity: share >= 1 ? 1 : 0.35
}}
>
{took(s.ms)}
</span>
</div>
);
})}
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 16,
fontSize: 36,
fontWeight: 600,
marginTop: 14,
...risen(live, 14)
}}
>
<Icon name={passed ? 'check' : 'x'} color={passed ? look.green : look.red} size={40} />
{passed
? deploy.finished
? `Deployed in ${took(ms(deploy.finished) - ms(deploy.started))}`
: 'Deployed'
: `The deploy ended: ${deploy.status}`}
</div>
</div>
);
}
marketing/video/video/change/Diff.tsxnew+158−0
@@ -0,0 +1,158 @@
// What the pull changes: its files with the lines each adds and takes away,
// then the stretch of code that says most, line by line.
import { useCurrentFrame } from 'remotion';
import { look } from '../look.ts';
import { risen, useRise, useStage, Window } from '../kit.tsx';
import type { Story } from '../story.ts';
function Blocks({ added, removed }: { added: number; removed: number }) {
const total = added + removed;
const green = total ? Math.round((added / total) * 5) : 0;
return (
<span style={{ display: 'inline-flex', gap: 3 }}>
{[0, 1, 2, 3, 4].map((i) => (
<span
key={i}
style={{
width: 12,
height: 12,
borderRadius: 2,
background: i < green ? look.green : total ? look.red : look.line
}}
/>
))}
</span>
);
}
function Files({ files, most }: { files: Story['diff']['files']; most: number }) {
const shown = files.slice(0, most);
const rest = files.length - shown.length;
const added = files.reduce((s, f) => s + f.added, 0);
const removed = files.reduce((s, f) => s + f.removed, 0);
const head = useRise(0);
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ fontSize: 30, fontWeight: 600, ...risen(head) }}>
{files.length} {files.length === 1 ? 'file' : 'files'}{' '}
<span style={{ color: look.green }}>+{added}</span>{' '}
<span style={{ color: look.red }}>−{removed}</span>
</div>
{shown.map((f, i) => (
<FileRow key={f.path} file={f} delay={6 + i * 4} />
))}
{rest > 0 && <div style={{ fontSize: 22, color: look.mute }}>and {rest} more</div>}
</div>
);
}
function FileRow({ file, delay }: { file: Story['diff']['files'][number]; delay: number }) {
const p = useRise(delay, 16);
const slash = file.path.lastIndexOf('/');
return (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 14,
fontFamily: look.mono,
fontSize: 21,
...risen(p, 10)
}}
>
<Blocks added={file.added} removed={file.removed} />
<span
style={{
flex: 1,
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
direction: 'rtl',
textAlign: 'left'
}}
>
<bdi>
<span style={{ color: look.faint }}>{file.path.slice(0, slash + 1)}</span>
{file.path.slice(slash + 1)}
</bdi>
</span>
<span style={{ color: look.green }}>+{file.added}</span>
<span style={{ color: look.red }}>−{file.removed}</span>
</div>
);
}
function Excerpt({
excerpt,
width,
from
}: {
excerpt: NonNullable<Story['diff']['excerpt']>;
width: number;
from: number;
}) {
const frame = useCurrentFrame();
const size = 21;
return (
<Window title={excerpt.path} width={width}>
<div style={{ padding: '14px 0', fontFamily: look.mono, fontSize: size, lineHeight: 1.6 }}>
{excerpt.lines.map((l, i) => {
const at = from + i * 2.5;
const on = Math.min(1, Math.max(0, (frame - at) / 6));
const bg = l.mark === '+' ? look.ins : l.mark === '-' ? look.del : 'transparent';
const color = l.mark === '+' ? look.green : l.mark === '-' ? look.red : look.faint;
return (
<div
key={i}
style={{
display: 'flex',
background: bg,
opacity: on,
transform: `translateX(${(1 - on) * -12}px)`
}}
>
<span style={{ width: 70, textAlign: 'right', color: look.faint, flexShrink: 0 }}>
{l.n}
</span>
<span style={{ width: 34, textAlign: 'center', color, flexShrink: 0 }}>
{l.mark === ' ' ? '' : l.mark === '-' ? '−' : '+'}
</span>
<span
style={{
whiteSpace: 'pre',
overflow: 'hidden',
textOverflow: 'ellipsis',
paddingRight: 24,
color: l.mark === ' ' ? look.mute : look.fg
}}
>
{l.text}
</span>
</div>
);
})}
</div>
</Window>
);
}
export function Diff({ story }: { story: Story }) {
const { wide, inner } = useStage();
const { files, excerpt } = story.diff;
if (wide)
return (
<div style={{ display: 'flex', gap: 56, alignItems: 'center', width: inner }}>
<div style={{ width: 560, flexShrink: 0 }}>
<Files files={files} most={7} />
</div>
{excerpt && <Excerpt excerpt={excerpt} width={inner - 560 - 56} from={18} />}
</div>
);
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 44, width: inner }}>
<Files files={files} most={5} />
{excerpt && <Excerpt excerpt={excerpt} width={inner} from={22} />}
</div>
);
}
marketing/video/video/change/Land.tsxnew+90−0
@@ -0,0 +1,90 @@
// The pull landing: queued, then squashed onto its base as one commit, drawn
// as its branch joining the base's line.
import { interpolate, useCurrentFrame } from 'remotion';
import { look } from '../look.ts';
import { Code, risen, useRise, useStage, Words } from '../kit.tsx';
import { ms, short, took, type Story } from '../story.ts';
export function Land({ story }: { story: Story }) {
const frame = useCurrentFrame();
const { inner, wide } = useStage();
const { landed, queued, pull } = story;
const width = Math.min(inner, 1300);
const h = 220;
const join = width * 0.62;
// The branch drawn in, then the commit it lands as.
const draw = interpolate(frame, [18, 48], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
const dot = useRise(46, 14);
const note = useRise(56, 16);
const wait = queued && landed?.at ? took(ms(landed.at) - ms(queued)) : null;
const branch = `M 40 ${h - 40} C ${join * 0.45} ${h - 40}, ${join * 0.7} ${h / 2}, ${join} ${h / 2}`;
return (
<div style={{ width, display: 'flex', flexDirection: 'column', gap: 34, alignItems: 'center' }}>
<Words
text={landed ? (queued ? 'Queued, then landed' : 'Merged') : 'Queued to land'}
size={wide ? 76 : 66}
/>
<svg width={width} height={h} style={{ overflow: 'visible' }}>
<line
x1={0}
y1={h / 2}
x2={width}
y2={h / 2}
stroke={look.line}
strokeWidth={6}
strokeLinecap="round"
/>
{[0.12, 0.3, 0.46].map((x) => (
<circle key={x} cx={width * x} cy={h / 2} r={11} fill={look.faint} />
))}
<path
d={branch}
fill="none"
stroke={look.purple}
strokeWidth={6}
strokeLinecap="round"
pathLength={1}
strokeDasharray={1}
strokeDashoffset={1 - draw}
/>
<circle cx={40} cy={h - 40} r={11} fill={look.purple} />
{landed && (
<circle
cx={join}
cy={h / 2}
r={17 * dot}
fill={look.purple}
stroke={look.bg}
strokeWidth={5}
/>
)}
<text x={0} y={h / 2 - 30} fill={look.mute} fontSize={24} fontFamily={look.mono}>
{pull.base}
</text>
<text x={20} y={h + 4} fill={look.purple} fontSize={22} fontFamily={look.mono}>
{pull.branch}
</text>
</svg>
{landed && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 14,
fontSize: 28,
color: look.mute,
flexWrap: 'wrap',
justifyContent: 'center',
...risen(note, 12)
}}
>
on {pull.base} as <Code size={26}>{short(landed.commit)}</Code>
{wait && <span>· {wait} after queueing</span>}
</div>
)}
</div>
);
}
marketing/video/video/change/Review.tsxnew+93−0
marketing/video/video/change/Terminal.tsxnew+72−0
marketing/video/video/assets.d.tsnew+8−0
marketing/video/video/Change.tsxnew+329−0
marketing/video/video/Feature.tsxnew+166−0
marketing/video/video/Footage.tsxnew+193−0
marketing/video/video/index.tsnew+4−0
marketing/video/video/kit.tsxnew+338−0
marketing/video/video/look.tsnew+40−0
marketing/video/video/Root.tsxnew+39−0
marketing/video/video/story.tsnew+65−0
marketing/video/package-lock.jsonnew+4175−0
A large diff, not shown.
marketing/video/package.jsonnew+24−0
marketing/video/render.mjsnew+46−0
marketing/video/tsconfig.jsonnew+15−0
marketing/README.md+36−0
.gitignore+3−0
Justfile+18−1