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

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

Pages go brotli, run their theme script inline, and have an icon

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:web/brotli-and-small-fixes
crates/knotless-serve/src/web/pages.rs+6−2
@@ -35,10 +35,14 @@ static CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
.expect("a client builds")
});
/// A router that hands every request to the app at `web`.
/// A router that hands every request to the app at `web`: what it answers
/// compressed as this server's own answers are, brotli to a browser that
/// takes it, but for what the app compressed itself, as its scripts.
pub fn pages(web: String) -> Router {
let web: &'static str = web.trim_end_matches('/').to_string().leak();
Router::new().fallback(move |req: Request| page(web, req))
Router::new()
.fallback(move |req: Request| page(web, req))
.layer(tower_http::compression::CompressionLayer::new())
crates/knotless-serve/src/web/pages.rsline 45
review.knotless.dev

This uses the default level, not the quality 4 the body describes. Set it with CompressionLayer::new().quality(CompressionLevel::Precise(4)).

chadtmiller.com

The default is quality 4 for brotli, not 11: tower-http 0.7.1, which this workspace pins, sets it so itself in src/compression/body.rs (lines 333-339): CompressionLevel::Default => async_compression::Level::Precise(4), with the comment that brotli's own default of 11 is too slow, and that 4 is NGINX's default for on-the-fly brotli. Setting .quality(Precise(4)) here would do the same for brotli but also drop gzip, for browsers without brotli, from its default 6 to 4. So I've left it at the default, which the XRPC layers use too.

}
async fn page(web: &str, req: Request) -> Response {
crates/knotless-serve/tests/web.rs+19−0
@@ -2679,6 +2679,10 @@ fn pages_drawn_elsewhere() {
res.headers_mut()
.insert("referrer-policy", policy.parse().unwrap());
}
if uri.path() == "/packed" {
res.headers_mut()
.insert("content-encoding", "zstd".parse().unwrap());
}
res
},
);
@@ -2729,6 +2733,21 @@ fn pages_drawn_elsewhere() {
let own = client.get(format!("{base}/own")).send().unwrap();
assert_eq!(own.headers()["referrer-policy"], "no-referrer");
assert_eq!(own.json::<Value>().unwrap()["proto"], "http");
// A page goes compressed to a browser that takes brotli; what the app
// compressed itself goes as it is.
let squeezed = |at: &str| {
let res = client
.get(format!("{base}{at}"))
.header("accept-encoding", "br")
.send()
.unwrap();
res.headers()["content-encoding"]
.to_str()
.unwrap()
.to_string()
};
assert_eq!(squeezed("/alice.test/tally"), "br");
assert_eq!(squeezed("/packed"), "zstd");
// What knotless-serve answers itself is not handed on.
let ours = client
.get(format!("{base}/xrpc/dev.knotless.getFeed"))
docs/design/serve.md+10−5
@@ -712,7 +712,9 @@ branches.
The pages are a SvelteKit app in `web/`, run separately. The server hands it
every request it does not answer itself, and the app asks the server for each
page's data, then draws the HTML. It asks `dev.knotless.*` XRPC queries, as
page's data, then draws the HTML. The server compresses what the app answers
as it does its own, brotli to a browser that takes it, but for what the app
sends compressed already, as its scripts. It asks `dev.knotless.*` XRPC queries, as
atproto's appviews answer theirs: each with a lexicon under `lexicons/`,
sharing the views of `dev.knotless.defs` (a repository, a commit, a check, a
diff, a pull or an issue). Pictures a page shows, a check's or an account's own,
@@ -1397,10 +1399,13 @@ it goes in a cookie as it is. Each theme is a block of colour tokens in
`web/src/tokens.css`. A pair keeps its night half in `theme-night` and
`palette-night`, and the server writes both halves on `<html>`, as
`data-day` and `data-night`, with the palettes of either in a `<style>`
of their own; `/theme.js`, before the page is drawn, takes the half the
system's light or dark says, as `data-theme` and `data-half`, and again
as it changes, so the pages flip with the system without a reload. Its
own file, as the pages allow no script inline.
of their own; a script in the page's head, before the page is drawn,
takes the half the system's light or dark says, as `data-theme` and
`data-half`, and again as it changes, so the pages flip with the system
without a reload. It is in the page rather than a file of its own, so
that nothing is asked for before the page is drawn, and the pages'
policy lets it run by its hash alone, taken from `web/src/app.html` as
the pages are built.
Anyone can make a theme of their own colours, and publish it for anyone to
choose. A `dev.knotless.theme` record holds one: a name, and a palette of
web/src/lib/organisms/CodeResults.svelte+3−1
@@ -73,7 +73,9 @@
{#each f.chunks as c, i (i)}
{@const shown = lines(c)}
<div class="code code-result">
<pre class="ln" aria-hidden="true">{#each shown as l (l.n)}<a href={href(f, l.n)}>{l.n}</a
<pre class="ln" aria-hidden="true">{#each shown as l (l.n)}<a
href={href(f, l.n)}
tabindex="-1">{l.n}</a
>{'\n'}{/each}</pre>
<pre class="src"><code use:painted={c.highlights}
>{#each shown as l (l.n)}{#each l.pieces as p, j (j)}{#if p.found}<mark>{p.text}</mark
web/src/lib/organisms/CodeView.svelte+2−1
@@ -45,7 +45,8 @@
class="ln"
aria-hidden="true"
bind:this={ln}
onclick={mark}>{#each { length: lines }, i (i)}<a id="L{i + 1}" href="#L{i + 1}">{i + 1}</a
onclick={mark}>{#each { length: lines }, i (i)}<a id="L{i + 1}" href="#L{i + 1}" tabindex="-1"
>{i + 1}</a
>{'\n'}{/each}</pre>
<pre class="src" use:highlight={{ ranges, text }}><code>{text}</code></pre>
</div>
web/src/lib/theme.ts+2−2
@@ -13,7 +13,7 @@
// A pair, one theme by day and another at night, keeps its night half in
// `theme-night` and `palette-night` too. The server writes both halves on
// <html>, as `data-day` and `data-night`, with any palette of either in a
// <style> of its own; and /theme.js, before the page is drawn, takes the
// <style> of its own; and app.html's script, before the page is drawn, takes the
// half the system's light or dark says, as `data-theme` and `data-half`,
// and again as it changes.
@@ -182,7 +182,7 @@ export function halvesStyle(day: Palette | null, night: Palette | null): string
return rule('day', day) + rule('night', night);
}
/** Shows the half of a pair the system's light or dark says, as /theme.js does. */
/** Shows the half of a pair the system's light or dark says, as app.html's script does. */
function flip() {
const html = document.documentElement;
const half = matchMedia('(prefers-color-scheme: dark)').matches ? 'night' : 'day';
web/src/routes/[authority]/[repo]/commits/[...rest]/+page.svelte+1−1
@@ -67,7 +67,7 @@
tags={data.tags}
/>
{#each days as [day, commits] (commits[0].commit.sha)}
<h3 class="day" class:zoneless={!zone.tz}><Icon name="commit" />Commits on {day}</h3>
<h2 class="day" class:zoneless={!zone.tz}><Icon name="commit" />Commits on {day}</h2>
<div class="box">
<ul class="list">
{#each commits as { commit: c, checks } (c.sha)}
web/src/app.html+25−1
@@ -4,7 +4,31 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<script src="/theme.js"></script>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<!--
Draws the page in the half of its pair of themes the system's light
or dark says: `data-day` while it is light, `data-night` while it is
dark, as `data-theme`, with `data-half` naming which
(src/lib/theme.ts). Run before the page is drawn, and again as the
system changes; a page with no pair is left as it is. Here rather
than a file of its own, so that a page asks for nothing before it is
drawn: vite.config.ts lets it run by its hash.
-->
<script>
(() => {
const html = document.documentElement;
const dark = matchMedia('(prefers-color-scheme: dark)');
const flip = () => {
const half = dark.matches ? 'night' : 'day';
const id = html.dataset[half];
if (!id) return;
html.dataset.half = half;
html.dataset.theme = id;
};
flip();
dark.addEventListener('change', flip);
})();
</script>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
web/src/hooks.server.ts+1−1
@@ -25,7 +25,7 @@ function half(theme: string | null, said: string | undefined) {
return theme && (palette || !theme.startsWith('custom-')) ? { id: theme, palette } : null;
}
/** What `<html>` says of a pair, by day and at night, and the <style> its palettes are drawn from: /theme.js shows the half the system says. */
/** What `<html>` says of a pair, by day and at night, and the <style> its palettes are drawn from: app.html's script shows the half the system says. */
function pair(day: ReturnType<typeof half>, night: ReturnType<typeof half>) {
if (!day || !night) return null;
const own = halvesStyle(day.palette, night.palette);
web/static/favicon.svgnew+1−0
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><rect width="1024" height="1024" fill="#171717"/><path d="M204.8 542.7 L211.2 536.4 L217.6 530.1 L224.0 523.8 L230.4 517.6 L236.8 511.5 L243.2 505.6 L249.6 499.7 L256.0 494.1 L262.4 488.6 L268.8 483.3 L275.2 478.3 L281.6 473.5 L288.0 469.0 L294.4 464.7 L300.8 460.7 L307.2 457.1 L313.6 453.8 L320.0 450.8 L326.4 448.1 L332.8 445.8 L339.2 443.9 L345.6 442.3 L352.0 441.1 L358.4 440.3 L364.8 439.9 L371.2 439.8 L377.6 440.1 L384.0 440.7 L390.4 441.7 L396.8 443.1 L403.2 444.8 L409.6 446.9 L416.0 449.2 L422.4 451.9 L428.8 454.9 L435.2 458.1 L441.6 461.7 L448.0 465.4 L454.4 469.4 L460.8 473.6 L467.2 478.0 L473.6 482.5 L480.0 487.2 L486.4 492.0 L492.8 496.9 L499.2 501.9 L505.6 506.9 L512.0 512.0 L518.4 517.1 L524.8 522.1 L531.2 527.1 L537.6 532.0 L544.0 536.8 L550.4 541.5 L556.8 546.0 L563.2 550.4 L569.6 554.6 L576.0 558.6 L582.4 562.3 L588.8 565.9 L595.2 569.1 L601.6 572.1 L608.0 574.8 L614.4 577.1 L620.8 579.2 L627.2 580.9 L633.6 582.3 L640.0 583.3 L646.4 583.9 L652.8 584.2 L659.2 584.1 L665.6 583.7 L672.0 582.9 L678.4 581.7 L684.8 580.1 L691.2 578.2 L697.6 575.9 L704.0 573.2 L710.4 570.2 L716.8 566.9 L723.2 563.3 L729.6 559.3 L736.0 555.0 L742.4 550.5 L748.8 545.7 L755.2 540.7 L761.6 535.4 L768.0 529.9 L774.4 524.3 L780.8 518.4 L787.2 512.5 L793.6 506.4 L800.0 500.2 L806.4 493.9 L812.8 487.6 L819.2 481.3" fill="none" stroke="#fff" stroke-width="76.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
web/static/theme.jsdeleted+0−18
web/vite.config.ts+8−1