chadtmiller.com/knotless
Git hosted in your atproto account, on any stock PDS, fast enough for agents.
git clone knotless://chadtmiller.com/knotlessneeds the knotless git helper. set it up
Feed: pushes to a branch within an hour shown as one, and each commit once
A run of pushes by one account to one ref, each within an hour of the
last, is one event. Each commit is listed once on a page, by the earliest
event that brought it, and only when no other ref of its repository had
it before: a push to main of what a branch already held counts those
commits ("and 1 on another branch") rather than listing them again. A
pushed event says how many of its commits are new (new), beside count.
The screenshots' world has a branch that main then moved past, to show
it. Each checkout's shots have three ports of their own, from its path,
so two sessions' runs no longer take each other's; the commands on the
getting started page that name this server are covered in its shots, as
they name the port. And a file's raw answer at a commit hash, cached for
a year, is tested again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Showing 132 changed files with 388 additions and 165 deletions
CLAUDE.md+2−3
| @@ -21,9 +21,8 @@ work is done. | ||
no PDS. Look at the PNGs it writes under | ||
`harness/shots/baseline/<platform>/`. After a change to what a page's | ||
answer holds, `just shots-up` in the background once, then `just | ||
shots-record`. If another checkout's shots hold the ports (2593 to | ||
2595), set `SHOTS_PORT` to another base for all of these, though | ||
`start` shows its port and will not match on another. | ||
shots-record`. Each checkout's shots have three ports of their own, | ||
from its path; `SHOTS_PORT` sets another base for all of these. | ||
- `shots-check` and `shots-live` take only pages whose HTML or browser | ||
changed since their baselines, as `shots` does; `SHOTS_ALL=1` takes | ||
every one. Keep `just shots-up` running in the background while | ||
crates/knotless-serve/src/index.rs+53−0
| @@ -515,6 +515,30 @@ impl Index { | ||
Ok(tip.flatten()) | ||
} | ||
| ||
/// Where each of the repository at `repo`'s refs but `refname` was | ||
/// just before the push `uri` at `at`, as far as the index saw; a ref | ||
/// deleted by then, where it was when it was. | ||
pub fn tips_before( | ||
&self, | ||
repo: &str, | ||
refname: &str, | ||
at: &str, | ||
uri: &str, | ||
) -> rusqlite::Result<Vec<String>> { | ||
let db = self.db.lock().unwrap(); | ||
let mut stmt = db.prepare_cached( | ||
"SELECT COALESCE(p.after, p.before), MAX(p.created_at || ' ' || p.uri) | ||
FROM push p JOIN repo r ON r.did = p.did AND r.key = p.key | ||
WHERE r.uri = ?1 AND p.ref != ?2 | ||
AND (p.created_at < ?3 OR (p.created_at = ?3 AND p.uri < ?4)) | ||
GROUP BY p.ref", | ||
)?; | ||
let tips: Vec<Option<String>> = stmt | ||
.query_map([repo, refname, at, uri], tip)? | ||
.collect::<rusqlite::Result<_>>()?; | ||
Ok(tips.into_iter().flatten().collect()) | ||
} | ||
| ||
/// Forks of `did`'s repository `key`. | ||
pub fn forks(&self, did: &str, key: &str) -> rusqlite::Result<Vec<Listed>> { | ||
let db = self.db.lock().unwrap(); | ||
| @@ -565,6 +589,10 @@ fn push(row: &rusqlite::Row) -> rusqlite::Result<Push> { | ||
}) | ||
} | ||
| ||
fn tip(row: &rusqlite::Row) -> rusqlite::Result<Option<String>> { | ||
row.get(0) | ||
} | ||
| ||
fn recent(row: &rusqlite::Row) -> rusqlite::Result<(Entry, String, String)> { | ||
Ok((entry(row)?, row.get(3)?, row.get(4)?)) | ||
} | ||
| @@ -847,6 +875,31 @@ mod tests { | ||
let all = index.pushes(10, None).unwrap(); | ||
let at = cursor_at(&all[0].created_at, &all[0].uri); | ||
assert_eq!(index.pushes(10, Some(&at)).unwrap(), all[1..]); | ||
// The other refs' tips, a deleted one's where it was. | ||
let side = |rkey: &str, c: Value| { | ||
let mut c = c; | ||
c["repo"] = json!("k"); | ||
c["name"] = json!("refs/heads/side"); | ||
index.put("did:a", nsid::CLAIM, rkey, &c).unwrap(); | ||
}; | ||
side( | ||
"k.s.0", | ||
json!({"to": "s1", "createdAt": "2026-01-02T00:00:00Z"}), | ||
); | ||
let others = |at| index.tips_before(uri, main, at, "z").unwrap(); | ||
assert_eq!(others("2026-01-02T00:00:00Z"), ["s1"]); | ||
assert!(others("2026-01-01T00:00:00Z").is_empty()); | ||
assert!( | ||
index | ||
.tips_before(uri, "refs/heads/side", "2026-01-02T00:00:00Z", "a") | ||
.unwrap() | ||
.is_empty() | ||
); | ||
side( | ||
"k.s.1", | ||
json!({"from": "s1", "createdAt": "2026-01-03T06:00:00Z"}), | ||
); | ||
assert_eq!(others("2026-01-04T00:00:00Z"), ["s1"]); | ||
| ||
// Pulls, issues and what was said, newest first, without what the | ||
// owner hides or blocks, or those records themselves. | ||
crates/knotless-serve/src/web/feed.rs+142−23
| @@ -2,7 +2,7 @@ | ||
//! knows, newest first. The front page shows it. | ||
| ||
use super::*; | ||
use crate::index::{Index, cursor_at}; | ||
use crate::index::{Index, Push, cursor_at}; | ||
use knotless::records::nsid; | ||
use std::collections::HashSet; | ||
| ||
| @@ -13,6 +13,8 @@ const LIMIT: usize = 40; | ||
const WALKED: usize = 100; | ||
/// Commits a push lists; the rest are counted. | ||
const LISTED: usize = 10; | ||
/// Seconds between pushes to a ref that are shown as one. | ||
const RUN: i64 = 60 * 60; | ||
| ||
#[derive(Serialize, TS)] | ||
#[ts(export, rename = "Feed")] | ||
| @@ -72,13 +74,15 @@ pub enum Action { | ||
#[ts(optional)] | ||
fork_of: Option<RepoView>, | ||
}, | ||
/// A ref made or moved, and the newest of the commits that brought. | ||
/// A ref made or moved, and the newest of the commits that brought | ||
/// which no other ref had first. | ||
#[serde(rename = "dev.knotless.getFeed#pushed")] | ||
Pushed { | ||
#[serde(rename = "ref")] | ||
refname: String, | ||
created: bool, | ||
count: usize, | ||
new: usize, | ||
commits: Vec<CommitView>, | ||
}, | ||
#[serde(rename = "dev.knotless.getFeed#deleted")] | ||
| @@ -257,11 +261,15 @@ async fn events( | ||
.into_iter() | ||
.filter_map(|(at, repo)| Some((at, repo.ok()?))) | ||
.collect(); | ||
for p in &pushes { | ||
let mirror = mirrors.get(&(p.did.clone(), p.name.clone())); | ||
let action = match &p.to { | ||
// Each commit is listed once, by the earliest run on the page to bring | ||
// it that no other ref of its repository had it before. | ||
let mut listed: HashSet<(&str, String)> = HashSet::new(); | ||
for run in runs(&pushes).iter().rev() { | ||
let (last, first) = (run[0], run[run.len() - 1]); | ||
let mirror = mirrors.get(&(last.did.clone(), last.name.clone())); | ||
let action = match &last.to { | ||
None => Action::Deleted { | ||
refname: p.refname.clone(), | ||
refname: last.refname.clone(), | ||
}, | ||
Some(to) => { | ||
// A ref that moved brought what it did not reach before; a | ||
| @@ -270,12 +278,12 @@ async fn events( | ||
// a fork's is named for. | ||
let head = mirror.map(|r| r.head.as_str()).unwrap_or_default(); | ||
let branch = format!("refs/heads/{head}"); | ||
let before = |at: &str| index.tip_before(at, &branch, &p.created_at); | ||
let known: Vec<String> = match &p.from { | ||
let before = |at: &str| index.tip_before(at, &branch, &first.created_at); | ||
let known: Vec<String> = match &first.from { | ||
Some(from) => vec![from.clone()], | ||
None => [ | ||
(branch != p.refname).then_some(p.repo.as_str()), | ||
p.fork_of.as_deref(), | ||
(branch != first.refname).then_some(first.repo.as_str()), | ||
first.fork_of.as_deref(), | ||
] | ||
.into_iter() | ||
.flatten() | ||
| @@ -287,20 +295,33 @@ async fn events( | ||
.flatten() | ||
.collect(), | ||
}; | ||
let known: Vec<&str> = known.iter().map(String::as_str).collect(); | ||
let brought = mirror | ||
.and_then(|r| r.objects().ok()) | ||
.and_then(|o| o.brought(to, &known, WALKED).ok()) | ||
.unwrap_or_default(); | ||
let others = index | ||
.tips_before(&first.repo, &first.refname, &first.created_at, &first.uri) | ||
.map_err(anyhow::Error::from) | ||
.map_err(AppError::internal)?; | ||
let walk = |known: &[&str]| { | ||
mirror | ||
.and_then(|r| r.objects().ok()) | ||
.and_then(|o| o.brought(to, known, WALKED).ok()) | ||
.unwrap_or_default() | ||
}; | ||
let mut known: Vec<&str> = known.iter().map(String::as_str).collect(); | ||
let brought = walk(&known); | ||
// A fork's copy of its source's refs is the fork itself. | ||
if p.fork_of.is_some() && p.from.is_none() && brought.is_empty() { | ||
if first.fork_of.is_some() && first.from.is_none() && brought.is_empty() { | ||
continue; | ||
} | ||
known.extend(others.iter().map(String::as_str)); | ||
let new: Vec<_> = walk(&known) | ||
.into_iter() | ||
.filter(|c| listed.insert((last.repo.as_str(), c.sha.clone()))) | ||
.collect(); | ||
Action::Pushed { | ||
refname: p.refname.clone(), | ||
created: p.from.is_none(), | ||
refname: last.refname.clone(), | ||
created: first.from.is_none(), | ||
count: brought.len(), | ||
commits: brought | ||
new: new.len(), | ||
commits: new | ||
.into_iter() | ||
.take(LISTED) | ||
.map(|c| CommitView { | ||
| @@ -312,12 +333,12 @@ async fn events( | ||
} | ||
}; | ||
let event = Event { | ||
created_at: p.created_at.clone(), | ||
actor: actor(&p.did), | ||
repo: repo(&p.did, &p.name), | ||
created_at: last.created_at.clone(), | ||
actor: actor(&last.did), | ||
repo: repo(&last.did, &last.name), | ||
action, | ||
}; | ||
events.push((p.uri.clone(), event)); | ||
events.push((last.uri.clone(), event)); | ||
} | ||
| ||
// Pulls and issues, as their repositories' owners let them stand, by | ||
| @@ -385,10 +406,108 @@ async fn events( | ||
Ok((events.into_iter().map(|(_, e)| e).collect(), next)) | ||
} | ||
| ||
/// `pushes`, newest first, in runs shown as one, newest first, each newest | ||
/// first: the pushes to a ref, each within an hour of the one after it. | ||
/// A deletion is a run of its own, which ends the run after it. | ||
fn runs(pushes: &[Push]) -> Vec<Vec<&Push>> { | ||
let mut runs: Vec<Vec<&Push>> = Vec::new(); | ||
let mut open: HashMap<(&str, &str), usize> = HashMap::new(); | ||
for p in pushes { | ||
let key = (p.repo.as_str(), p.refname.as_str()); | ||
let after = open.get(&key).filter(|&&i| { | ||
let next = &runs[i][runs[i].len() - 1].created_at; | ||
p.to.is_some() && within(&p.created_at, next) | ||
}); | ||
match after { | ||
Some(&i) => runs[i].push(p), | ||
None if p.to.is_none() => { | ||
open.remove(&key); | ||
runs.push(vec![p]); | ||
} | ||
None => { | ||
open.insert(key, runs.len()); | ||
runs.push(vec![p]); | ||
} | ||
} | ||
} | ||
runs | ||
} | ||
| ||
/// Whether `next` came within a run's gap of `at`. | ||
fn within(at: &str, next: &str) -> bool { | ||
seconds(at) | ||
.zip(seconds(next)) | ||
.is_some_and(|(at, next)| next - at <= RUN) | ||
} | ||
| ||
/// Seconds since 1970 at `at`, a UTC datetime such as | ||
/// `2026-10-21T22:50:00Z`, by Howard Hinnant's days from civil. | ||
fn seconds(at: &str) -> Option<i64> { | ||
let n = |from: usize, to: usize| at.get(from..to)?.parse::<i64>().ok(); | ||
let (y, m, d) = (n(0, 4)?, n(5, 7)?, n(8, 10)?); | ||
let (h, min, s) = (n(11, 13)?, n(14, 16)?, n(17, 19)?); | ||
let y = if m <= 2 { y - 1 } else { y }; | ||
let era = y.div_euclid(400); | ||
let yoe = y - era * 400; | ||
let doy = (153 * ((m + 9) % 12) + 2) / 5 + d - 1; | ||
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; | ||
Some((era * 146097 + doe - 719468) * 86400 + h * 3600 + min * 60 + s) | ||
} | ||
| ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
| ||
#[test] | ||
fn times() { | ||
assert_eq!(seconds("1970-01-01T00:00:00Z"), Some(0)); | ||
assert_eq!(seconds("2026-10-21T22:50:07.123Z"), Some(1792623007)); | ||
assert_eq!(seconds("2000-02-29T00:00:00Z"), Some(951782400)); | ||
assert_eq!(seconds("t"), None); | ||
assert!(within("2026-01-01T00:00:00Z", "2026-01-01T01:00:00Z")); | ||
assert!(!within("2026-01-01T00:00:00Z", "2026-01-01T01:00:01Z")); | ||
assert!(!within("t", "2026-01-01T01:00:00Z")); | ||
} | ||
| ||
#[test] | ||
fn pushes_in_runs() { | ||
let push = |r: &str, at: &str, to: Option<&str>| Push { | ||
uri: format!("at://did:a/dev.knotless.claim/{at}"), | ||
repo: "at://did:a/dev.knotless.repo/r".into(), | ||
did: "did:a".into(), | ||
name: "r".into(), | ||
fork_of: None, | ||
refname: format!("refs/heads/{r}"), | ||
from: None, | ||
to: to.map(str::to_string), | ||
created_at: format!("2026-01-01T{at}:00Z"), | ||
}; | ||
// Newest first: main moved at five, four and three, a gap, then at | ||
// one; side made at three thirty; main deleted at half past four. | ||
let pushes = [ | ||
push("main", "05:00", Some("e")), | ||
push("main", "04:30", None), | ||
push("main", "04:00", Some("d")), | ||
push("side", "03:30", Some("s")), | ||
push("main", "03:00", Some("c")), | ||
push("main", "01:00", Some("a")), | ||
]; | ||
let runs: Vec<Vec<&str>> = runs(&pushes) | ||
.into_iter() | ||
.map(|run| run.into_iter().map(|p| &p.created_at[11..16]).collect()) | ||
.collect(); | ||
assert_eq!( | ||
runs, | ||
[ | ||
vec!["05:00"], | ||
vec!["04:30"], | ||
vec!["04:00", "03:00"], | ||
vec!["03:30"], | ||
vec!["01:00"] | ||
] | ||
); | ||
} | ||
| ||
#[test] | ||
fn owners_and_names() { | ||
assert_eq!( | ||
crates/knotless-serve/tests/web.rs+37−13
| @@ -315,6 +315,12 @@ fn browsing_a_repository() { | ||
.starts_with("sandbox") | ||
); | ||
} | ||
// A file at a commit's hash is the same forever; at a branch, it can move. | ||
let cached = |rev: &str| { | ||
site.get(&format!("{repo}/raw/{rev}/src/main.rs")).headers()["cache-control"].clone() | ||
}; | ||
assert_eq!(cached(tip.trim()), "public, max-age=31536000, immutable"); | ||
assert_eq!(cached("main"), "no-cache"); | ||
| ||
let log = site.data(&format!("{repo}/commits/main")); | ||
assert_eq!(log["commits"].as_array().unwrap().len(), 50); | ||
| @@ -834,8 +840,9 @@ fn the_feed() { | ||
); | ||
let work = owner.work("w", 1); | ||
owner.git(&work, &["push", "-q", &url, "main"]); | ||
// A push of several commits lists them, newest first; one back to | ||
// where it was brings nothing. | ||
// Pushes to a ref close together are one, from where the first found | ||
// it to where the last left it, so one back to where it was counts | ||
// only what stayed. | ||
for i in 0..6 { | ||
owner.commit_file(&work, "f", &format!("{i}\n")); | ||
} | ||
| @@ -844,8 +851,11 @@ fn the_feed() { | ||
owner.git(&work, &["checkout", "-q", "-b", "side"]); | ||
owner.commit_file(&work, "s", "s\n"); | ||
owner.git(&work, &["push", "-q", &url, "side"]); | ||
// A commit another ref had first is counted, not listed again; nor is | ||
// one listed by an earlier event, though main was pushed first. | ||
owner.git(&work, &["tag", "v1"]); | ||
owner.git(&work, &["push", "-q", &url, "v1"]); | ||
owner.git(&work, &["push", "-q", &url, "side:main"]); | ||
owner.git(&work, &["push", "-q", &url, ":side"]); | ||
let feed = shows( | ||
o, | ||
| @@ -854,26 +864,40 @@ fn the_feed() { | ||
("created", json!({"description": "Fed <to> the feed"})), | ||
( | ||
"pushed", | ||
json!({"ref": "refs/heads/main", "created": false, "count": 6}), | ||
json!({"ref": "refs/heads/main", "created": true, "count": 8, "new": 5}), | ||
), | ||
( | ||
"pushed", | ||
json!({"ref": "refs/heads/main", "created": false, "count": 0, "commits": []}), | ||
json!({"ref": "refs/heads/side", "created": true, "count": 3, "new": 3}), | ||
), | ||
( | ||
"pushed", | ||
json!({"ref": "refs/heads/side", "created": true, "count": 3}), | ||
json!({"ref": "refs/tags/v1", "created": true, "count": 3, "new": 0, "commits": []}), | ||
), | ||
("pushed", json!({"ref": "refs/tags/v1", "created": true})), | ||
], | ||
); | ||
let six = about(&feed, o, "fed") | ||
.into_iter() | ||
.find(|e| e["action"]["count"] == 6) | ||
.unwrap(); | ||
assert_eq!(six["action"]["commits"].as_array().unwrap().len(), 6); | ||
assert_eq!(six["actor"]["handle"], o.as_str()); | ||
assert_eq!(six["actor"]["did"], owner.account.did.as_str()); | ||
let on = |r: &str| { | ||
about(&feed, o, "fed") | ||
.into_iter() | ||
.filter(|e| e["action"]["ref"] == r) | ||
.collect::<Vec<_>>() | ||
}; | ||
let (main, side) = (on("refs/heads/main"), on("refs/heads/side")); | ||
assert_eq!((main.len(), side.len()), (1, 2)); | ||
// None of what side listed. | ||
let shas = |e: &Value| { | ||
e["action"]["commits"] | ||
.as_array() | ||
.unwrap() | ||
.iter() | ||
.map(|c| c["sha"].as_str().unwrap().to_string()) | ||
.collect::<Vec<_>>() | ||
}; | ||
let (main_shas, side_shas) = (shas(main[0]), shas(side[1])); | ||
assert_eq!((main_shas.len(), side_shas.len()), (5, 3)); | ||
assert!(main_shas.iter().all(|sha| !side_shas.contains(sha))); | ||
assert_eq!(main[0]["actor"]["handle"], o.as_str()); | ||
assert_eq!(main[0]["actor"]["did"], owner.account.did.as_str()); | ||
| ||
let fork = ok(&other, &["fork", &url]); | ||
let clone = other.path("clone"); | ||
harness/shots/baseline/darwin/commit-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
5f33fdeee80107fbfd0cde6823aa256066ab72c372765cf12176229e107646f5 | ||
| \ No newline at end of file | ||
55523575b08edca3dd0533f9b61f8b8d676c6a6d8613d5831a5eb41da7d46fcd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commit-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
5f33fdeee80107fbfd0cde6823aa256066ab72c372765cf12176229e107646f5 | ||
| \ No newline at end of file | ||
55523575b08edca3dd0533f9b61f8b8d676c6a6d8613d5831a5eb41da7d46fcd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commit-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
5f33fdeee80107fbfd0cde6823aa256066ab72c372765cf12176229e107646f5 | ||
| \ No newline at end of file | ||
55523575b08edca3dd0533f9b61f8b8d676c6a6d8613d5831a5eb41da7d46fcd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commit-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
5f33fdeee80107fbfd0cde6823aa256066ab72c372765cf12176229e107646f5 | ||
| \ No newline at end of file | ||
55523575b08edca3dd0533f9b61f8b8d676c6a6d8613d5831a5eb41da7d46fcd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commits-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
a29044f01576d24fb93df70c5e8f6d06855c0070f875f5d27055335f217a7b70 | ||
| \ No newline at end of file | ||
c910b813a20a4915611565155ab84e92563129dd13d6d8ea825fc2f0fcfc67a4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commits-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
a29044f01576d24fb93df70c5e8f6d06855c0070f875f5d27055335f217a7b70 | ||
| \ No newline at end of file | ||
c910b813a20a4915611565155ab84e92563129dd13d6d8ea825fc2f0fcfc67a4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commits-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
a29044f01576d24fb93df70c5e8f6d06855c0070f875f5d27055335f217a7b70 | ||
| \ No newline at end of file | ||
c910b813a20a4915611565155ab84e92563129dd13d6d8ea825fc2f0fcfc67a4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/commits-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
a29044f01576d24fb93df70c5e8f6d06855c0070f875f5d27055335f217a7b70 | ||
| \ No newline at end of file | ||
c910b813a20a4915611565155ab84e92563129dd13d6d8ea825fc2f0fcfc67a4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/directory-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
a479045b6c6c00e1127b7960476abe9b409bac0a230fac4ec4f82afc1a3c165e | ||
| \ No newline at end of file | ||
9be5b93f7d33ce2b0ffec21715efe83f6e9cd3625c5ae144336ea55b7de65af4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/directory-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
a479045b6c6c00e1127b7960476abe9b409bac0a230fac4ec4f82afc1a3c165e | ||
| \ No newline at end of file | ||
9be5b93f7d33ce2b0ffec21715efe83f6e9cd3625c5ae144336ea55b7de65af4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/directory-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
a479045b6c6c00e1127b7960476abe9b409bac0a230fac4ec4f82afc1a3c165e | ||
| \ No newline at end of file | ||
9be5b93f7d33ce2b0ffec21715efe83f6e9cd3625c5ae144336ea55b7de65af4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/directory-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
a479045b6c6c00e1127b7960476abe9b409bac0a230fac4ec4f82afc1a3c165e | ||
| \ No newline at end of file | ||
9be5b93f7d33ce2b0ffec21715efe83f6e9cd3625c5ae144336ea55b7de65af4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/empty-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
b761ba0df24067cb45f0da46a6a0ede9a0ad68b7b6bd4bbd083b16bb28e6cca0 | ||
| \ No newline at end of file | ||
c47fb4a9f08c24ac94baf8d3fa18447cb95a6f44a65832e87f919691c372cb16 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/empty-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
b761ba0df24067cb45f0da46a6a0ede9a0ad68b7b6bd4bbd083b16bb28e6cca0 | ||
| \ No newline at end of file | ||
c47fb4a9f08c24ac94baf8d3fa18447cb95a6f44a65832e87f919691c372cb16 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/empty-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
b761ba0df24067cb45f0da46a6a0ede9a0ad68b7b6bd4bbd083b16bb28e6cca0 | ||
| \ No newline at end of file | ||
c47fb4a9f08c24ac94baf8d3fa18447cb95a6f44a65832e87f919691c372cb16 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/empty-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
b761ba0df24067cb45f0da46a6a0ede9a0ad68b7b6bd4bbd083b16bb28e6cca0 | ||
| \ No newline at end of file | ||
c47fb4a9f08c24ac94baf8d3fa18447cb95a6f44a65832e87f919691c372cb16 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/file-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
477b5dd5b713ad61fe043581595dff1dd7ccc26295140d72c9ef8ab4378db407 | ||
| \ No newline at end of file | ||
946bd65188f6e54fe6cc1f2122f129a8f6b39ac5c0f6b6e1e6d378af4f85ae1d | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/file-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
477b5dd5b713ad61fe043581595dff1dd7ccc26295140d72c9ef8ab4378db407 | ||
| \ No newline at end of file | ||
946bd65188f6e54fe6cc1f2122f129a8f6b39ac5c0f6b6e1e6d378af4f85ae1d | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/file-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
477b5dd5b713ad61fe043581595dff1dd7ccc26295140d72c9ef8ab4378db407 | ||
| \ No newline at end of file | ||
946bd65188f6e54fe6cc1f2122f129a8f6b39ac5c0f6b6e1e6d378af4f85ae1d | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/file-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
477b5dd5b713ad61fe043581595dff1dd7ccc26295140d72c9ef8ab4378db407 | ||
| \ No newline at end of file | ||
946bd65188f6e54fe6cc1f2122f129a8f6b39ac5c0f6b6e1e6d378af4f85ae1d | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/fork-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
7a414e54a8301431eb6d4ad6a143c1c834b921c494ea30aa623268adf83ae41a | ||
| \ No newline at end of file | ||
7a1853cb11a21d4246888ba642feab7a7c22d9566a5a87feee4afa45b05f9bdd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/fork-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
7a414e54a8301431eb6d4ad6a143c1c834b921c494ea30aa623268adf83ae41a | ||
| \ No newline at end of file | ||
7a1853cb11a21d4246888ba642feab7a7c22d9566a5a87feee4afa45b05f9bdd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/fork-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
7a414e54a8301431eb6d4ad6a143c1c834b921c494ea30aa623268adf83ae41a | ||
| \ No newline at end of file | ||
7a1853cb11a21d4246888ba642feab7a7c22d9566a5a87feee4afa45b05f9bdd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/fork-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
7a414e54a8301431eb6d4ad6a143c1c834b921c494ea30aa623268adf83ae41a | ||
| \ No newline at end of file | ||
7a1853cb11a21d4246888ba642feab7a7c22d9566a5a87feee4afa45b05f9bdd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/home-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/home-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
69e43d28d6bb91a144feb18e5828905c126364d320d8ab6af1899f8639143e33 | ||
| \ No newline at end of file | ||
c0847961f51639a5b84b40e8af596b81e704161fbcf04793258db593a835f218 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/home-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/home-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
69e43d28d6bb91a144feb18e5828905c126364d320d8ab6af1899f8639143e33 | ||
| \ No newline at end of file | ||
c0847961f51639a5b84b40e8af596b81e704161fbcf04793258db593a835f218 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/home-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/home-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
69e43d28d6bb91a144feb18e5828905c126364d320d8ab6af1899f8639143e33 | ||
| \ No newline at end of file | ||
c0847961f51639a5b84b40e8af596b81e704161fbcf04793258db593a835f218 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/home-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/home-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
69e43d28d6bb91a144feb18e5828905c126364d320d8ab6af1899f8639143e33 | ||
| \ No newline at end of file | ||
c0847961f51639a5b84b40e8af596b81e704161fbcf04793258db593a835f218 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/image-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
44f48b67e5931381247fc9aa76614c26326cf132174382fd02f88eb8e3f00d86 | ||
| \ No newline at end of file | ||
ed1a97810f3a60b19cb97bfd628f0fd58d8813db8869c689567c815dee14a712 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/image-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
44f48b67e5931381247fc9aa76614c26326cf132174382fd02f88eb8e3f00d86 | ||
| \ No newline at end of file | ||
ed1a97810f3a60b19cb97bfd628f0fd58d8813db8869c689567c815dee14a712 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/image-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
44f48b67e5931381247fc9aa76614c26326cf132174382fd02f88eb8e3f00d86 | ||
| \ No newline at end of file | ||
ed1a97810f3a60b19cb97bfd628f0fd58d8813db8869c689567c815dee14a712 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/image-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
44f48b67e5931381247fc9aa76614c26326cf132174382fd02f88eb8e3f00d86 | ||
| \ No newline at end of file | ||
ed1a97810f3a60b19cb97bfd628f0fd58d8813db8869c689567c815dee14a712 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-closed-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
56a3f516604027d9dfcc09f95514afdc8028f98dae66850c4bb29a03460fa230 | ||
| \ No newline at end of file | ||
a73b55b364c8514d91fab15c5598e8f730ec39d6c46fc8bed91b7211440bc1dd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-closed-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
56a3f516604027d9dfcc09f95514afdc8028f98dae66850c4bb29a03460fa230 | ||
| \ No newline at end of file | ||
a73b55b364c8514d91fab15c5598e8f730ec39d6c46fc8bed91b7211440bc1dd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-closed-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
56a3f516604027d9dfcc09f95514afdc8028f98dae66850c4bb29a03460fa230 | ||
| \ No newline at end of file | ||
a73b55b364c8514d91fab15c5598e8f730ec39d6c46fc8bed91b7211440bc1dd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-closed-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
56a3f516604027d9dfcc09f95514afdc8028f98dae66850c4bb29a03460fa230 | ||
| \ No newline at end of file | ||
a73b55b364c8514d91fab15c5598e8f730ec39d6c46fc8bed91b7211440bc1dd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issue-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
2bc3e7020528e239e2ce7712002c6cdf390c99a8f8758ffe8fade4fc4e770236 | ||
| \ No newline at end of file | ||
1ba98580965f7cc89bc716ce5753536ab7654aa1af1dc311c462a41f16a8507e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issue-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
2bc3e7020528e239e2ce7712002c6cdf390c99a8f8758ffe8fade4fc4e770236 | ||
| \ No newline at end of file | ||
1ba98580965f7cc89bc716ce5753536ab7654aa1af1dc311c462a41f16a8507e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issue-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
2bc3e7020528e239e2ce7712002c6cdf390c99a8f8758ffe8fade4fc4e770236 | ||
| \ No newline at end of file | ||
1ba98580965f7cc89bc716ce5753536ab7654aa1af1dc311c462a41f16a8507e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issue-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issue-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
2bc3e7020528e239e2ce7712002c6cdf390c99a8f8758ffe8fade4fc4e770236 | ||
| \ No newline at end of file | ||
1ba98580965f7cc89bc716ce5753536ab7654aa1af1dc311c462a41f16a8507e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-closed-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
c19af68e9834765eef196d108ed794145a81006d840e3bcb7b925a4d4124314c | ||
| \ No newline at end of file | ||
3d230b1ad31a5d0fd3d64e3fefdb583ca57411d4027510015616637138fdfe89 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-closed-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
c19af68e9834765eef196d108ed794145a81006d840e3bcb7b925a4d4124314c | ||
| \ No newline at end of file | ||
3d230b1ad31a5d0fd3d64e3fefdb583ca57411d4027510015616637138fdfe89 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-closed-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
c19af68e9834765eef196d108ed794145a81006d840e3bcb7b925a4d4124314c | ||
| \ No newline at end of file | ||
3d230b1ad31a5d0fd3d64e3fefdb583ca57411d4027510015616637138fdfe89 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-closed-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
c19af68e9834765eef196d108ed794145a81006d840e3bcb7b925a4d4124314c | ||
| \ No newline at end of file | ||
3d230b1ad31a5d0fd3d64e3fefdb583ca57411d4027510015616637138fdfe89 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issues-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
c31abe88126abf5cb983d1c4cf5e3de4dc8697d99bb4a20624da8682de6bd470 | ||
| \ No newline at end of file | ||
dc07d6d77285f6910fa1d6620bb0e9ba9b70ead8201015d95e9b119f1e8f8f77 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issues-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
c31abe88126abf5cb983d1c4cf5e3de4dc8697d99bb4a20624da8682de6bd470 | ||
| \ No newline at end of file | ||
dc07d6d77285f6910fa1d6620bb0e9ba9b70ead8201015d95e9b119f1e8f8f77 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issues-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
c31abe88126abf5cb983d1c4cf5e3de4dc8697d99bb4a20624da8682de6bd470 | ||
| \ No newline at end of file | ||
dc07d6d77285f6910fa1d6620bb0e9ba9b70ead8201015d95e9b119f1e8f8f77 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/issues-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/issues-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
c31abe88126abf5cb983d1c4cf5e3de4dc8697d99bb4a20624da8682de6bd470 | ||
| \ No newline at end of file | ||
dc07d6d77285f6910fa1d6620bb0e9ba9b70ead8201015d95e9b119f1e8f8f77 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/markdown-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
2bcc1c4cb881fb0e36764ff9715171f6cd493f4d8290261121a82756a299f260 | ||
| \ No newline at end of file | ||
dd7b363b46884884e156bfc9f823e7b411c8554f41b3a5e5a39c4cf8e49e95c6 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/markdown-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
2bcc1c4cb881fb0e36764ff9715171f6cd493f4d8290261121a82756a299f260 | ||
| \ No newline at end of file | ||
dd7b363b46884884e156bfc9f823e7b411c8554f41b3a5e5a39c4cf8e49e95c6 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/markdown-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
2bcc1c4cb881fb0e36764ff9715171f6cd493f4d8290261121a82756a299f260 | ||
| \ No newline at end of file | ||
dd7b363b46884884e156bfc9f823e7b411c8554f41b3a5e5a39c4cf8e49e95c6 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/markdown-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
2bcc1c4cb881fb0e36764ff9715171f6cd493f4d8290261121a82756a299f260 | ||
| \ No newline at end of file | ||
dd7b363b46884884e156bfc9f823e7b411c8554f41b3a5e5a39c4cf8e49e95c6 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/missing-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
68fb65092837b2dab69af58d1ca50ae4a8b7762354d62d06b72741e92c82afbf | ||
| \ No newline at end of file | ||
9872ccfd82fe0e00914101bce6cee132af07c0bd50c14ad62deb0ffb7fbbe81b | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/missing-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
68fb65092837b2dab69af58d1ca50ae4a8b7762354d62d06b72741e92c82afbf | ||
| \ No newline at end of file | ||
9872ccfd82fe0e00914101bce6cee132af07c0bd50c14ad62deb0ffb7fbbe81b | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/missing-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
68fb65092837b2dab69af58d1ca50ae4a8b7762354d62d06b72741e92c82afbf | ||
| \ No newline at end of file | ||
9872ccfd82fe0e00914101bce6cee132af07c0bd50c14ad62deb0ffb7fbbe81b | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/missing-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
68fb65092837b2dab69af58d1ca50ae4a8b7762354d62d06b72741e92c82afbf | ||
| \ No newline at end of file | ||
9872ccfd82fe0e00914101bce6cee132af07c0bd50c14ad62deb0ffb7fbbe81b | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
019faa64f2278213d21bee20c62386996347d75a7d9ce703e13554893ae85521 | ||
| \ No newline at end of file | ||
d16cf0c2b2394a5f10a42cd322002f1d21644793a422d1a7bd7d31e401a1bb99 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
019faa64f2278213d21bee20c62386996347d75a7d9ce703e13554893ae85521 | ||
| \ No newline at end of file | ||
d16cf0c2b2394a5f10a42cd322002f1d21644793a422d1a7bd7d31e401a1bb99 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-fork-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/profile-fork-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
384d2aa6d1e8a35b24c13b282f88134956c69e1d17231fd8d9d487ac472aa5c5 | ||
| \ No newline at end of file | ||
a9b12b3f0dc154daa17bfbcb0cabc299c18c50437eae6655186cf5dc16303fd4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-fork-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
384d2aa6d1e8a35b24c13b282f88134956c69e1d17231fd8d9d487ac472aa5c5 | ||
| \ No newline at end of file | ||
a9b12b3f0dc154daa17bfbcb0cabc299c18c50437eae6655186cf5dc16303fd4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-fork-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/profile-fork-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
384d2aa6d1e8a35b24c13b282f88134956c69e1d17231fd8d9d487ac472aa5c5 | ||
| \ No newline at end of file | ||
a9b12b3f0dc154daa17bfbcb0cabc299c18c50437eae6655186cf5dc16303fd4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-fork-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
384d2aa6d1e8a35b24c13b282f88134956c69e1d17231fd8d9d487ac472aa5c5 | ||
| \ No newline at end of file | ||
a9b12b3f0dc154daa17bfbcb0cabc299c18c50437eae6655186cf5dc16303fd4 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
019faa64f2278213d21bee20c62386996347d75a7d9ce703e13554893ae85521 | ||
| \ No newline at end of file | ||
d16cf0c2b2394a5f10a42cd322002f1d21644793a422d1a7bd7d31e401a1bb99 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/profile-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
019faa64f2278213d21bee20c62386996347d75a7d9ce703e13554893ae85521 | ||
| \ No newline at end of file | ||
d16cf0c2b2394a5f10a42cd322002f1d21644793a422d1a7bd7d31e401a1bb99 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
6563f759880226dc1a30ca6c3421101e6cdad6ee79c134ffe826d3b29c95cbfe | ||
| \ No newline at end of file | ||
747a24ce632a28e4192d5e3ffb1842c958ebc5b16d2350ddff5f3c3ab9af11cd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
6563f759880226dc1a30ca6c3421101e6cdad6ee79c134ffe826d3b29c95cbfe | ||
| \ No newline at end of file | ||
747a24ce632a28e4192d5e3ffb1842c958ebc5b16d2350ddff5f3c3ab9af11cd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-open-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pull-open-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
9d8c1e92639e49042e980e181cf0de860ad0ba85dc662de983d4bc681c045937 | ||
| \ No newline at end of file | ||
b12cb9cd3fecb1bcf020ee4684440bbccfd631997616928e30f4434ce1f98036 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-open-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pull-open-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
9d8c1e92639e49042e980e181cf0de860ad0ba85dc662de983d4bc681c045937 | ||
| \ No newline at end of file | ||
b12cb9cd3fecb1bcf020ee4684440bbccfd631997616928e30f4434ce1f98036 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-open-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pull-open-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
9d8c1e92639e49042e980e181cf0de860ad0ba85dc662de983d4bc681c045937 | ||
| \ No newline at end of file | ||
b12cb9cd3fecb1bcf020ee4684440bbccfd631997616928e30f4434ce1f98036 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-open-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pull-open-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
9d8c1e92639e49042e980e181cf0de860ad0ba85dc662de983d4bc681c045937 | ||
| \ No newline at end of file | ||
b12cb9cd3fecb1bcf020ee4684440bbccfd631997616928e30f4434ce1f98036 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
6563f759880226dc1a30ca6c3421101e6cdad6ee79c134ffe826d3b29c95cbfe | ||
| \ No newline at end of file | ||
747a24ce632a28e4192d5e3ffb1842c958ebc5b16d2350ddff5f3c3ab9af11cd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pull-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
6563f759880226dc1a30ca6c3421101e6cdad6ee79c134ffe826d3b29c95cbfe | ||
| \ No newline at end of file | ||
747a24ce632a28e4192d5e3ffb1842c958ebc5b16d2350ddff5f3c3ab9af11cd | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-closed-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
eee77c78ee5b8631fce52e93142b0406d3e783c16ffd98e5700d5f9d9905204d | ||
| \ No newline at end of file | ||
49f57b2917357ae0f3cf82e42b037fe2951f3a1e9cc92a4febc249b6c2531270 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-closed-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
eee77c78ee5b8631fce52e93142b0406d3e783c16ffd98e5700d5f9d9905204d | ||
| \ No newline at end of file | ||
49f57b2917357ae0f3cf82e42b037fe2951f3a1e9cc92a4febc249b6c2531270 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-closed-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
eee77c78ee5b8631fce52e93142b0406d3e783c16ffd98e5700d5f9d9905204d | ||
| \ No newline at end of file | ||
49f57b2917357ae0f3cf82e42b037fe2951f3a1e9cc92a4febc249b6c2531270 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-closed-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
eee77c78ee5b8631fce52e93142b0406d3e783c16ffd98e5700d5f9d9905204d | ||
| \ No newline at end of file | ||
49f57b2917357ae0f3cf82e42b037fe2951f3a1e9cc92a4febc249b6c2531270 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pulls-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
b25928b19bb96125e47691e9039500f93ad343042a32202fdc469b58ca67f036 | ||
| \ No newline at end of file | ||
23463e7393c3d7e700b0453d9924466d72ab0b1e5201808e4d32718cd96ef3a7 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pulls-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
b25928b19bb96125e47691e9039500f93ad343042a32202fdc469b58ca67f036 | ||
| \ No newline at end of file | ||
23463e7393c3d7e700b0453d9924466d72ab0b1e5201808e4d32718cd96ef3a7 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pulls-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
b25928b19bb96125e47691e9039500f93ad343042a32202fdc469b58ca67f036 | ||
| \ No newline at end of file | ||
23463e7393c3d7e700b0453d9924466d72ab0b1e5201808e4d32718cd96ef3a7 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/pulls-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/pulls-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
b25928b19bb96125e47691e9039500f93ad343042a32202fdc469b58ca67f036 | ||
| \ No newline at end of file | ||
23463e7393c3d7e700b0453d9924466d72ab0b1e5201808e4d32718cd96ef3a7 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/repository-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
eac125019c62eab00ad5c67a627c8409ab575daba682b9283063968691b71769 | ||
| \ No newline at end of file | ||
29422ff9c0c91e3b9baeb6ce66555c8df73ac0c39c0ee4e5a05412871e8dfa04 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/repository-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
eac125019c62eab00ad5c67a627c8409ab575daba682b9283063968691b71769 | ||
| \ No newline at end of file | ||
29422ff9c0c91e3b9baeb6ce66555c8df73ac0c39c0ee4e5a05412871e8dfa04 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/repository-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
eac125019c62eab00ad5c67a627c8409ab575daba682b9283063968691b71769 | ||
| \ No newline at end of file | ||
29422ff9c0c91e3b9baeb6ce66555c8df73ac0c39c0ee4e5a05412871e8dfa04 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/repository-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
eac125019c62eab00ad5c67a627c8409ab575daba682b9283063968691b71769 | ||
| \ No newline at end of file | ||
29422ff9c0c91e3b9baeb6ce66555c8df73ac0c39c0ee4e5a05412871e8dfa04 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/search-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
23008d19fec907bc53c030dedebee818357798ba0b0847725e16b606f64afa9f | ||
| \ No newline at end of file | ||
05d96d25012fd62b3b0cce0ad6034f1fe854810f691981dde52da3931f22c877 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/search-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
23008d19fec907bc53c030dedebee818357798ba0b0847725e16b606f64afa9f | ||
| \ No newline at end of file | ||
05d96d25012fd62b3b0cce0ad6034f1fe854810f691981dde52da3931f22c877 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/search-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
23008d19fec907bc53c030dedebee818357798ba0b0847725e16b606f64afa9f | ||
| \ No newline at end of file | ||
05d96d25012fd62b3b0cce0ad6034f1fe854810f691981dde52da3931f22c877 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/search-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
23008d19fec907bc53c030dedebee818357798ba0b0847725e16b606f64afa9f | ||
| \ No newline at end of file | ||
05d96d25012fd62b3b0cce0ad6034f1fe854810f691981dde52da3931f22c877 | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/start-desktop-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/start-desktop-dark.sha256+1−1
| @@ -1 +1 @@ | ||
4144258dbb38373e3bf6d6a7ea8663d0602a0bd6c228080acfacfa87c22df26c | ||
| \ No newline at end of file | ||
1f23dd671d3dbeee1455f45d21ac82100dfa8d33b9b953b97d3570ee65e26e1e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/start-desktop-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/start-desktop-light.sha256+1−1
| @@ -1 +1 @@ | ||
4144258dbb38373e3bf6d6a7ea8663d0602a0bd6c228080acfacfa87c22df26c | ||
| \ No newline at end of file | ||
1f23dd671d3dbeee1455f45d21ac82100dfa8d33b9b953b97d3570ee65e26e1e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/start-phone-dark.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/start-phone-dark.sha256+1−1
| @@ -1 +1 @@ | ||
4144258dbb38373e3bf6d6a7ea8663d0602a0bd6c228080acfacfa87c22df26c | ||
| \ No newline at end of file | ||
1f23dd671d3dbeee1455f45d21ac82100dfa8d33b9b953b97d3570ee65e26e1e | ||
| \ No newline at end of file | ||
harness/shots/baseline/darwin/start-phone-light.png+0−0
A binary file, not shown.
harness/shots/baseline/darwin/start-phone-light.sha256+1−1
| @@ -1 +1 @@ | ||
4144258dbb38373e3bf6d6a7ea8663d0602a0bd6c228080acfacfa87c22df26c | ||
| \ No newline at end of file | ||
1f23dd671d3dbeee1455f45d21ac82100dfa8d33b9b953b97d3570ee65e26e1e | ||
| \ No newline at end of file | ||
harness/shots/fixture/reading/more.mdnew+5−0
| @@ -0,0 +1,5 @@ | ||
# Reading | ||
| ||
- *A Philosophy of Software Design*, John Ousterhout | ||
- *The Mythical Man-Month*, Fred Brooks | ||
- *Working in Public*, Nadia Eghbal | ||
harness/shots/fixture/reading/reading.mdnew+4−0
| @@ -0,0 +1,4 @@ | ||
# Reading | ||
| ||
- *A Philosophy of Software Design*, John Ousterhout | ||
- *The Mythical Man-Month*, Fred Brooks | ||
harness/shots/fixtures.json+20−20
| @@ -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:rlblzwyobhzohu5brycywuq5/3mygok6ir5j4g", | ||
"pull-open": "/alice.test/tally/pull/did:plc:rlblzwyobhzohu5brycywuq5/3mygokbv5rksi", | ||
"pull": "/alice.test/tally/pull/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr62puktrn", | ||
"pull-open": "/alice.test/tally/pull/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67mpz2lh", | ||
"issues": "/alice.test/tally/issues", | ||
"issues-closed": "/alice.test/tally/issues?state=closed", | ||
"issue": "/alice.test/tally/issue/did:plc:rlblzwyobhzohu5brycywuq5/3mygokbyh7bvs", | ||
"issue-closed": "/alice.test/tally/issue/did:plc:rlblzwyobhzohu5brycywuq5/3mygokcei6c5m", | ||
"issue": "/alice.test/tally/issue/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67pi5ynj", | ||
"issue-closed": "/alice.test/tally/issue/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67tjxsqp", | ||
"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:5y4vhycox5bylby7jqcqga6c\",\"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\",\"avatar\":null,\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"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:d7crjayi6h6l3ek2wvnvl7qa\",\"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\",\"avatar\":null,\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T14:56:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"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\":{\"name\":\"Bob Tanaka\",\"account\":\"bob.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-09T09:31:00+00:00\",\"message\":\"Count no lines in empty input\"},{\"sha\":\"97727c2c19d5d8b3849b2ed9ad47130cee9798f5\",\"parents\":[\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\"],\"author\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-08T11:47:00+00:00\",\"message\":\"Split parsing into its own module\"},{\"sha\":\"4f6b9a1168c6534f64c836b4c3a11e23e74637ad\",\"parents\":[\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\"],\"author\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-06T10:02:00+00:00\",\"message\":\"Document usage and output\"},{\"sha\":\"d1f55d39d12c4d6ba6ab25b1e9c76d15fb2cbaca\",\"parents\":[\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\"],\"author\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-05T17:21:00+00:00\",\"message\":\"Add a logo\"},{\"sha\":\"7818a5bc1bef9184ad313cf79175da3d87d4b9cf\",\"parents\":[\"a7e467c6f9acd7b1be0603afdec0b8cfdcf3cf49\"],\"author\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"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\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-02T14:40:00+00:00\",\"message\":\"Count words, lines and bytes\"},{\"sha\":\"cac02c464843db4d55fecfa69d327ef7b12702c0\",\"parents\":[],\"author\":{\"name\":\"Alice Moreau\",\"account\":\"alice.test\",\"avatar\":null},\"coauthors\":[],\"date\":\"2026-10-01T09:12:00+00:00\",\"message\":\"Start tally\"}],\"more\":false}" | ||
}, | ||
"/_data/alice.test/tally/issue/did:plc:rlblzwyobhzohu5brycywuq5/3mygokbyh7bvs": { | ||
"/_data/alice.test/tally/issue/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67pi5ynj": { | ||
"status": 200, | ||
"headers": { | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokbyh7bvs/card.png?v=160a3e2db8db2127\"}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"createdAt\":\"2026-10-10T02:02: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\",\"avatar\":null,\"createdAt\":\"2026-10-10T02:39: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:7p6ytxf46wo2lwsehtykseck/3mygr67pi5ynj/card.png?v=160a3e2db8db2127\"}" | ||
}, | ||
"/_data/alice.test/tally/issue/did:plc:rlblzwyobhzohu5brycywuq5/3mygokcei6c5m": { | ||
"/_data/alice.test/tally/issue/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67tjxsqp": { | ||
"status": 200, | ||
"headers": { | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokcei6c5m/card.png?v=72fe47df61de13f9\"}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"createdAt\":\"2026-10-10T03:16: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:7p6ytxf46wo2lwsehtykseck/3mygr67tjxsqp/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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokbyh7bvs\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"comments\":1}]}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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:7p6ytxf46wo2lwsehtykseck/3mygr67pi5ynj\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T02:02: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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokcei6c5m\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"comments\":0}]}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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:7p6ytxf46wo2lwsehtykseck/3mygr67tjxsqp\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T03:16:00.000Z\",\"comments\":0}]}" | ||
}, | ||
"/_data/alice.test/tally/pull/did:plc:rlblzwyobhzohu5brycywuq5/3mygok6ir5j4g": { | ||
"/_data/alice.test/tally/pull/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr62puktrn": { | ||
"status": 200, | ||
"headers": { | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"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\",\"avatar\":null,\"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\",\"avatar\":null,\"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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygok6ir5j4g/card.png?v=7f555a00efc7106b\"}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"createdAt\":\"2026-10-09T21:06: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\",\"avatar\":null,\"createdAt\":\"2026-10-09T21:43:00.000Z\",\"html\":\"<p>Could <code>lines</code> check for emptiness once, rather than in the filter?</p>\\n\",\"verdict\":\"changes\"},{\"author\":\"bob.test\",\"avatar\":null,\"createdAt\":\"2026-10-09T22:20: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\",\"avatar\":null,\"createdAt\":\"2026-10-09T22:57: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:7p6ytxf46wo2lwsehtykseck/3mygr62puktrn/card.png?v=7f555a00efc7106b\"}" | ||
}, | ||
"/_data/alice.test/tally/pull/did:plc:rlblzwyobhzohu5brycywuq5/3mygokbv5rksi": { | ||
"/_data/alice.test/tally/pull/did:plc:7p6ytxf46wo2lwsehtykseck/3mygr67mpz2lh": { | ||
"status": 200, | ||
"headers": { | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokbv5rksi/card.png?v=029fd2cd417c8b94\"}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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\",\"avatar\":null,\"createdAt\":\"2026-10-10T01:25: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:7p6ytxf46wo2lwsehtykseck/3mygr67mpz2lh/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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygokbv5rksi\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"comments\":0}]}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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:7p6ytxf46wo2lwsehtykseck/3mygr67mpz2lh\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-10T01:25: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\",\"avatar\":null,\"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:rlblzwyobhzohu5brycywuq5/3mygok6ir5j4g\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"comments\":3}]}" | ||
"body": "{\"repo\":{\"authority\":\"alice.test\",\"avatar\":null,\"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:7p6ytxf46wo2lwsehtykseck/3mygr62puktrn\",\"author\":\"bob.test\",\"createdAt\":\"2026-10-09T21:06: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:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\",\"name\":null,\"bio\":null,\"avatar\":null,\"about\":null,\"repos\":[{\"owner\":\"bob.test\",\"avatar\":null,\"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:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\",\"name\":null,\"bio\":null,\"avatar\":null,\"about\":null,\"repos\":[{\"owner\":\"bob.test\",\"avatar\":null,\"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-09T16:47:00.000Z\",\"private\":false}]}" | ||
}, | ||
"/_data/bob.test/tally": { | ||
"status": 200, | ||
| @@ -191,7 +191,7 @@ | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"query\":\"t\",\"repos\":[{\"owner\":\"bob.test\",\"avatar\":null,\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"createdAt\":\"2026-10-09T14:19:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"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": "{\"query\":\"t\",\"repos\":[{\"owner\":\"bob.test\",\"avatar\":null,\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"createdAt\":\"2026-10-09T16:47:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"scratch\",\"description\":null,\"forkOf\":null,\"createdAt\":\"2026-10-09T14:56:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"notes\",\"description\":\"Scratch notes 📝\",\"forkOf\":null,\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"private\":false},{\"owner\":\"alice.test\",\"avatar\":null,\"name\":\"tally\",\"description\":\"Counts lines, words and bytes, with output that lines up\",\"forkOf\":null,\"createdAt\":\"2026-10-09T10:00:00.000Z\",\"private\":false}]}" | ||
}, | ||
"/alice.test/tally/raw/main/assets/logo.svg": { | ||
"status": 200, | ||
| @@ -216,7 +216,7 @@ | ||
"headers": { | ||
"content-type": "application/json" | ||
}, | ||
"body": "{\"events\":[{\"createdAt\":\"2026-10-10T01:25:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.issue/3mygokcei6c5m\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\"}},{\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"actor\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.issue/3mygokcei6c5m\",\"title\":\"Typo in the README ✏️\"}},{\"createdAt\":\"2026-10-10T00:11:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.issue/3mygokbyh7bvs\",\"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:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.issue/3mygokbyh7bvs\",\"title\":\"Fails on files that are not UTF-8\"}},{\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"actor\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygokbv5rksi\",\"title\":\"Print counts as JSON with --json\"}},{\"createdAt\":\"2026-10-09T22:20:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygok6ir5j4g\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\"}},{\"createdAt\":\"2026-10-09T21:06:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygok6ir5j4g\",\"title\":\"Count no lines in empty input\",\"text\":\"Fair enough. Thanks!\",\"verdict\":\"approve\"}},{\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"actor\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygok6ir5j4g\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygok6ir5j4g\",\"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:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:rlblzwyobhzohu5brycywuq5/dev.knotless.pull/3mygok6ir5j4g\",\"title\":\"Count no lines in empty input\"}},{\"createdAt\":\"2026-10-09T17:24:00.000Z\",\"actor\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"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:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"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:rlblzwyobhzohu5brycywuq5\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:rlblzwyobhzohu5brycywuq5\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"tally\"}}},{\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\",\"name\":\"scratch\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\"}},{\"createdAt\":\"2026-10-09T12:28:00.000Z\",\"actor\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"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:5y4vhycox5bylby7jqcqga6c\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:5y4vhycox5bylby7jqcqga6c\",\"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-10T03:53:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.issue/3mygr67tjxsqp\",\"title\":\"Typo in the README ✏️\",\"state\":\"closed\"}},{\"createdAt\":\"2026-10-10T03:16:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.issue/3mygr67tjxsqp\",\"title\":\"Typo in the README ✏️\"}},{\"createdAt\":\"2026-10-10T02:39:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.issue/3mygr67pi5ynj\",\"title\":\"Fails on files that are not UTF-8\",\"text\":\"Confirmed. Reading bytes and counting words lossily would fix it.\"}},{\"createdAt\":\"2026-10-10T02:02:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.issue/3mygr67pi5ynj\",\"title\":\"Fails on files that are not UTF-8\"}},{\"createdAt\":\"2026-10-10T01:25:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr67mpz2lh\",\"title\":\"Print counts as JSON with --json\"}},{\"createdAt\":\"2026-10-10T00:48:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#changed\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr62puktrn\",\"title\":\"Count no lines in empty input\",\"state\":\"merged\"}},{\"createdAt\":\"2026-10-09T23:34:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":false,\"count\":1,\"new\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T22:57:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr62puktrn\",\"title\":\"Count no lines in empty input\",\"text\":\"Fair enough. Thanks!\",\"verdict\":\"approve\"}},{\"createdAt\":\"2026-10-09T22:20:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr62puktrn\",\"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-09T21:43:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#commented\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr62puktrn\",\"title\":\"Count no lines in empty input\",\"text\":\"Could `lines` check for emptiness once, rather than in the filter?\",\"verdict\":\"changes\"}},{\"createdAt\":\"2026-10-09T21:06:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#opened\",\"subject\":\"at://did:plc:7p6ytxf46wo2lwsehtykseck/dev.knotless.pull/3mygr62puktrn\",\"title\":\"Count no lines in empty input\"}},{\"createdAt\":\"2026-10-09T19:52:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/json\",\"created\":true,\"count\":1,\"new\":1,\"commits\":[{\"sha\":\"2063dabcda7417d5831f3b6bfd8ffb18da82fb8e\",\"message\":\"Print counts as JSON with --json\"}]}},{\"createdAt\":\"2026-10-09T18:38:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/empty-input\",\"created\":true,\"count\":1,\"new\":1,\"commits\":[{\"sha\":\"686a36205b82620af07dd99997c570f04f461df9\",\"message\":\"Count no lines in empty input\"}]}},{\"createdAt\":\"2026-10-09T16:47:00.000Z\",\"actor\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"handle\":\"bob.test\"},\"repo\":{\"did\":\"did:plc:7p6ytxf46wo2lwsehtykseck\",\"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:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"}}},{\"createdAt\":\"2026-10-09T15:33:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":false,\"count\":2,\"new\":1,\"commits\":[{\"sha\":\"661c67a96bf920012bc578c6c6895e4103128dfe\",\"message\":\"Add Working in Public\"}]}},{\"createdAt\":\"2026-10-09T14:56:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"scratch\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\"}},{\"createdAt\":\"2026-10-09T13:42:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/reading\",\"created\":true,\"count\":1,\"new\":1,\"commits\":[{\"sha\":\"7b66c6ffd4369e246fc00c2bcb3e16d0ed1846d1\",\"message\":\"Start a reading list\"}]}},{\"createdAt\":\"2026-10-09T12:28:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"notes\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":1,\"new\":1,\"commits\":[{\"sha\":\"e78543abf0f799b7eb5713ccdf8d88b84d0f8ec0\",\"message\":\"Begin\"}]}},{\"createdAt\":\"2026-10-09T11:51:00.000Z\",\"actor\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"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:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#pushed\",\"ref\":\"refs/heads/main\",\"created\":true,\"count\":6,\"new\":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:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\"},\"repo\":{\"did\":\"did:plc:d7crjayi6h6l3ek2wvnvl7qa\",\"handle\":\"alice.test\",\"name\":\"tally\"},\"action\":{\"$type\":\"dev.knotless.getFeed#created\",\"description\":\"Counts lines, words and bytes, with output that lines up\"}}]}" | ||
} | ||
} | ||
} | ||
harness/shots/views.spec.mjs+5−3
| @@ -33,9 +33,11 @@ for (const name of names) { | ||
await page.goto(path, { waitUntil: 'load' }) | ||
// The pages paint their highlighting once they are running. | ||
await page.locator('html[data-ready]').waitFor({ state: 'attached' }) | ||
// A DID is new each run, as the PLC directory makes one per account: | ||
// it is covered, so the rest can match to the pixel. | ||
await expect(page).toHaveScreenshot(`${name}.png`, { fullPage: true, mask: [page.locator('.did')], maskColor: '#80808a' }) | ||
// A DID is new each run, as the PLC directory makes one per account, | ||
// and a command naming this server names the port it is on: they are | ||
// covered, so the rest can match to the pixel. | ||
const mask = [page.locator('.did'), page.locator('.cmd', { hasText: /127\.0\.0\.1:\d+/ })] | ||
await expect(page).toHaveScreenshot(`${name}.png`, { fullPage: true, mask, maskColor: '#80808a' }) | ||
writeFileSync(info.snapshotPath(`${name}.sha256`), sums[name]) | ||
}) | ||
}) | ||
harness/shots/world.mjs+14−2
| @@ -10,6 +10,7 @@ | ||
// ctrl-c; written to target/shots/world.json | ||
| ||
import { spawn, execFileSync } from 'node:child_process' | ||
import { createHash } from 'node:crypto' | ||
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' | ||
import { tmpdir } from 'node:os' | ||
import { dirname, join, resolve } from 'node:path' | ||
| @@ -21,8 +22,10 @@ export const root = resolve(here, '../..') | ||
const fixture = join(here, 'fixture') | ||
const bin = join(root, 'target/debug') | ||
// Where the world answers, and the pages behind it on the next two ports: | ||
// SHOTS_PORT moves them, for a second checkout's shots beside a first's. | ||
export const port = Number(process.env.SHOTS_PORT ?? 2593) | ||
// three of their own for each checkout, from its path, so the shots of two | ||
// side by side do not meet; SHOTS_PORT moves them. | ||
const own = 20000 + 3 * (createHash('sha256').update(root).digest().readUInt16BE(0) % 3000) | ||
export const port = Number(process.env.SHOTS_PORT ?? own) | ||
export const base = `http://127.0.0.1:${port}` | ||
export const saved = join(root, 'target/shots/world.json') | ||
| ||
| @@ -196,7 +199,16 @@ export async function create() { | ||
git('alice', n, null, 'init', '-q', '-b', 'main') | ||
commit('alice', n, '2026-09-20T19:30:00Z', 'Begin', { 'notes/README.md': 'README.md' }) | ||
git('alice', n, null, 'push', '-q', notes, 'main') | ||
// A branch, then main moved past it: the feed lists only what main | ||
// brought that the branch had not. | ||
git('alice', n, null, 'checkout', '-q', '-b', 'reading') | ||
commit('alice', n, '2026-09-22T08:15:00Z', 'Start a reading list', { 'reading/reading.md': 'reading.md' }) | ||
git('alice', n, null, 'push', '-q', notes, 'reading') | ||
git('alice', n, null, 'checkout', '-q', 'main') | ||
git('alice', n, null, 'merge', '-q', '--ff-only', 'reading') | ||
commit('alice', n, '2026-09-23T21:40:00Z', 'Add Working in Public', { 'reading/more.md': 'reading.md' }) | ||
knotless('alice', 'create', 'knotless://alice.test/scratch') | ||
git('alice', n, null, 'push', '-q', notes, 'main') | ||
| ||
const fork = knotless('bob', 'fork', tally) | ||
const b = join(work, 'bob/tally') | ||
lexicons/dev/knotless/getFeed.json+4−3
| @@ -67,13 +67,14 @@ | ||
}, | ||
"pushed": { | ||
"type": "object", | ||
"description": "A ref created or moved. A new ref brings what the repository's default branch did not reach just before, nor its fork source's; a fork's copies of its source's refs are not events.", | ||
"required": ["ref", "created", "count", "commits"], | ||
"description": "A ref created or moved, or moved again and again by one account, each move within an hour of the last, as one. A new ref brings what the repository's default branch did not reach just before, nor its fork source's; a fork's copies of its source's refs are not events.", | ||
"required": ["ref", "created", "count", "new", "commits"], | ||
"properties": { | ||
"ref": { "type": "string", "description": "The full ref name, such as refs/heads/main." }, | ||
"created": { "type": "boolean", "description": "Whether the ref was new." }, | ||
"count": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Commits the push brought; 100 means at least that many." }, | ||
"commits": { "type": "array", "maxLength": 10, "items": { "type": "ref", "ref": "#commitView" }, "description": "The newest of them, newest first." } | ||
"new": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Of those, the commits no other ref of the repository had before, and no event before it on the same page lists; the rest are only counted." }, | ||
"commits": { "type": "array", "maxLength": 10, "items": { "type": "ref", "ref": "#commitView" }, "description": "The newest of the new ones, newest first." } | ||
} | ||
}, | ||
"deleted": { | ||
web/src/app.css+1−1
| @@ -211,6 +211,6 @@ main.wrap{padding-top:1.25rem} | ||
.start .cmd{background:var(--panel)} | ||
.results h1{font-size:18px;font-weight:600;margin:1rem 0} | ||
.results .find{margin-bottom:1.25rem} | ||
@media (max-width:640px){.feed>li{flex-direction:column;gap:.1rem}.feed time{width:auto}} | ||
@media (max-width:640px){.feed>li{flex-direction:column;align-items:stretch;gap:.1rem}.feed time{width:auto}} | ||
::highlight(comment){color:var(--comment)}::highlight(string){color:var(--string)}::highlight(keyword){color:var(--keyword)}::highlight(number){color:var(--number)}::highlight(function){color:var(--function)}::highlight(type){color:var(--type)}::highlight(tag){color:var(--tag)}::highlight(attr){color:var(--attr)} | ||
::highlight(xins){background-color:var(--ins-x)}::highlight(xdel){background-color:var(--del-x)}::highlight(target){background-color:var(--target)} | ||
web/src/lib/api/FeedAction.ts+1−1
| @@ -2,4 +2,4 @@ | ||
import type { FeedCommit } from "./FeedCommit"; | ||
import type { FeedRepo } from "./FeedRepo"; | ||
| ||
export type FeedAction = { "$type": "dev.knotless.getFeed#created", description?: string, forkOf?: FeedRepo, } | { "$type": "dev.knotless.getFeed#pushed", ref: string, created: boolean, count: number, commits: Array<FeedCommit>, } | { "$type": "dev.knotless.getFeed#deleted", ref: string, } | { "$type": "dev.knotless.getFeed#opened", subject: string, title: string, } | { "$type": "dev.knotless.getFeed#commented", subject: string, title: string, text: string, verdict?: string, } | { "$type": "dev.knotless.getFeed#changed", subject: string, title: string, state: string, }; | ||
export type FeedAction = { "$type": "dev.knotless.getFeed#created", description?: string, forkOf?: FeedRepo, } | { "$type": "dev.knotless.getFeed#pushed", ref: string, created: boolean, count: number, new: number, commits: Array<FeedCommit>, } | { "$type": "dev.knotless.getFeed#deleted", ref: string, } | { "$type": "dev.knotless.getFeed#opened", subject: string, title: string, } | { "$type": "dev.knotless.getFeed#commented", subject: string, title: string, text: string, verdict?: string, } | { "$type": "dev.knotless.getFeed#changed", subject: string, title: string, state: string, }; | ||
web/src/lib/molecules/CommitList.svelte+7−3
| @@ -1,9 +1,13 @@ | ||
<script lang="ts"> | ||
import type { FeedCommit } from '#lib/api/FeedCommit.ts'; | ||
import { subject } from '#lib/format.ts'; | ||
/** The commits a push brought, the first few listed and the rest counted. */ | ||
let { commits, count, repo }: { commits: FeedCommit[]; count: number; repo: string } = $props(); | ||
/** | ||
* The commits a push brought, the first few listed and the rest counted; | ||
* and those `elsewhere`, which other branches had first, counted only. | ||
*/ | ||
let { commits, count, elsewhere, repo }: { commits: FeedCommit[]; count: number; elsewhere: number; repo: string } = $props(); | ||
const LISTED = 5; | ||
const other = $derived(elsewhere === 1 ? 'on another branch' : 'on other branches'); | ||
</script> | ||
| ||
{#if commits.length}<ul class="commits">{#each commits.slice(0, LISTED) as c (c.sha)}<li><a class="hash" href="{repo}/commit/{c.sha}">{c.sha.slice(0, 7)}</a><span class="clip">{subject(c.message)}</span></li>{/each}{#if count > LISTED}<li class="rest">and {count - LISTED} more</li>{/if}</ul>{/if} | ||
{#if commits.length || elsewhere}<ul class="commits">{#each commits.slice(0, LISTED) as c (c.sha)}<li><a class="hash" href="{repo}/commit/{c.sha}">{c.sha.slice(0, 7)}</a><span class="clip">{subject(c.message)}</span></li>{/each}{#if count > LISTED}<li class="rest">and {count - LISTED} more</li>{/if}{#if elsewhere}<li class="rest">{commits.length ? `and ${elsewhere} ${other}` : `${elsewhere} ${other}`}</li>{/if}</ul>{/if} | ||
web/src/lib/molecules/FeedItem.svelte+1−1
| @@ -41,7 +41,7 @@ | ||
{#if a.$type === 'dev.knotless.getFeed#created' && a.description} | ||
<p class="feed-note">{a.description}</p> | ||
{:else if a.$type === 'dev.knotless.getFeed#pushed'} | ||
<CommitList commits={a.commits} count={a.count} {repo} /> | ||
<CommitList commits={a.commits} count={a.new} elsewhere={a.count - a.new} {repo} /> | ||
{:else if a.$type === 'dev.knotless.getFeed#commented'} | ||
<p class="feed-note">“{excerpt(a.text, 120)}”</p> | ||
{/if} | ||