chadtmiller.com/knotless

Git hosted in your atproto account, on any stock PDS, fast enough for agents.

git clone knotless://chadtmiller.com/knotless

needs the knotless git helper. set it up

Profile pages show the Bluesky avatar, from Bluesky's CDN

The profile answer names the avatar blob of app.bsky.actor.profile as
its cdn.bsky.app URL, and the profile header draws it in place of the
lettered mark when there is one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chad Miller committed on Oct 21, 2026 parent a4ed85c commit 8b01bf7
Showing 99 changed files with 150 additions and 117 deletions
crates/knotless-serve/src/web.rs+9−0
@@ -476,11 +476,16 @@ pub struct Profile {
handle: String,
name: Option<String>,
bio: Option<String>,
/// Its profile picture, as Bluesky's CDN serves it, if it has one.
avatar: Option<String>,
/// Its bio on one line, for whatever unfurls a link to it.
about: Option<String>,
repos: Vec<Listed>,
}
/// Where Bluesky's CDN serves profile pictures, by DID and blob CID.
const AVATAR_CDN: &str = "https://cdn.bsky.app/img/avatar/plain";
async fn profile(State(app): State<Arc<App>>, Path(authority): Path<String>) -> Result<Response> {
let at = RepoUrl {
authority: authority.clone(),
@@ -498,6 +503,9 @@ async fn profile(State(app): State<Arc<App>>, Path(authority): Path<String>) ->
.unwrap_or_default();
let text = |field: &str| about[field].as_str().map(str::to_string);
let name = text("displayName").filter(|n| !n.trim().is_empty());
let avatar = about["avatar"]["ref"]["$link"]
.as_str()
.map(|cid| format!("{AVATAR_CDN}/{did}/{cid}@jpeg"));
let mut found = remote.list_repos().await.map_err(AppError::upstream)?;
let mut private = Vec::new();
if app.private
@@ -537,6 +545,7 @@ async fn profile(State(app): State<Arc<App>>, Path(authority): Path<String>) ->
handle: shown,
name,
bio: text("description"),
avatar,
about: about["description"].as_str().map(|bio| summary(bio, 200)),
repos,
},
crates/knotless-serve/tests/web.rs+20−2
@@ -571,8 +571,8 @@ fn pulls_and_issues() {
assert_eq!(theirs["did"], other_did.as_str());
assert_eq!(theirs["repos"][0]["forkOf"], source);
assert_eq!(
(&theirs["name"], &theirs["bio"]),
(&Value::Null, &Value::Null)
(&theirs["name"], &theirs["bio"], &theirs["avatar"]),
(&Value::Null, &Value::Null, &Value::Null)
);
let session: Value = Client::new()
.post(format!(
@@ -587,6 +587,15 @@ fn pulls_and_issues() {
.unwrap()
.json()
.unwrap();
let picture: Value = Client::new()
.post(format!("{}/xrpc/com.atproto.repo.uploadBlob", net().pds))
.bearer_auth(session["accessJwt"].as_str().unwrap())
.header("content-type", "image/png")
.body(b"\x89PNG\r\n\x1a\n".to_vec())
.send()
.unwrap()
.json()
.unwrap();
let put = Client::new()
.post(format!("{}/xrpc/com.atproto.repo.putRecord", net().pds))
.bearer_auth(session["accessJwt"].as_str().unwrap())
@@ -598,6 +607,7 @@ fn pulls_and_issues() {
"$type": "app.bsky.actor.profile",
"displayName": "The <Owner>",
"description": "Keeps things tracked.",
"avatar": picture["blob"],
},
}))
.send()
@@ -610,6 +620,14 @@ fn pulls_and_issues() {
assert_eq!(mine["handle"], owner.account.handle.as_str());
assert_eq!(mine["bio"], "Keeps things tracked.");
assert_eq!(mine["about"], "Keeps things tracked.");
let cid = picture["blob"]["ref"]["$link"].as_str().unwrap();
assert_eq!(
mine["avatar"],
format!(
"https://cdn.bsky.app/img/avatar/plain/{}/{cid}@jpeg",
owner.account.did
)
);
assert_eq!(
named(&mine["repos"], "tracked")["description"],
"Tracks <things>"
harness/shots/baseline/darwin/commit-desktop-dark.sha256+1−1
@@ -1 +1 @@
231150f83bbc3c4b27f062948bd57ab7eb266bd01f3ca7407eccc48a87cc47a7
\ No newline at end of file
1541518c44a2c6bb3302b6ec0cd245c099d10781e2ad665e78dfc58d9c062dfa
\ No newline at end of file
harness/shots/baseline/darwin/commit-desktop-light.sha256+1−1
@@ -1 +1 @@
231150f83bbc3c4b27f062948bd57ab7eb266bd01f3ca7407eccc48a87cc47a7
\ No newline at end of file
1541518c44a2c6bb3302b6ec0cd245c099d10781e2ad665e78dfc58d9c062dfa
\ No newline at end of file
harness/shots/baseline/darwin/commit-phone-dark.sha256+1−1
@@ -1 +1 @@
231150f83bbc3c4b27f062948bd57ab7eb266bd01f3ca7407eccc48a87cc47a7
\ No newline at end of file
1541518c44a2c6bb3302b6ec0cd245c099d10781e2ad665e78dfc58d9c062dfa
\ No newline at end of file
harness/shots/baseline/darwin/commit-phone-light.sha256+1−1
@@ -1 +1 @@
231150f83bbc3c4b27f062948bd57ab7eb266bd01f3ca7407eccc48a87cc47a7
\ No newline at end of file
1541518c44a2c6bb3302b6ec0cd245c099d10781e2ad665e78dfc58d9c062dfa
\ No newline at end of file
harness/shots/baseline/darwin/commits-desktop-dark.sha256+1−1
@@ -1 +1 @@
33fa56b10d23597dfcdc7c1c7d59571973d7c57663817022f37400842b463df2
\ No newline at end of file
39a3ad3f28561ef0c742c57783665f40ef0a9e7fb8871b44fbb8c2fec30ed2a0
\ No newline at end of file
harness/shots/baseline/darwin/commits-desktop-light.sha256+1−1
@@ -1 +1 @@
33fa56b10d23597dfcdc7c1c7d59571973d7c57663817022f37400842b463df2
\ No newline at end of file
39a3ad3f28561ef0c742c57783665f40ef0a9e7fb8871b44fbb8c2fec30ed2a0
\ No newline at end of file
harness/shots/baseline/darwin/commits-phone-dark.sha256+1−1
@@ -1 +1 @@
33fa56b10d23597dfcdc7c1c7d59571973d7c57663817022f37400842b463df2
\ No newline at end of file
39a3ad3f28561ef0c742c57783665f40ef0a9e7fb8871b44fbb8c2fec30ed2a0
\ No newline at end of file
harness/shots/baseline/darwin/commits-phone-light.sha256+1−1
@@ -1 +1 @@
33fa56b10d23597dfcdc7c1c7d59571973d7c57663817022f37400842b463df2
\ No newline at end of file
39a3ad3f28561ef0c742c57783665f40ef0a9e7fb8871b44fbb8c2fec30ed2a0
\ No newline at end of file
harness/shots/baseline/darwin/directory-desktop-dark.sha256+1−1
@@ -1 +1 @@
3f787a67b0f14f68a1ccff26ef15697989568757d9b8f27c7a6c996095a85f9e
\ No newline at end of file
b0b6c319627a76fb3b49e7b1f218f459cf68f09fe8cf212b77d48e4f6997db5e
\ No newline at end of file
harness/shots/baseline/darwin/directory-desktop-light.sha256+1−1
@@ -1 +1 @@
3f787a67b0f14f68a1ccff26ef15697989568757d9b8f27c7a6c996095a85f9e
\ No newline at end of file
b0b6c319627a76fb3b49e7b1f218f459cf68f09fe8cf212b77d48e4f6997db5e
\ No newline at end of file
harness/shots/baseline/darwin/directory-phone-dark.sha256+1−1
@@ -1 +1 @@
3f787a67b0f14f68a1ccff26ef15697989568757d9b8f27c7a6c996095a85f9e
\ No newline at end of file
b0b6c319627a76fb3b49e7b1f218f459cf68f09fe8cf212b77d48e4f6997db5e
\ No newline at end of file
harness/shots/baseline/darwin/directory-phone-light.sha256+1−1
@@ -1 +1 @@
3f787a67b0f14f68a1ccff26ef15697989568757d9b8f27c7a6c996095a85f9e
\ No newline at end of file
b0b6c319627a76fb3b49e7b1f218f459cf68f09fe8cf212b77d48e4f6997db5e
\ No newline at end of file
harness/shots/baseline/darwin/empty-desktop-dark.sha256+1−1
@@ -1 +1 @@
b8cd4b41ec8aab3fcf5478d4184b47923ed517d92d20326fc3c25b813ecb65a6
\ No newline at end of file
87e5b1dde7ab2b30352e6b9c41c74f5d45367db70d5717860b8a6d4e28a975e8
\ No newline at end of file
harness/shots/baseline/darwin/empty-desktop-light.sha256+1−1
@@ -1 +1 @@
b8cd4b41ec8aab3fcf5478d4184b47923ed517d92d20326fc3c25b813ecb65a6
\ No newline at end of file
87e5b1dde7ab2b30352e6b9c41c74f5d45367db70d5717860b8a6d4e28a975e8
\ No newline at end of file
harness/shots/baseline/darwin/empty-phone-dark.sha256+1−1
@@ -1 +1 @@
b8cd4b41ec8aab3fcf5478d4184b47923ed517d92d20326fc3c25b813ecb65a6
\ No newline at end of file
87e5b1dde7ab2b30352e6b9c41c74f5d45367db70d5717860b8a6d4e28a975e8
\ No newline at end of file
harness/shots/baseline/darwin/empty-phone-light.sha256+1−1
@@ -1 +1 @@
b8cd4b41ec8aab3fcf5478d4184b47923ed517d92d20326fc3c25b813ecb65a6
\ No newline at end of file
87e5b1dde7ab2b30352e6b9c41c74f5d45367db70d5717860b8a6d4e28a975e8
\ No newline at end of file
harness/shots/baseline/darwin/file-desktop-dark.sha256+1−1
@@ -1 +1 @@
8600c0447ec01a23f3257a61810782b4ecdc1934b5bbae1de6377aca618e384f
\ No newline at end of file
90674a818ce5ae4c7722b433ea8f92a84b874b4380ba44fff2c5e648d556c92a
\ No newline at end of file
harness/shots/baseline/darwin/file-desktop-light.sha256+1−1
@@ -1 +1 @@
8600c0447ec01a23f3257a61810782b4ecdc1934b5bbae1de6377aca618e384f
\ No newline at end of file
90674a818ce5ae4c7722b433ea8f92a84b874b4380ba44fff2c5e648d556c92a
\ No newline at end of file
harness/shots/baseline/darwin/file-phone-dark.sha256+1−1
@@ -1 +1 @@
8600c0447ec01a23f3257a61810782b4ecdc1934b5bbae1de6377aca618e384f
\ No newline at end of file
90674a818ce5ae4c7722b433ea8f92a84b874b4380ba44fff2c5e648d556c92a
\ No newline at end of file
harness/shots/baseline/darwin/file-phone-light.sha256+1−1
@@ -1 +1 @@
8600c0447ec01a23f3257a61810782b4ecdc1934b5bbae1de6377aca618e384f
\ No newline at end of file
90674a818ce5ae4c7722b433ea8f92a84b874b4380ba44fff2c5e648d556c92a
\ No newline at end of file
harness/shots/baseline/darwin/fork-desktop-dark.sha256+1−1
@@ -1 +1 @@
8c806e17acfcf930bc642a17c6dc2c6a35c23e4125714d5e47b51f804f6119f5
\ No newline at end of file
91b63089b6f59cc8405abfba5b8b6cdb0c88e74b73d082c340d39e013844dbf6
\ No newline at end of file
harness/shots/baseline/darwin/fork-desktop-light.sha256+1−1
@@ -1 +1 @@
8c806e17acfcf930bc642a17c6dc2c6a35c23e4125714d5e47b51f804f6119f5
\ No newline at end of file
91b63089b6f59cc8405abfba5b8b6cdb0c88e74b73d082c340d39e013844dbf6
\ No newline at end of file
harness/shots/baseline/darwin/fork-phone-dark.sha256+1−1
@@ -1 +1 @@
8c806e17acfcf930bc642a17c6dc2c6a35c23e4125714d5e47b51f804f6119f5
\ No newline at end of file
91b63089b6f59cc8405abfba5b8b6cdb0c88e74b73d082c340d39e013844dbf6
\ No newline at end of file
harness/shots/baseline/darwin/fork-phone-light.sha256+1−1
@@ -1 +1 @@
8c806e17acfcf930bc642a17c6dc2c6a35c23e4125714d5e47b51f804f6119f5
\ No newline at end of file
91b63089b6f59cc8405abfba5b8b6cdb0c88e74b73d082c340d39e013844dbf6
\ No newline at end of file
harness/shots/baseline/darwin/home-desktop-dark.sha256+1−1
@@ -1 +1 @@
98a036c149815520817cb0620c76a402f20133015f91662979322bc8df432b7a
\ No newline at end of file
81f2defd3d1d369ba4b9d010480be85b351b188d467bf127783e2c921f1b535d
\ No newline at end of file
harness/shots/baseline/darwin/home-desktop-light.sha256+1−1
@@ -1 +1 @@
98a036c149815520817cb0620c76a402f20133015f91662979322bc8df432b7a
\ No newline at end of file
81f2defd3d1d369ba4b9d010480be85b351b188d467bf127783e2c921f1b535d
\ No newline at end of file
harness/shots/baseline/darwin/home-phone-dark.sha256+1−1
@@ -1 +1 @@
98a036c149815520817cb0620c76a402f20133015f91662979322bc8df432b7a
\ No newline at end of file
81f2defd3d1d369ba4b9d010480be85b351b188d467bf127783e2c921f1b535d
\ No newline at end of file
harness/shots/baseline/darwin/home-phone-light.sha256+1−1
@@ -1 +1 @@
98a036c149815520817cb0620c76a402f20133015f91662979322bc8df432b7a
\ No newline at end of file
81f2defd3d1d369ba4b9d010480be85b351b188d467bf127783e2c921f1b535d
\ No newline at end of file
harness/shots/baseline/darwin/image-desktop-dark.sha256+1−1
@@ -1 +1 @@
cb07442df329f821e64cfa93c62a0ad0d02ca2df078c2bd0912119130fb8e60d
\ No newline at end of file
bfa31d84c5e1f0db09d78a7d133a6c5ae7b2dd5546e8b0f359aec4455aa31794
\ No newline at end of file
harness/shots/baseline/darwin/image-desktop-light.sha256+1−1
@@ -1 +1 @@
cb07442df329f821e64cfa93c62a0ad0d02ca2df078c2bd0912119130fb8e60d
\ No newline at end of file
bfa31d84c5e1f0db09d78a7d133a6c5ae7b2dd5546e8b0f359aec4455aa31794
\ No newline at end of file
harness/shots/baseline/darwin/image-phone-dark.sha256+1−1
@@ -1 +1 @@
cb07442df329f821e64cfa93c62a0ad0d02ca2df078c2bd0912119130fb8e60d
\ No newline at end of file
bfa31d84c5e1f0db09d78a7d133a6c5ae7b2dd5546e8b0f359aec4455aa31794
\ No newline at end of file
harness/shots/baseline/darwin/image-phone-light.sha256+1−1
@@ -1 +1 @@
cb07442df329f821e64cfa93c62a0ad0d02ca2df078c2bd0912119130fb8e60d
\ No newline at end of file
bfa31d84c5e1f0db09d78a7d133a6c5ae7b2dd5546e8b0f359aec4455aa31794
\ No newline at end of file
harness/shots/baseline/darwin/issue-closed-desktop-dark.sha256+1−1
@@ -1 +1 @@
7ef619151e58bea40721dbe4523a72c6754ac4739c1cb83e6fd180dfb846d022
\ No newline at end of file
c4980838942c9c06cba8382387d414588a99919caaf5089e8388ed5018ea5b8f
\ No newline at end of file
harness/shots/baseline/darwin/issue-closed-desktop-light.sha256+1−1
@@ -1 +1 @@
7ef619151e58bea40721dbe4523a72c6754ac4739c1cb83e6fd180dfb846d022
\ No newline at end of file
c4980838942c9c06cba8382387d414588a99919caaf5089e8388ed5018ea5b8f
\ No newline at end of file
harness/shots/baseline/darwin/issue-closed-phone-dark.sha256+1−1
@@ -1 +1 @@
7ef619151e58bea40721dbe4523a72c6754ac4739c1cb83e6fd180dfb846d022
\ No newline at end of file
c4980838942c9c06cba8382387d414588a99919caaf5089e8388ed5018ea5b8f
\ No newline at end of file
harness/shots/baseline/darwin/issue-closed-phone-light.sha256+1−1
@@ -1 +1 @@
7ef619151e58bea40721dbe4523a72c6754ac4739c1cb83e6fd180dfb846d022
\ No newline at end of file
c4980838942c9c06cba8382387d414588a99919caaf5089e8388ed5018ea5b8f
\ No newline at end of file
harness/shots/baseline/darwin/issue-desktop-dark.sha256+1−1
@@ -1 +1 @@
d419596dc734b255bc9062810f0aaed872eb4f0b2b2ab15b4b82400b5deb1f14
\ No newline at end of file
2cf2e1356a46e2e3f1b7768c2de2977d8a499a92e214647383e98d508f85d4a5
\ No newline at end of file
harness/shots/baseline/darwin/issue-desktop-light.sha256+1−1
@@ -1 +1 @@
d419596dc734b255bc9062810f0aaed872eb4f0b2b2ab15b4b82400b5deb1f14
\ No newline at end of file
2cf2e1356a46e2e3f1b7768c2de2977d8a499a92e214647383e98d508f85d4a5
\ No newline at end of file
harness/shots/baseline/darwin/issue-phone-dark.sha256+1−1
@@ -1 +1 @@
d419596dc734b255bc9062810f0aaed872eb4f0b2b2ab15b4b82400b5deb1f14
\ No newline at end of file
2cf2e1356a46e2e3f1b7768c2de2977d8a499a92e214647383e98d508f85d4a5
\ No newline at end of file
harness/shots/baseline/darwin/issue-phone-light.sha256+1−1
@@ -1 +1 @@
d419596dc734b255bc9062810f0aaed872eb4f0b2b2ab15b4b82400b5deb1f14
\ No newline at end of file
2cf2e1356a46e2e3f1b7768c2de2977d8a499a92e214647383e98d508f85d4a5
\ No newline at end of file
harness/shots/baseline/darwin/issues-closed-desktop-dark.sha256+1−1
@@ -1 +1 @@
b925277da1226009850013244f7a969bfdb28dd371ab082556609df06443838c
\ No newline at end of file
be8df56ce2fcf453d1f8b55cbb8b7d4e5f58845480016108f3322fdec10714ca
\ No newline at end of file
harness/shots/baseline/darwin/issues-closed-desktop-light.sha256+1−1
@@ -1 +1 @@
b925277da1226009850013244f7a969bfdb28dd371ab082556609df06443838c
\ No newline at end of file
be8df56ce2fcf453d1f8b55cbb8b7d4e5f58845480016108f3322fdec10714ca
\ No newline at end of file
harness/shots/baseline/darwin/issues-closed-phone-dark.sha256+1−1
@@ -1 +1 @@
b925277da1226009850013244f7a969bfdb28dd371ab082556609df06443838c
\ No newline at end of file
be8df56ce2fcf453d1f8b55cbb8b7d4e5f58845480016108f3322fdec10714ca
\ No newline at end of file
harness/shots/baseline/darwin/issues-closed-phone-light.sha256+1−1
@@ -1 +1 @@
b925277da1226009850013244f7a969bfdb28dd371ab082556609df06443838c
\ No newline at end of file
be8df56ce2fcf453d1f8b55cbb8b7d4e5f58845480016108f3322fdec10714ca
\ No newline at end of file
harness/shots/baseline/darwin/issues-desktop-dark.sha256+1−1
@@ -1 +1 @@
93a19835ea718810296a179449177b3dbed688108e1ad0589a779c52409d71de
\ No newline at end of file
743947e8ebf32fb3b174f4330242d180f6ee2381bff8acf72b7ab6ece39b6b5b
\ No newline at end of file
harness/shots/baseline/darwin/issues-desktop-light.sha256+1−1
@@ -1 +1 @@
93a19835ea718810296a179449177b3dbed688108e1ad0589a779c52409d71de
\ No newline at end of file
743947e8ebf32fb3b174f4330242d180f6ee2381bff8acf72b7ab6ece39b6b5b
\ No newline at end of file
harness/shots/baseline/darwin/issues-phone-dark.sha256+1−1
@@ -1 +1 @@
93a19835ea718810296a179449177b3dbed688108e1ad0589a779c52409d71de
\ No newline at end of file
743947e8ebf32fb3b174f4330242d180f6ee2381bff8acf72b7ab6ece39b6b5b
\ No newline at end of file
harness/shots/baseline/darwin/issues-phone-light.sha256+1−1
@@ -1 +1 @@
93a19835ea718810296a179449177b3dbed688108e1ad0589a779c52409d71de
\ No newline at end of file
743947e8ebf32fb3b174f4330242d180f6ee2381bff8acf72b7ab6ece39b6b5b
\ No newline at end of file
harness/shots/baseline/darwin/markdown-desktop-dark.sha256+1−1
@@ -1 +1 @@
7016ab2327fa4487b19ff297005e3e5ef7531c8cc23e9244d4c966dcc7283ecc
\ No newline at end of file
b6ab77b3fbb7d46ab5de8d6e90a731f09c75184950441c3b851b085a31761fbc
\ No newline at end of file
harness/shots/baseline/darwin/markdown-desktop-light.sha256+1−1
@@ -1 +1 @@
7016ab2327fa4487b19ff297005e3e5ef7531c8cc23e9244d4c966dcc7283ecc
\ No newline at end of file
b6ab77b3fbb7d46ab5de8d6e90a731f09c75184950441c3b851b085a31761fbc
\ No newline at end of file
harness/shots/baseline/darwin/markdown-phone-dark.sha256+1−1
@@ -1 +1 @@
7016ab2327fa4487b19ff297005e3e5ef7531c8cc23e9244d4c966dcc7283ecc
\ No newline at end of file
b6ab77b3fbb7d46ab5de8d6e90a731f09c75184950441c3b851b085a31761fbc
\ No newline at end of file
harness/shots/baseline/darwin/markdown-phone-light.sha256+1−1
@@ -1 +1 @@
7016ab2327fa4487b19ff297005e3e5ef7531c8cc23e9244d4c966dcc7283ecc
\ No newline at end of file
b6ab77b3fbb7d46ab5de8d6e90a731f09c75184950441c3b851b085a31761fbc
\ No newline at end of file
harness/shots/baseline/darwin/missing-desktop-dark.sha256+1−1
@@ -1 +1 @@
f612e04cb5d93bd7862da07e31a1b19aa50e50eca25b6e86cffe32c5aa1440a0
\ No newline at end of file
cc05725c4b4c6f2c636984a48e167c9ae026974b2d429f1da4ba7387c8d6472a
\ No newline at end of file
harness/shots/baseline/darwin/missing-desktop-light.sha256+1−1
@@ -1 +1 @@
f612e04cb5d93bd7862da07e31a1b19aa50e50eca25b6e86cffe32c5aa1440a0
\ No newline at end of file
cc05725c4b4c6f2c636984a48e167c9ae026974b2d429f1da4ba7387c8d6472a
\ No newline at end of file
harness/shots/baseline/darwin/missing-phone-dark.sha256+1−1
@@ -1 +1 @@
f612e04cb5d93bd7862da07e31a1b19aa50e50eca25b6e86cffe32c5aa1440a0
\ No newline at end of file
cc05725c4b4c6f2c636984a48e167c9ae026974b2d429f1da4ba7387c8d6472a
\ No newline at end of file
harness/shots/baseline/darwin/missing-phone-light.sha256+1−1
@@ -1 +1 @@
f612e04cb5d93bd7862da07e31a1b19aa50e50eca25b6e86cffe32c5aa1440a0
\ No newline at end of file
cc05725c4b4c6f2c636984a48e167c9ae026974b2d429f1da4ba7387c8d6472a
\ No newline at end of file
harness/shots/baseline/darwin/profile-desktop-dark.sha256+1−1
@@ -1 +1 @@
fc5c802dc8471b25bf353d15f21572af0cc37672ac8aeb42f94864f33121388b
\ No newline at end of file
c0761a48e149c127f5fa11b7c6a9e5364b68906320ecabd2d239f90a342484a0
\ No newline at end of file
harness/shots/baseline/darwin/profile-desktop-light.sha256+1−1
@@ -1 +1 @@
fc5c802dc8471b25bf353d15f21572af0cc37672ac8aeb42f94864f33121388b
\ No newline at end of file
c0761a48e149c127f5fa11b7c6a9e5364b68906320ecabd2d239f90a342484a0
\ No newline at end of file
harness/shots/baseline/darwin/profile-fork-desktop-dark.sha256+1−1
@@ -1 +1 @@
d5da524a30d761af795d5640982bc9939a32bb974be6e76845dbd0bc49c617f1
\ No newline at end of file
0b985d5463606ab499740b541c0161571601bcc788647c0a58b3e0a2cad10d42
\ No newline at end of file
harness/shots/baseline/darwin/profile-fork-desktop-light.sha256+1−1
@@ -1 +1 @@
d5da524a30d761af795d5640982bc9939a32bb974be6e76845dbd0bc49c617f1
\ No newline at end of file
0b985d5463606ab499740b541c0161571601bcc788647c0a58b3e0a2cad10d42
\ No newline at end of file
harness/shots/baseline/darwin/profile-fork-phone-dark.sha256+1−1
@@ -1 +1 @@
d5da524a30d761af795d5640982bc9939a32bb974be6e76845dbd0bc49c617f1
\ No newline at end of file
0b985d5463606ab499740b541c0161571601bcc788647c0a58b3e0a2cad10d42
\ No newline at end of file
harness/shots/baseline/darwin/profile-fork-phone-light.sha256+1−1
@@ -1 +1 @@
d5da524a30d761af795d5640982bc9939a32bb974be6e76845dbd0bc49c617f1
\ No newline at end of file
0b985d5463606ab499740b541c0161571601bcc788647c0a58b3e0a2cad10d42
\ No newline at end of file
harness/shots/baseline/darwin/profile-phone-dark.sha256+1−1
@@ -1 +1 @@
fc5c802dc8471b25bf353d15f21572af0cc37672ac8aeb42f94864f33121388b
\ No newline at end of file
c0761a48e149c127f5fa11b7c6a9e5364b68906320ecabd2d239f90a342484a0
\ No newline at end of file
harness/shots/baseline/darwin/profile-phone-light.sha256+1−1
@@ -1 +1 @@
fc5c802dc8471b25bf353d15f21572af0cc37672ac8aeb42f94864f33121388b
\ No newline at end of file
c0761a48e149c127f5fa11b7c6a9e5364b68906320ecabd2d239f90a342484a0
\ No newline at end of file
harness/shots/baseline/darwin/pull-desktop-dark.sha256+1−1
@@ -1 +1 @@
9522bab29ee7e47a5d28c8490819c77793782acfb1d36462329a5381a326c0e2
\ No newline at end of file
0292f8464f05e73aa1a45729bba0c92ed3559755e5b61ecd161cef77360e6de2
\ No newline at end of file
harness/shots/baseline/darwin/pull-desktop-light.sha256+1−1
@@ -1 +1 @@
9522bab29ee7e47a5d28c8490819c77793782acfb1d36462329a5381a326c0e2
\ No newline at end of file
0292f8464f05e73aa1a45729bba0c92ed3559755e5b61ecd161cef77360e6de2
\ No newline at end of file
harness/shots/baseline/darwin/pull-open-desktop-dark.sha256+1−1
@@ -1 +1 @@
c04f1d8b084e3cffd4510f4d57fdcc3f0083b1fa844a3001d7220c7aa4852828
\ No newline at end of file
8b08f39ac139531ee3bd6f1b0e4902a76878a7f8242fb8b6bd2dcb50e083307b
\ No newline at end of file
harness/shots/baseline/darwin/pull-open-desktop-light.sha256+1−1
@@ -1 +1 @@
c04f1d8b084e3cffd4510f4d57fdcc3f0083b1fa844a3001d7220c7aa4852828
\ No newline at end of file
8b08f39ac139531ee3bd6f1b0e4902a76878a7f8242fb8b6bd2dcb50e083307b
\ No newline at end of file
harness/shots/baseline/darwin/pull-open-phone-dark.sha256+1−1
@@ -1 +1 @@
c04f1d8b084e3cffd4510f4d57fdcc3f0083b1fa844a3001d7220c7aa4852828
\ No newline at end of file
8b08f39ac139531ee3bd6f1b0e4902a76878a7f8242fb8b6bd2dcb50e083307b
\ No newline at end of file
harness/shots/baseline/darwin/pull-open-phone-light.sha256+1−1
@@ -1 +1 @@
c04f1d8b084e3cffd4510f4d57fdcc3f0083b1fa844a3001d7220c7aa4852828
\ No newline at end of file
8b08f39ac139531ee3bd6f1b0e4902a76878a7f8242fb8b6bd2dcb50e083307b
\ No newline at end of file
harness/shots/baseline/darwin/pull-phone-dark.sha256+1−1
@@ -1 +1 @@
9522bab29ee7e47a5d28c8490819c77793782acfb1d36462329a5381a326c0e2
\ No newline at end of file
0292f8464f05e73aa1a45729bba0c92ed3559755e5b61ecd161cef77360e6de2
\ No newline at end of file
harness/shots/baseline/darwin/pull-phone-light.sha256+1−1
@@ -1 +1 @@
9522bab29ee7e47a5d28c8490819c77793782acfb1d36462329a5381a326c0e2
\ No newline at end of file
0292f8464f05e73aa1a45729bba0c92ed3559755e5b61ecd161cef77360e6de2
\ No newline at end of file
harness/shots/baseline/darwin/pulls-closed-desktop-dark.sha256+1−1
@@ -1 +1 @@
215bf854102a2bab9e32b9df79049a57a000cb8368f959ad27f5faf745db13ce
\ No newline at end of file
3be90c357aa50fa9883b29bb497601758c35c0cbc9436af50ddf8bc162bde7ca
\ No newline at end of file
harness/shots/baseline/darwin/pulls-closed-desktop-light.sha256+1−1
@@ -1 +1 @@
215bf854102a2bab9e32b9df79049a57a000cb8368f959ad27f5faf745db13ce
\ No newline at end of file
3be90c357aa50fa9883b29bb497601758c35c0cbc9436af50ddf8bc162bde7ca
\ No newline at end of file
harness/shots/baseline/darwin/pulls-closed-phone-dark.sha256+1−1
@@ -1 +1 @@
215bf854102a2bab9e32b9df79049a57a000cb8368f959ad27f5faf745db13ce
\ No newline at end of file
3be90c357aa50fa9883b29bb497601758c35c0cbc9436af50ddf8bc162bde7ca
\ No newline at end of file
harness/shots/baseline/darwin/pulls-closed-phone-light.sha256+1−1
@@ -1 +1 @@
215bf854102a2bab9e32b9df79049a57a000cb8368f959ad27f5faf745db13ce
\ No newline at end of file
3be90c357aa50fa9883b29bb497601758c35c0cbc9436af50ddf8bc162bde7ca
\ No newline at end of file
harness/shots/baseline/darwin/pulls-desktop-dark.sha256+1−1
@@ -1 +1 @@
4e6b808a37d6cce49c6a489dd534bb15a71fce48e531114a9dedb19fd1e7c898
\ No newline at end of file
af1b06ab2147c2453ce04d7bb149f50d0a2a752a2b11e624f2da802e81438b33
\ No newline at end of file
harness/shots/baseline/darwin/pulls-desktop-light.sha256+1−1
@@ -1 +1 @@
4e6b808a37d6cce49c6a489dd534bb15a71fce48e531114a9dedb19fd1e7c898
\ No newline at end of file
af1b06ab2147c2453ce04d7bb149f50d0a2a752a2b11e624f2da802e81438b33
\ No newline at end of file
harness/shots/baseline/darwin/pulls-phone-dark.sha256+1−1
@@ -1 +1 @@
4e6b808a37d6cce49c6a489dd534bb15a71fce48e531114a9dedb19fd1e7c898
\ No newline at end of file
af1b06ab2147c2453ce04d7bb149f50d0a2a752a2b11e624f2da802e81438b33
\ No newline at end of file
harness/shots/baseline/darwin/pulls-phone-light.sha256+1−1
@@ -1 +1 @@
4e6b808a37d6cce49c6a489dd534bb15a71fce48e531114a9dedb19fd1e7c898
\ No newline at end of file
af1b06ab2147c2453ce04d7bb149f50d0a2a752a2b11e624f2da802e81438b33
\ No newline at end of file
harness/shots/baseline/darwin/repository-desktop-dark.sha256+1−1
@@ -1 +1 @@
5dc644fccc12f6508f7a371039e28b183d526fdff6eefc91adb49799c94bf63d
\ No newline at end of file
b058bc48e648af1a58c4ecdb576ff70cfc3d24a7a14b930d827d3c7c5087eb3e
\ No newline at end of file
harness/shots/baseline/darwin/repository-desktop-light.sha256+1−1
@@ -1 +1 @@
5dc644fccc12f6508f7a371039e28b183d526fdff6eefc91adb49799c94bf63d
\ No newline at end of file
b058bc48e648af1a58c4ecdb576ff70cfc3d24a7a14b930d827d3c7c5087eb3e
\ No newline at end of file
harness/shots/baseline/darwin/repository-phone-dark.sha256+1−1
@@ -1 +1 @@
5dc644fccc12f6508f7a371039e28b183d526fdff6eefc91adb49799c94bf63d
\ No newline at end of file
b058bc48e648af1a58c4ecdb576ff70cfc3d24a7a14b930d827d3c7c5087eb3e
\ No newline at end of file
harness/shots/baseline/darwin/repository-phone-light.sha256+1−1
@@ -1 +1 @@
5dc644fccc12f6508f7a371039e28b183d526fdff6eefc91adb49799c94bf63d
\ No newline at end of file
b058bc48e648af1a58c4ecdb576ff70cfc3d24a7a14b930d827d3c7c5087eb3e
\ No newline at end of file
harness/shots/baseline/darwin/search-desktop-dark.sha256+1−1
@@ -1 +1 @@
3a7af9542d2b8cd7dc09b5e7d242f1f413fe109635b0ec376b2c0ebc42a8c49b
\ No newline at end of file
518ddae04caeb665e46b4c19ff0ac8d7895202d1f92702e3a8f6f65b2ae83ed6
\ No newline at end of file
harness/shots/baseline/darwin/search-desktop-light.sha256+1−1
@@ -1 +1 @@
3a7af9542d2b8cd7dc09b5e7d242f1f413fe109635b0ec376b2c0ebc42a8c49b
\ No newline at end of file
518ddae04caeb665e46b4c19ff0ac8d7895202d1f92702e3a8f6f65b2ae83ed6
\ No newline at end of file
harness/shots/baseline/darwin/search-phone-dark.sha256+1−1
@@ -1 +1 @@
3a7af9542d2b8cd7dc09b5e7d242f1f413fe109635b0ec376b2c0ebc42a8c49b
\ No newline at end of file
518ddae04caeb665e46b4c19ff0ac8d7895202d1f92702e3a8f6f65b2ae83ed6
\ No newline at end of file
harness/shots/baseline/darwin/search-phone-light.sha256+1−1
@@ -1 +1 @@
3a7af9542d2b8cd7dc09b5e7d242f1f413fe109635b0ec376b2c0ebc42a8c49b
\ No newline at end of file
518ddae04caeb665e46b4c19ff0ac8d7895202d1f92702e3a8f6f65b2ae83ed6
\ No newline at end of file
harness/shots/baseline/darwin/start-desktop-dark.sha256+1−1
@@ -1 +1 @@
b5ac0252161d9cd6730be57892053351a88e7a16d13704fb1e682109834b98de
\ No newline at end of file
7f6d6bc4fc4ed315b7f7fad13181bd0adec610bb4eb0bb00c1f667bca0039c2e
\ No newline at end of file
harness/shots/baseline/darwin/start-desktop-light.sha256+1−1
@@ -1 +1 @@
b5ac0252161d9cd6730be57892053351a88e7a16d13704fb1e682109834b98de
\ No newline at end of file
7f6d6bc4fc4ed315b7f7fad13181bd0adec610bb4eb0bb00c1f667bca0039c2e
\ No newline at end of file
harness/shots/baseline/darwin/start-phone-dark.sha256+1−1
@@ -1 +1 @@
b5ac0252161d9cd6730be57892053351a88e7a16d13704fb1e682109834b98de
\ No newline at end of file
7f6d6bc4fc4ed315b7f7fad13181bd0adec610bb4eb0bb00c1f667bca0039c2e
\ No newline at end of file
harness/shots/baseline/darwin/start-phone-light.sha256+1−1
@@ -1 +1 @@
b5ac0252161d9cd6730be57892053351a88e7a16d13704fb1e682109834b98de
\ No newline at end of file
7f6d6bc4fc4ed315b7f7fad13181bd0adec610bb4eb0bb00c1f667bca0039c2e
\ No newline at end of file
harness/shots/fixtures.json+19−19
@@ -14,12 +14,12 @@
"commit": "/alice.test/tally/commit/97727c2c19d5d8b3849b2ed9ad47130cee9798f5",
"pulls": "/alice.test/tally/pulls",
"pulls-closed": "/alice.test/tally/pulls?state=closed",
"pull": "/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iaenkpxm",
"pull-open": "/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iblvbjf4",
"pull": "/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7ltvdaqlc",
"pull-open": "/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqbsoys",
"issues": "/alice.test/tally/issues",
"issues-closed": "/alice.test/tally/issues?state=closed",
"issue": "/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibo7p2f5",
"issue-closed": "/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibstrlfw",
"issue": "/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqyfmyu",
"issue-closed": "/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7lusdxeyx",
"fork": "/bob.test/tally",
"empty": "/alice.test/scratch",
"missing": "/alice.test/tally/blob/main/nowhere"
@@ -31,7 +31,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"Alice Moreau\",\"bio\":\"Small tools for text. Writes tally.\",\"about\":\"Small tools for text. Writes tally.\",\"repos\":[{\"owner\":\"alice.test\",\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"createdAt\":\"2026-10-09T10:00:00.000Z\",\"private\":false}]}"
"body": "{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"Alice Moreau\",\"bio\":\"Small tools for text. Writes tally.\",\"avatar\":null,\"about\":\"Small tools for text. Writes tally.\",\"repos\":[{\"owner\":\"alice.test\",\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"createdAt\":\"2026-10-09T10:00:00.000Z\",\"private\":false}]}"
},
"/_data/alice.test/scratch": {
"status": 200,
@@ -97,21 +97,21 @@
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"rev\":\"main\",\"path\":\"\",\"page\":0,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"parents\":[\"97727c2c19d5d8b3849b2ed9ad47130cee9798f5\"],\"author\":\"Bob Tanaka\",\"date\":\"2026-10-09T09:31:00+00:00\",\"message\":\"Count no lines in empty input\"},{\"sha\":\"97727c2c19d5d8b3849b2ed9ad47130cee9798f5\",\"parents\":[\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\"],\"author\":\"Alice Moreau\",\"date\":\"2026-10-08T11:47:00+00:00\",\"message\":\"Split parsing into its own module\"},{\"sha\":\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\",\"parents\":[\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\"],\"author\":\"Alice Moreau\",\"date\":\"2026-10-06T10:02:00+00:00\",\"message\":\"Document usage and output\"},{\"sha\":\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\",\"parents\":[\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\"],\"author\":\"Alice Moreau\",\"date\":\"2026-10-05T17:21:00+00:00\",\"message\":\"Add a logo\"},{\"sha\":\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\",\"parents\":[\"a7e467c6f9acd7b1be0603afdec0b8cfdcf3cf49\"],\"author\":\"Alice Moreau\",\"date\":\"2026-10-03T08:05:00+00:00\",\"message\":\"Read from stdin when no file is given\\n\\nSo tally works at the end of a pipe, as wc does. The name shown\\nfor stdin is \\\"-\\\".\"},{\"sha\":\"a7e467c6f9acd7b1be0603afdec0b8cfdcf3cf49\",\"parents\":[\"cac02c464843db4d55fecfa69d327ef7b12702c0\"],\"author\":\"Alice Moreau\",\"date\":\"2026-10-02T14:40:00+00:00\",\"message\":\"Count words, lines and bytes\"},{\"sha\":\"cac02c464843db4d55fecfa69d327ef7b12702c0\",\"parents\":[],\"author\":\"Alice Moreau\",\"date\":\"2026-10-01T09:12:00+00:00\",\"message\":\"Start tally\"}],\"more\":false}"
},
"/_data/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibo7p2f5": {
"/_data/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqyfmyu": {
"status": 200,
"headers": {
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"title\":\"Fails on files that are not UTF-8\",\"state\":\"open\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"base\":null,\"from\":null,\"branch\":null,\"body\":\"<p>Running <code>tally image.png</code> panics:</p>\\n<pre><code>thread 'main' panicked at src/main.rs:8:56:\\nstream did not contain valid UTF-8\\n</code></pre>\\n<p>wc counts bytes for any file.</p>\\n\",\"comments\":[{\"author\":\"alice.test\",\"createdAt\":\"2026-10-10T00:11:00.000Z\",\"html\":\"<p>Confirmed. Reading bytes and counting words lossily would fix it.</p>\\n\",\"verdict\":null}],\"merged\":null,\"about\":\"Running `tally image.png` panics: ``` thread 'main' panicked at src/main.rs:8:56: stream did not contain valid UTF-8 ``` wc counts bytes for any file.\",\"card\":\"/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibo7p2f5/card.png?v=160a3e2db8db2127\"}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"title\":\"Fails on files that are not UTF-8\",\"state\":\"open\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"base\":null,\"from\":null,\"branch\":null,\"body\":\"<p>Running <code>tally image.png</code> panics:</p>\\n<pre><code>thread 'main' panicked at src/main.rs:8:56:\\nstream did not contain valid UTF-8\\n</code></pre>\\n<p>wc counts bytes for any file.</p>\\n\",\"comments\":[{\"author\":\"alice.test\",\"createdAt\":\"2026-10-10T00:11:00.000Z\",\"html\":\"<p>Confirmed. Reading bytes and counting words lossily would fix it.</p>\\n\",\"verdict\":null}],\"merged\":null,\"about\":\"Running `tally image.png` panics: ``` thread 'main' panicked at src/main.rs:8:56: stream did not contain valid UTF-8 ``` wc counts bytes for any file.\",\"card\":\"/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqyfmyu/card.png?v=160a3e2db8db2127\"}"
},
"/_data/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibstrlfw": {
"/_data/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7lusdxeyx": {
"status": 200,
"headers": {
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"base\":null,\"from\":null,\"branch\":null,\"body\":null,\"comments\":[],\"merged\":null,\"about\":\"An issue on alice.test/tally by bob.test.\",\"card\":\"/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibstrlfw/card.png?v=72fe47df61de13f9\"}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"base\":null,\"from\":null,\"branch\":null,\"body\":null,\"comments\":[],\"merged\":null,\"about\":\"An issue on alice.test/tally by bob.test.\",\"card\":\"/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7lusdxeyx/card.png?v=72fe47df61de13f9\"}"
},
"/_data/alice.test/tally/issues": {
"status": 200,
@@ -119,7 +119,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"open\":true,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Fails on files that are not UTF-8\",\"state\":\"open\",\"href\":\"/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibo7p2f5\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"comments\":1}]}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"open\":true,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Fails on files that are not UTF-8\",\"state\":\"open\",\"href\":\"/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqyfmyu\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"comments\":1}]}"
},
"/_data/alice.test/tally/issues?state=closed": {
"status": 200,
@@ -127,23 +127,23 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"open\":false,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Typo in the README ✏️\",\"state\":\"closed\",\"href\":\"/alice.test/tally/issue/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2ibstrlfw\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"comments\":0}]}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"issue\",\"open\":false,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Typo in the README ✏️\",\"state\":\"closed\",\"href\":\"/alice.test/tally/issue/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7lusdxeyx\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"comments\":0}]}"
},
"/_data/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iaenkpxm": {
"/_data/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7ltvdaqlc": {
"status": 200,
"headers": {
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"base\":\"main\",\"from\":\"bob.test/tally\",\"branch\":\"empty-input\",\"body\":\"<p><code>tally</code> printed one line for an empty file. <code>lines</code> now yields nothing when the text is empty, and a test covers it.</p>\\n\",\"comments\":[{\"author\":\"alice.test\",\"createdAt\":\"2026-10-09T19:15:00.000Z\",\"html\":\"<p>Could <code>lines</code> check for emptiness once, rather than in the filter?</p>\\n\",\"verdict\":\"changes\"},{\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"html\":\"<p>It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.</p>\\n\",\"verdict\":null},{\"author\":\"alice.test\",\"createdAt\":\"2026-10-09T20:29:00.000Z\",\"html\":\"<p>Fair enough. Thanks!</p>\\n\",\"verdict\":\"approve\"}],\"merged\":\"686a36205b82620af07dd99997c570f04f461df9\",\"about\":\"`tally` printed one line for an empty file. `lines` now yields nothing when the text is empty, and a test covers it.\",\"card\":\"/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iaenkpxm/card.png?v=7f555a00efc7106b\"}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"base\":\"main\",\"from\":\"bob.test/tally\",\"branch\":\"empty-input\",\"body\":\"<p><code>tally</code> printed one line for an empty file. <code>lines</code> now yields nothing when the text is empty, and a test covers it.</p>\\n\",\"comments\":[{\"author\":\"alice.test\",\"createdAt\":\"2026-10-09T19:15:00.000Z\",\"html\":\"<p>Could <code>lines</code> check for emptiness once, rather than in the filter?</p>\\n\",\"verdict\":\"changes\"},{\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"html\":\"<p>It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.</p>\\n\",\"verdict\":null},{\"author\":\"alice.test\",\"createdAt\":\"2026-10-09T20:29:00.000Z\",\"html\":\"<p>Fair enough. Thanks!</p>\\n\",\"verdict\":\"approve\"}],\"merged\":\"686a36205b82620af07dd99997c570f04f461df9\",\"about\":\"`tally` printed one line for an empty file. `lines` now yields nothing when the text is empty, and a test covers it.\",\"card\":\"/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7ltvdaqlc/card.png?v=7f555a00efc7106b\"}"
},
"/_data/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iblvbjf4": {
"/_data/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqbsoys": {
"status": 200,
"headers": {
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"title\":\"Print counts as JSON with --json\",\"state\":\"open\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"base\":\"main\",\"from\":\"bob.test/tally\",\"branch\":\"json\",\"body\":\"<p>For scripts. The object has <code>lines</code>, <code>words</code>, <code>bytes</code> and <code>name</code>.</p>\\n\",\"comments\":[],\"merged\":null,\"about\":\"For scripts. The object has `lines`, `words`, `bytes` and `name`.\",\"card\":\"/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iblvbjf4/card.png?v=029fd2cd417c8b94\"}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"title\":\"Print counts as JSON with --json\",\"state\":\"open\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"base\":\"main\",\"from\":\"bob.test/tally\",\"branch\":\"json\",\"body\":\"<p>For scripts. The object has <code>lines</code>, <code>words</code>, <code>bytes</code> and <code>name</code>.</p>\\n\",\"comments\":[],\"merged\":null,\"about\":\"For scripts. The object has `lines`, `words`, `bytes` and `name`.\",\"card\":\"/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqbsoys/card.png?v=029fd2cd417c8b94\"}"
},
"/_data/alice.test/tally/pulls": {
"status": 200,
@@ -151,7 +151,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"open\":true,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Print counts as JSON with --json\",\"state\":\"open\",\"href\":\"/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iblvbjf4\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"comments\":0}]}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"open\":true,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Print counts as JSON with --json\",\"state\":\"open\",\"href\":\"/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7luqbsoys\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"comments\":0}]}"
},
"/_data/alice.test/tally/pulls?state=closed": {
"status": 200,
@@ -159,7 +159,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"open\":false,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Count no lines in empty input\",\"state\":\"merged\",\"href\":\"/alice.test/tally/pull/did:plc:gr2tavtgbdy4zqtlwnzlgzdc/3myg2iaenkpxm\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"comments\":3}]}"
"body": "{\"repo\":{\"authority\":\"alice.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"head\":\"main\",\"pulls\":1,\"issues\":1,\"about\":\"Counts lines, words and bytes, with output that lines up\",\"card\":\"/alice.test/tally/card.png?v=715ad689e9f7ec63\"},\"kind\":\"pull\",\"open\":false,\"opened\":1,\"closed\":1,\"items\":[{\"title\":\"Count no lines in empty input\",\"state\":\"merged\",\"href\":\"/alice.test/tally/pull/did:plc:22yzhoyrfxuuax3knxdw3t72/3myg7ltvdaqlc\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"comments\":3}]}"
},
"/_data/alice.test/tally/tree/main/src": {
"status": 200,
@@ -175,7 +175,7 @@
"content-type": "application/json",
"cache-control": "no-cache"
},
"body": "{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\",\"name\":null,\"bio\":null,\"about\":null,\"repos\":[{\"owner\":\"bob.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":{\"shown\":\"alice.test/tally\",\"href\":\"/alice.test/tally\"},\"createdAt\":\"2026-10-09T14:19:00.000Z\",\"private\":false}]}"
"body": "{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\",\"name\":null,\"bio\":null,\"avatar\":null,\"about\":null,\"repos\":[{\"owner\":\"bob.test\",\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":{\"shown\":\"alice.test/tally\",\"href\":\"/alice.test/tally\"},\"createdAt\":\"2026-10-09T14:19:00.000Z\",\"private\":false}]}"
},
"/_data/bob.test/tally": {
"status": 200,
@@ -216,7 +216,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"events\":[{\"createdAt\":\"2026-10-10T01:25:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.issue/3myg2ibstrlfw\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\"}},{\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.issue/3myg2ibstrlfw\",\"title\":\"Typo in the README ✏️\"}},{\"createdAt\":\"2026-10-10T00:11:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.issue/3myg2ibo7p2f5\",\"title\":\"Fails on files that are not UTF-8\",\"text\":\"Confirmed. Reading bytes and counting words lossily would fix it.\"}},{\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.issue/3myg2ibo7p2f5\",\"title\":\"Fails on files that are not UTF-8\"}},{\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iblvbjf4\",\"title\":\"Print counts as JSON with --json\"}},{\"createdAt\":\"2026-10-09T22:20:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iaenkpxm\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\"}},{\"createdAt\":\"2026-10-09T21:06:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":false,\"count\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T20:29:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iaenkpxm\",\"title\":\"Count no lines in empty input\",\"text\":\"Fair enough. Thanks!\",\"verdict\":\"approve\"}},{\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iaenkpxm\",\"title\":\"Count no lines in empty input\",\"text\":\"It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.\"}},{\"createdAt\":\"2026-10-09T19:15:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iaenkpxm\",\"title\":\"Count no lines in empty input\",\"text\":\"Could `lines` check for emptiness once, rather than in the filter?\",\"verdict\":\"changes\"}},{\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:gr2tavtgbdy4zqtlwnzlgzdc/dev.knotless.pull/3myg2iaenkpxm\",\"title\":\"Count no lines in empty input\"}},{\"createdAt\":\"2026-10-09T17:24:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/json\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"2063dabcda7417d5831f3b6bfd8ffb18da82fb8e\",\"message\":\"Print counts as JSON with --json\"}]}},{\"createdAt\":\"2026-10-09T16:10:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/empty-input\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T14:19:00.000Z\",\"actor\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:gr2tavtgbdy4zqtlwnzlgzdc\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"}}},{\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"scratch\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\"}},{\"createdAt\":\"2026-10-09T12:28:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"e78543abf0f799b7eb5713ccdf8d88b84d0f8ec0\",\"message\":\"Begin\"}]}},{\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Scratch notes 📝\"}},{\"createdAt\":\"2026-10-09T10:37:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":6,\"commits\":[{\"sha\":\"97727c2c19d5d8b3849b2ed9ad47130cee9798f5\",\"message\":\"Split parsing into its own module\"},{\"sha\":\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\",\"message\":\"Document usage and output\"},{\"sha\":\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\",\"message\":\"Add a logo\"},{\"sha\":\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\",\"message\":\"Read from stdin when no file is given\\n\\nSo tally works at the end of a pipe, as wc does. The name shown\\nfor stdin is \\\"-\\\".\"},{\"sha\":\"a7e467c6f9acd7b1be0603afdec0b8cfdcf3cf49\",\"message\":\"Count words, lines and bytes\"},{\"sha\":\"cac02c464843db4d55fecfa69d327ef7b12702c0\",\"message\":\"Start tally\"}]}},{\"createdAt\":\"2026-10-09T10:00:00.000Z\",\"actor\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:fx5ba2ixb2z4obfxhstbwga6\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Counts lines, words and bytes, with output that lines up\"}}]}"
"body": "{\"events\":[{\"createdAt\":\"2026-10-10T01:25:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.issue/3myg7lusdxeyx\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\"}},{\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.issue/3myg7lusdxeyx\",\"title\":\"Typo in the README ✏️\"}},{\"createdAt\":\"2026-10-10T00:11:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.issue/3myg7luqyfmyu\",\"title\":\"Fails on files that are not UTF-8\",\"text\":\"Confirmed. Reading bytes and counting words lossily would fix it.\"}},{\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.issue/3myg7luqyfmyu\",\"title\":\"Fails on files that are not UTF-8\"}},{\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7luqbsoys\",\"title\":\"Print counts as JSON with --json\"}},{\"createdAt\":\"2026-10-09T22:20:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7ltvdaqlc\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\"}},{\"createdAt\":\"2026-10-09T21:06:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":false,\"count\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T20:29:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7ltvdaqlc\",\"title\":\"Count no lines in empty input\",\"text\":\"Fair enough. Thanks!\",\"verdict\":\"approve\"}},{\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7ltvdaqlc\",\"title\":\"Count no lines in empty input\",\"text\":\"It only runs per line, so the cost is one comparison each. Happy to change it if you prefer.\"}},{\"createdAt\":\"2026-10-09T19:15:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7ltvdaqlc\",\"title\":\"Count no lines in empty input\",\"text\":\"Could `lines` check for emptiness once, rather than in the filter?\",\"verdict\":\"changes\"}},{\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:22yzhoyrfxuuax3knxdw3t72/dev.knotless.pull/3myg7ltvdaqlc\",\"title\":\"Count no lines in empty input\"}},{\"createdAt\":\"2026-10-09T17:24:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/json\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"2063dabcda7417d5831f3b6bfd8ffb18da82fb8e\",\"message\":\"Print counts as JSON with --json\"}]}},{\"createdAt\":\"2026-10-09T16:10:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/empty-input\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T14:19:00.000Z\",\"actor\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:22yzhoyrfxuuax3knxdw3t72\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"}}},{\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"scratch\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\"}},{\"createdAt\":\"2026-10-09T12:28:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":1,\"commits\":[{\"sha\":\"e78543abf0f799b7eb5713ccdf8d88b84d0f8ec0\",\"message\":\"Begin\"}]}},{\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Scratch notes 📝\"}},{\"createdAt\":\"2026-10-09T10:37:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":6,\"commits\":[{\"sha\":\"97727c2c19d5d8b3849b2ed9ad47130cee9798f5\",\"message\":\"Split parsing into its own module\"},{\"sha\":\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\",\"message\":\"Document usage and output\"},{\"sha\":\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\",\"message\":\"Add a logo\"},{\"sha\":\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\",\"message\":\"Read from stdin when no file is given\\n\\nSo tally works at the end of a pipe, as wc does. The name shown\\nfor stdin is \\\"-\\\".\"},{\"sha\":\"a7e467c6f9acd7b1be0603afdec0b8cfdcf3cf49\",\"message\":\"Count words, lines and bytes\"},{\"sha\":\"cac02c464843db4d55fecfa69d327ef7b12702c0\",\"message\":\"Start tally\"}]}},{\"createdAt\":\"2026-10-09T10:00:00.000Z\",\"actor\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:3ygt44h3csutc5c7xfpkp45l\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Counts lines, words and bytes, with output that lines up\"}}]}"
}
}
}
web/src/app.css+1−0
@@ -24,6 +24,7 @@ code,pre,.mono{font-family:var(--mono);font-size:12.5px}
.end{margin-left:auto}
svg.i{width:16px;height:16px;flex:none;fill:currentColor;vertical-align:-3px}
.av{display:inline-grid;place-items:center;flex:none;width:20px;height:20px;border-radius:50%;font:600 10px var(--sans);color:#fff;background:hsl(var(--h) 45% 52%);text-transform:uppercase}
img.av{object-fit:cover;background:var(--soft)}
.av.lg{width:32px;height:32px;font-size:13px}
.av.xl{width:88px;height:88px;font-size:34px;font-weight:600}
.profile{display:flex;align-items:center;gap:1.5rem;margin:.75rem 0 2.25rem;min-width:0}
web/src/lib/api/Profile.ts+4−0
@@ -7,6 +7,10 @@ import type { Listed } from "./Listed";
* `--private`, the account logged in sees its private ones too.
*/
export type Profile = { did: string, handle: string, name: string | null, bio: string | null,
/**
* Its profile picture, as Bluesky's CDN serves it, if it has one.
*/
avatar: string | null,
/**
* Its bio on one line, for whatever unfurls a link to it.
*/
web/src/lib/atoms/Avatar.svelte+4−3
@@ -1,7 +1,8 @@
<script lang="ts">
import { hue, initial } from '#lib/format.ts';
/** A round mark with a name's first letter, in a color its name picks. */
let { name, size }: { name: string; size?: 'lg' | 'xl' } = $props();
/** A round mark: the picture at `src`, or else a name's first letter, in a color its name picks. */
let { name, size, src }: { name: string; size?: 'lg' | 'xl'; src?: string | null } = $props();
let cls = $derived(size ? `av ${size}` : 'av');
</script>
<span class={size ? `av ${size}` : 'av'} style="--h:{hue(name)}" aria-hidden="true">{initial(name)}</span>
{#if src}<img class={cls} {src} alt="" />{:else}<span class={cls} style="--h:{hue(name)}" aria-hidden="true">{initial(name)}</span>{/if}
web/src/lib/organisms/ProfileHeader.svelte+1−1
@@ -6,7 +6,7 @@
</script>
<section class="profile">
<Avatar name={p.handle} size="xl" />
<Avatar name={p.handle} size="xl" src={p.avatar} />
<div>
<h1>{p.name ?? p.handle}</h1>
{#if p.name}<p class="who">{p.handle}</p>{/if}