<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<updated>2026-09-01T14:18:11.742Z</updated>
<title>shuppy — autost</title>

<entry>
<id>10000402.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000402.html"/>
<published>2026-07-10T11:46:36.090Z</published>
<title>embedding a javascript runtime in rust, part 4: faster handles</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="rust-lang" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000402.md&quot; id=&quot;thread-10000402.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;&lt;a href=&quot;/posts/10000401.html&quot; rel=&quot;noopener noreferrer&quot;&gt;previously&lt;/a&gt; we created our first real abstraction, a Handle type that represents a javascript object in rust, and keeps the object alive until it’s dropped.&lt;/p&gt;
&lt;p&gt;but if we benchmark it, we find that creating and destroying a Handle takes well over 1500ns, which is incredibly inefficient. what gives?&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;#![cfg_attr(feature = &quot;bench&quot;, feature(test))]
#[cfg(feature = &quot;bench&quot;)]
extern crate test;

#[cfg(feature = &quot;bench&quot;)]
#[bench]
fn bench(bencher: &amp;amp;mut test::bench::Bencher) {
    unsafe {
        let ctx = Context::create();
        ctx.push_c_function(Some(print), DUK_VARARGS);
        ctx.put_global_string(c&quot;print&quot;.as_ptr());

        ctx.eval_string(cr#&quot;
            var HANDLES = {};
            var NEXT_HANDLE_KEY = 0;
        &quot;#.as_ptr());
        ctx.pop();

        &lt;strong style=&quot;color:var(--not-black)&quot;&gt;bencher.iter(|| {
            ctx.push_string(c&quot;ao!!&quot;.as_ptr());
            let handle = Handle::new_from_stack(&amp;amp;ctx, -1);
            ctx.pop();
        });&lt;/strong&gt;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:       1,709.24 ns/iter (+/- 14.79)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2&gt;8. optimising handles with samply&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/mstange/samply&quot; rel=&quot;noopener noreferrer&quot;&gt;samply&lt;/a&gt; is a sampling profiler that integrates with the excellent &lt;a href=&quot;https://profiler.firefox.com&quot; rel=&quot;noopener noreferrer&quot;&gt;firefox profiler&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;i used to use &lt;a href=&quot;https://github.com/flamegraph-rs/flamegraph&quot; rel=&quot;noopener noreferrer&quot;&gt;cargo-flamegraph&lt;/a&gt;, and &lt;a href=&quot;https://github.com/brendangregg/FlameGraph&quot; rel=&quot;noopener noreferrer&quot;&gt;flamegraph.pl&lt;/a&gt; before that, but the firefox profiler UI is so much more powerful (and unlike the &lt;a href=&quot;https://ui.perfetto.dev&quot; rel=&quot;noopener noreferrer&quot;&gt;perfetto UI&lt;/a&gt;, i actually understand how to use it effectively). samply itself is also both faster and easier to use, in large part because it resolves stacks and symbols on the fly, no postprocessing needed.&lt;/p&gt;
&lt;p&gt;let’s get samply and perf (required by samply on linux), and while we’re at it, let’s set up &lt;a href=&quot;https://github.com/rui314/mold&quot; rel=&quot;noopener noreferrer&quot;&gt;mold&lt;/a&gt; for faster linking, since we’re gonna be iterating a lot.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;shell.nix&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;with import &amp;lt;nixpkgs&amp;gt; {};
(mkShell.override { stdenv = clangStdenv; }) {
  name = &quot;duktest-shell&quot;;
  LIBCLANG_PATH = lib.makeLibraryPath [ llvmPackages.clang-unwrapped.lib ];
  buildInputs = [ &lt;strong style=&quot;color:var(--not-black)&quot;&gt;samply perf mold&lt;/strong&gt; ];
  &lt;strong style=&quot;color:var(--not-black)&quot;&gt;RUSTFLAGS = &quot;-Clink-arg=-fuse-ld=mold&quot;;&lt;/strong&gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;we’ll need a couple of linux kernel settings too. the former will probably be familiar to anyone who has done profiling on linux, but the latter is also needed to avoid a “Failed to start profiling: mmap failed” error when running samply.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid
$ sudo sysctl kernel.perf_event_mlock_kb=2048
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;now let’s take a profile. for each profile i take, i’ll resolve everything and upload it, so you can see what i’m seeing.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --no-run --features bench
$ samply record -- cargo +nightly bench --features bench
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;8.1. avoiding bytecode compilation (1709ns → 331ns)&lt;/h3&gt;
&lt;p&gt;focusing on the &lt;code&gt;bencher.iter()&lt;/code&gt; closure, 88% of the time is spent in &lt;code&gt;duk_eval_raw()&lt;/code&gt;, and 80pp of that is in &lt;code&gt;duk_compile_raw()&lt;/code&gt;. we’re compiling &lt;code&gt;NEXT_HANDLE_KEY++&lt;/code&gt; over and over, every single time we create a handle!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/44UWvlj&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/44UWvlj&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/attachments/3c113bd2-433b-43c1-9b29-d3030e69a2d1/pasted.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;let’s turn that into a function.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;diff --git a/src/context.rs b/src/context.rs
index c8090f0..c192483 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -25,2 +25,5 @@ impl Context {
                 var NEXT_HANDLE_KEY = 0;
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+                function next_handle_key() {
+                    return NEXT_HANDLE_KEY++;
+                }&lt;/strong&gt;
             &quot;#.as_ptr());
diff --git a/src/main.rs b/src/main.rs
index 4d13455..7a8ecb4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,3 +43,4 @@ impl Handle&amp;lt;&#x27;_&amp;gt; {
             ctx.get_global_string(c&quot;HANDLES&quot;.as_ptr());
-            ctx.eval_string(c&quot;NEXT_HANDLE_KEY++&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            ctx.get_global_string(c&quot;next_handle_key&quot;.as_ptr());
+            ctx.call(0);&lt;/strong&gt;
             let key = ctx.get_uint(-1);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:         331.94 ns/iter (+/- 3.03)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;8.2. caching heap pointers (331ns → 245ns)&lt;/h3&gt;
&lt;p&gt;now we’re spending 29% of our time in &lt;code&gt;duk_get_global_string()&lt;/code&gt;, and 22pp of that in &lt;code&gt;duk_get_prop()&lt;/code&gt;. looking up properties is not fast!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/4gqvc9A&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/4gqvc9A&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/attachments/cf908f0f-e4ed-4b94-a2f4-11b17c2f991a/pasted.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;let’s cache the heap pointers for &lt;code&gt;HANDLES&lt;/code&gt; and &lt;code&gt;next_handle_key()&lt;/code&gt;.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;diff --git a/src/context.rs b/src/context.rs
index c192483..8d02f0d 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -7,2 +7,4 @@ pub struct Context {
     ctx: *mut duk_context,
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    pub(crate) handles: *mut c_void,
+    pub(crate) next_handle_key: *mut c_void,&lt;/strong&gt;
 }
@@ -31,3 +33,9 @@ impl Context {

-            Self { ctx }
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            duk_get_global_string(ctx, c&quot;HANDLES&quot;.as_ptr());
+            duk_get_global_string(ctx, c&quot;next_handle_key&quot;.as_ptr());
+            let handles = duk_get_heapptr(ctx, -2);
+            let next_handle_key = duk_get_heapptr(ctx, -1);
+            duk_pop_2(ctx);
+
+            Self { ctx, handles, next_handle_key }&lt;/strong&gt;
         }
diff --git a/src/main.rs b/src/main.rs
index ca3c650..07005f1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,3 +28,3 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
             // `delete HANDLES[rust self.key]`
-            self.ctx.get_global_string(c&quot;HANDLES&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            self.ctx.push_heapptr(self.ctx.handles);&lt;/strong&gt;
             self.ctx.del_prop_index(-1, self.key);
@@ -42,4 +42,4 @@ impl Handle&amp;lt;&#x27;_&amp;gt; {
             let index = ctx.normalize_index(index);
-            ctx.get_global_string(c&quot;HANDLES&quot;.as_ptr());
-            ctx.get_global_string(c&quot;next_handle_key&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            ctx.push_heapptr(ctx.handles);
+            ctx.push_heapptr(ctx.next_handle_key);&lt;/strong&gt;
             ctx.call(0);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:         245.00 ns/iter (+/- 8.03)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;8.3. avoiding js function calls (245ns → 152ns)&lt;/h3&gt;
&lt;p&gt;now we’re spending 33% of our time in duk__handle_call_raw(), 33% of our time in duk_put_prop(), and 22% of our time in duk_del_prop().&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/4vmq0qY&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/4vmq0qY&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/attachments/d6afe37a-e2b2-46d9-8df0-18a483e961cb/pasted.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;let’s move the steps for generating the next handle key out of javascript entirely. with a u64, we should be able to create 2&lt;sup&gt;32&lt;/sup&gt; new handles per second for 136 years, which is probably good enough. unfortunately duktape doesn’t support bigint yet, so we’ll only be able to create 2&lt;sup&gt;53&lt;/sup&gt; handles without collisions, which comes out to 2&lt;sup&gt;24&lt;/sup&gt; handles per second (59ns each) for 17 years. i don’t feel as comfortable with this, but we’ll replace it with something better soon anyway.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;diff --git a/src/context.rs b/src/context.rs
index 8d02f0d..e921295 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -8,3 +8,3 @@ pub struct Context {
     pub(crate) handles: *mut c_void,
-    pub(crate) next_handle_key: *mut c_void,
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    pub(crate) next_handle_key: std::sync::atomic::AtomicU64,&lt;/strong&gt;
 }
@@ -26,6 +26,2 @@ impl Context {
                 var HANDLES = {};
-                var NEXT_HANDLE_KEY = 0;
-                function next_handle_key() {
-                    return NEXT_HANDLE_KEY++;
-                }
             &quot;#.as_ptr());
@@ -34,8 +30,6 @@ impl Context {
             duk_get_global_string(ctx, c&quot;HANDLES&quot;.as_ptr());
-            duk_get_global_string(ctx, c&quot;next_handle_key&quot;.as_ptr());
-            let handles = duk_get_heapptr(ctx, -2);
-            let next_handle_key = duk_get_heapptr(ctx, -1);
-            duk_pop_2(ctx);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            let handles = duk_get_heapptr(ctx, -1);
+            duk_pop(ctx);&lt;/strong&gt;

-            Self { ctx, handles, next_handle_key }
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            Self { ctx, handles, next_handle_key: 0.into() }&lt;/strong&gt;
         }
diff --git a/src/main.rs b/src/main.rs
index 07005f1..3e321e8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,3 +20,3 @@ struct Handle&amp;lt;&#x27;ctx&amp;gt; {
     ctx: &amp;amp;&#x27;ctx Context,
-    key: duk_uint_t,
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    key: u64,&lt;/strong&gt;
 }
@@ -29,3 +29,4 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
             self.ctx.push_heapptr(self.ctx.handles);
-            self.ctx.del_prop_index(-1, self.key);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            self.ctx.push_number(self.key as f64);
+            self.ctx.del_prop(-2);&lt;/strong&gt;
             self.ctx.pop();
@@ -43,5 +44,4 @@ impl Handle&amp;lt;&#x27;_&amp;gt; {
             ctx.push_heapptr(ctx.handles);
-            ctx.push_heapptr(ctx.next_handle_key);
-            ctx.call(0);
-            let key = ctx.get_uint(-1);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            let key = ctx.next_handle_key.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+            ctx.push_number(key as f64);&lt;/strong&gt;
             ctx.dup(index);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:         152.22 ns/iter (+/- 1.02)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;8.4. avoiding stringifying integers (152ns → 103ns)&lt;/h3&gt;
&lt;p&gt;now we’re spending 44% of our time in duk_del_prop() and 39% of our time in duk_put_prop(), with 23pp plus 29pp of that in duk_to_string(). in other words, we’re spending over half of our time stringifying integers in decimal!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/4ym5iKO&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/4ym5iKO&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/attachments/9e149bc2-2bfe-481c-acce-822cac40f09f/pasted.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;let’s pack the bits of our u64 into a nine-byte string, of the form &lt;code&gt;FF xx xx xx xx xx xx xx xx&lt;/code&gt;&lt;sub&gt;h&lt;/sub&gt;. why the leading &lt;code&gt;FF&lt;/code&gt;&lt;sub&gt;h&lt;/sub&gt;?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://duktape.org/guide#type-string&quot; rel=&quot;noopener noreferrer&quot;&gt;duktape API strings&lt;/a&gt; are based on &lt;a href=&quot;https://en.wikipedia.org/wiki/CESU-8&quot; rel=&quot;noopener noreferrer&quot;&gt;CESU-8&lt;/a&gt;, which is like UTF-8 but encodes values above U+FFFF differently and less efficiently (making it not a superset of UTF-8):&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// U+10000 LINEAR B SYLLABLE B008 A
duk_eval_string(ctx, cr#&quot;&quot;\uD800\uDC00&quot;&quot;#.as_ptr());
let mut test_len = 0usize;
let test = duk_get_lstring(ctx, -1, &amp;amp;amp;mut test_len as *mut _);

// CESU-8: `[ed, a0, 80, ed, b0, 80]`
eprintln!(&quot;{:02x?}&quot;, std::slice::from_raw_parts(test as *const u8, test_len));
duk_pop(ctx);

// UTF-8: `[f0, 90, 80, 80]`
eprintln!(&quot;{:02x?}&quot;, &quot;\u{10000}&quot;.as_bytes());
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;but they also allow the encoding of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;unpaired surrogates, which are necessary to represent javascript strings faithfully. duktape encodes these using the same technique as CESU-8, albeit technically &lt;a href=&quot;https://www.unicode.org/reports/tr26/tr26-4.html&quot; rel=&quot;noopener noreferrer&quot;&gt;out of spec&lt;/a&gt;. the newer, more efficient UTF-8-based counterpart to this approach is &lt;a href=&quot;https://wtf-8.codeberg.page&quot; rel=&quot;noopener noreferrer&quot;&gt;WTF-8&lt;/a&gt;, created by my (now) colleague &lt;a href=&quot;https://exyr.org&quot; rel=&quot;noopener noreferrer&quot;&gt;Simon Sapin&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://duktape.org/guide#symbols&quot; rel=&quot;noopener noreferrer&quot;&gt;Symbol&lt;/a&gt; &lt;a href=&quot;https://duktape.org/api#defines.13&quot; rel=&quot;noopener noreferrer&quot;&gt;values&lt;/a&gt;, using byte sequences that are not otherwise valid. for example, Symbol.toPrimitive is &lt;code&gt;\x81Symbol.toPrimitive\xff&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;and “hidden” Symbol values, which can only be created outside javascript, and need not ever be exposed to javascript. notably these have the form &lt;code&gt;\xff&lt;/code&gt; followed by any sequence of bytes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;diff --git a/src/main.rs b/src/main.rs
index 3e321e8..61de949 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,3 +29,4 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
             self.ctx.push_heapptr(self.ctx.handles);
-            self.ctx.push_number(self.key as f64);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            let key_symbol = Self::key_to_symbol(self.key);
+            self.ctx.push_lstring(key_symbol.as_ptr() as *const _, key_symbol.len());&lt;/strong&gt;
             self.ctx.del_prop(-2);
@@ -37,2 +38,7 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
 impl Handle&amp;lt;&#x27;_&amp;gt; {
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    fn key_to_symbol(key: u64) -&amp;gt; [u8; 9] {
+        let key = key.to_ne_bytes();
+        [0xff, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]]
+    }
+&lt;/strong&gt;
     /// create a handle for the object at `index` on the stack,
@@ -45,3 +51,4 @@ impl Handle&amp;lt;&#x27;_&amp;gt; {
             let key = ctx.next_handle_key.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
-            ctx.push_number(key as f64);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            let key_symbol = Self::key_to_symbol(key);
+            ctx.push_lstring(key_symbol.as_ptr() as *const _, key_symbol.len());&lt;/strong&gt;
             ctx.dup(index);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:         103.73 ns/iter (+/- 0.67)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h3&gt;8.5. array object fast path (103ns → 79ns)&lt;/h3&gt;
&lt;p&gt;now we’re spending 39% of our time in duk_push_lstring(), with 37pp of that in duk_heap_strtable_intern(), and we also spend 28% and 16% in duk_put_prop() and duk_del_prop() respectively. in general, javascript property keys are strings, and property accesses require both string interning and hash table lookup.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/3SWUsdR&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/3SWUsdR&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/posts/attachments/ac88ae9f-e6d6-4f23-8b86-0eb8b3c3bc0f/pasted.png&quot; alt=&quot;&quot; loading=&quot;lazy&quot;&gt;&lt;/p&gt;
&lt;p&gt;to make most &lt;a href=&quot;https://262.ecma-international.org/5.1/#sec-15.4&quot; rel=&quot;noopener noreferrer&quot;&gt;Array objects&lt;/a&gt; faster, duktape has a fast path for &lt;strong&gt;array index&lt;/strong&gt; accesses on those objects, which stores values as an array internally. for that fast path to kick in though, we would need to meet a few &lt;a href=&quot;https://github.com/svaarala/duktape/blob/v2.7.0/src-input/duk_hobject.h#L17-L21&quot; rel=&quot;noopener noreferrer&quot;&gt;requirements&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;the object needs to be an Array object&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;the array needs to be dense, not sparse&lt;/strong&gt; (no &lt;code&gt;delete&lt;/code&gt; holes)&lt;/li&gt;
&lt;li&gt;all array index properties need to have default attributes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;let’s rework &lt;code&gt;HANDLES&lt;/code&gt; to meet these requirements. if we’re gonna store handles as an array, we’ll want to reuse low indices whenever possible, to keep the array only as large as the peak number of &lt;em&gt;concurrent&lt;/em&gt; handles.&lt;/p&gt;
&lt;p&gt;another reason to reuse low indices is that &lt;a href=&quot;https://262.ecma-international.org/5.1/#sec-15.4&quot; rel=&quot;noopener noreferrer&quot;&gt;by definition&lt;/a&gt;, an array index is less than 2&lt;sup&gt;32&lt;/sup&gt;. my rule of thumb for avoiding collisions in integer id types is that &lt;strong&gt;u32&lt;/strong&gt; requires reuse to avoid collisions, &lt;strong&gt;u64&lt;/strong&gt; can be sequentially issued without reuse (up to 2&lt;sup&gt;32&lt;/sup&gt; per second for 136 years), and &lt;strong&gt;u128&lt;/strong&gt; can be &lt;a href=&quot;https://en.wikipedia.org/wiki/Birthday_problem&quot; rel=&quot;noopener noreferrer&quot;&gt;randomly issued&lt;/a&gt; without reuse (hence &lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)&quot; rel=&quot;noopener noreferrer&quot;&gt;UUIDv4&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;when we destroy handles, we can ensure that future handles reuse their keys in constant time and constant extra space, by encoding a &lt;strong&gt;free list&lt;/strong&gt; in their place, and keeping track of the head of that linked list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;initial state:&lt;br&gt;
&lt;code&gt;HANDLES = [object, object, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → none&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;destroy handle 1:&lt;br&gt;
&lt;code&gt;HANDLES = [object, null, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → handle 1 → none&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;destroy handle 0:&lt;br&gt;
&lt;code&gt;HANDLES = [1, null, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → handle 0 → handle 1 → none&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;create handle (handle 0):&lt;br&gt;
&lt;code&gt;HANDLES = [object, null, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → handle 1 → none&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;create handle (handle 1):&lt;br&gt;
&lt;code&gt;HANDLES = [object, object, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → none&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;create handle (handle 2):&lt;br&gt;
&lt;code&gt;HANDLES = [object, object, object, object]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;head&lt;/code&gt; → none&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;diff --git a/src/context.rs b/src/context.rs
index e921295..d1f8b65 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -1,2 +1,2 @@
-use std::{ffi::{CStr, c_char, c_void}, ptr::null_mut};
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+use std::{cell::Cell, ffi::{CStr, c_char, c_void}, ptr::null_mut, rc::Rc};&lt;/strong&gt;

@@ -8,3 +8,4 @@ pub struct Context {
     pub(crate) handles: *mut c_void,
-    pub(crate) next_handle_key: std::sync::atomic::AtomicU64,
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    pub(crate) next_handle_key: Cell&amp;lt;u32&amp;gt;,
+    pub(crate) free_handle_key: Rc&amp;lt;Cell&amp;lt;Option&amp;lt;u32&amp;gt;&amp;gt;&amp;gt;,&lt;/strong&gt;
 }
@@ -25,3 +26,3 @@ impl Context {
             duk_eval_string(ctx, cr#&quot;
-                var HANDLES = {};
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+                var HANDLES = [];&lt;/strong&gt;
             &quot;#.as_ptr());
@@ -33,3 +34,3 @@ impl Context {

-            Self { ctx, handles, next_handle_key: 0.into() }
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            Self { ctx, handles, next_handle_key: 0.into(), free_handle_key: Rc::new(None.into()) }&lt;/strong&gt;
         }
diff --git a/src/main.rs b/src/main.rs
index 61de949..15204c4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -20,3 +20,3 @@ struct Handle&amp;lt;&#x27;ctx&amp;gt; {
     ctx: &amp;amp;&#x27;ctx Context,
-    key: u64,
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+    key: u32,&lt;/strong&gt;
 }
@@ -27,7 +27,10 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
         unsafe {
-            // `delete HANDLES[rust self.key]`
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            // `HANDLES[rust self.key] = tombstone`&lt;/strong&gt;
             self.ctx.push_heapptr(self.ctx.handles);
-            let key_symbol = Self::key_to_symbol(self.key);
-            self.ctx.push_lstring(key_symbol.as_ptr() as *const _, key_symbol.len());
-            self.ctx.del_prop(-2);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            if let Some(old_free_handle_key) = self.ctx.free_handle_key.replace(Some(self.key)) {
+                self.ctx.push_uint(old_free_handle_key);
+            } else {
+                self.ctx.push_null();
+            }
+            self.ctx.put_prop_index(-2, self.key);&lt;/strong&gt;
             self.ctx.pop();
@@ -38,7 +41,2 @@ impl Drop for Handle&amp;lt;&#x27;_&amp;gt; {
 impl Handle&amp;lt;&#x27;_&amp;gt; {
-    fn key_to_symbol(key: u64) -&amp;gt; [u8; 9] {
-        let key = key.to_ne_bytes();
-        [0xff, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]]
-    }
-
     /// create a handle for the object at `index` on the stack,
@@ -47,10 +45,18 @@ impl Handle&amp;lt;&#x27;_&amp;gt; {
         unsafe {
-            // `HANDLES[NEXT_HANDLE_KEY++] = stack[rust index]`
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            // `HANDLES[key] = stack[rust index]`&lt;/strong&gt;
             let index = ctx.normalize_index(index);
             ctx.push_heapptr(ctx.handles);
-            let key = ctx.next_handle_key.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
-            let key_symbol = Self::key_to_symbol(key);
-            ctx.push_lstring(key_symbol.as_ptr() as *const _, key_symbol.len());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            let key = if let Some(key) = ctx.free_handle_key.get() {
+                ctx.get_prop_index(-1, key.into());
+                let old_free_handle_key = ctx.get_uint(-1);
+                ctx.free_handle_key.set(Some(old_free_handle_key));
+                ctx.pop();
+                key
+            } else {
+                let key = ctx.next_handle_key.get();
+                ctx.next_handle_key.set(key.checked_add(1).expect(&quot;too many handles&quot;));
+                key
+            };&lt;/strong&gt;
             ctx.dup(index);
-            ctx.put_prop(-3);
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;+            ctx.put_prop_index(-2, key);&lt;/strong&gt;
             ctx.pop();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test handle_new_drop ... bench:          79.64 ns/iter (+/- 6.64)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://share.firefox.dev/4pgwq9P&quot; rel=&quot;noopener noreferrer&quot;&gt;https://share.firefox.dev/4pgwq9P&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;just the beginning&lt;/h2&gt;
&lt;p&gt;we now have handles that can be created and destroyed in under 80ns.&lt;/p&gt;
&lt;p&gt;there’s so much more we can do with handles though! see &lt;a href=&quot;https://github.com/ntrrgc/jsvm&quot; rel=&quot;noopener noreferrer&quot;&gt;JSVM&lt;/a&gt;, by my friend &lt;a href=&quot;https://ntrrgc.me&quot; rel=&quot;noopener noreferrer&quot;&gt;Alicia&lt;/a&gt;, for another duktape binding where the handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;support &lt;a href=&quot;https://github.com/ntrrgc/jsvm/wiki/JSVM-internals/621604b9d2503b275275014bae4470ad6149d4a4#object-mapping-with-full-canonicalization&quot; rel=&quot;noopener noreferrer&quot;&gt;canonicalisation&lt;/a&gt; (at least for java → javascript) – take another handle to the same object, you get the same handle&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;support &lt;a href=&quot;https://github.com/ntrrgc/jsvm/wiki/Accessor-chains-for-type-error-messages/9b7906e82f4e68258a021b6cb9b2a8320a205bba&quot; rel=&quot;noopener noreferrer&quot;&gt;chaining&lt;/a&gt; – drill down into a nested object without creating handles for the intermediate levels, and improve the error messages for type errors&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rust-lang&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000401.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000401.html"/>
<published>2026-07-09T04:33:16.630Z</published>
<title>embedding a javascript runtime in rust, part 3: object handles</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="rust-lang" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000401.md&quot; id=&quot;thread-10000401.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;&lt;a href=&quot;/posts/10000400.html&quot; rel=&quot;noopener noreferrer&quot;&gt;previously&lt;/a&gt; we looked at how to call javascript functions in rust, and found a relatively efficient way to export rust structs to javascript.&lt;/p&gt;
&lt;p&gt;how do we access a javascript object from rust later, without keeping it on the &lt;a href=&quot;https://duktape.org/guide#programming.4&quot; rel=&quot;noopener noreferrer&quot;&gt;value stack&lt;/a&gt; the whole time? we could store it in a js variable, and look it up later:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;unsafe {}&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;let ctx = duk_create_heap(None, None, None, null_mut(), None);
duk_push_c_function(ctx, Some(print), DUK_VARARGS);
duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

duk_push_array(ctx);
duk_push_int(ctx, 0*0);
duk_put_prop_index(ctx, -2, 0);
duk_push_int(ctx, 1*1);
duk_put_prop_index(ctx, -2, 1);
duk_push_int(ctx, 2*2);
duk_put_prop_index(ctx, -2, 2);
duk_push_int(ctx, 3*3);
duk_put_prop_index(ctx, -2, 3);

// done with the array for now!
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;duk_put_global_string(ctx, c&quot;squares&quot;.as_ptr());&lt;/strong&gt;

assert_eq!(duk_get_top(ctx), 0);
// TODO: some other work
assert_eq!(duk_get_top(ctx), 0);

duk_get_global_string(ctx, c&quot;print&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;duk_get_global_string(ctx, c&quot;squares&quot;.as_ptr());&lt;/strong&gt;
duk_call(ctx, 1);
duk_pop(ctx);

println!();
duk_destroy_heap(ctx);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
0,1,4,9
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;this works, but property lookup can be slow. and for js objects that matter to rust, we really want to be able to interact with them as rust values, and tie their lifetimes to other values that depend on them.&lt;/p&gt;
&lt;h2&gt;6. js values must outlive their rust usage&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;((o)&lt;/code&gt; &lt;a href=&quot;https://duktape.org&quot; rel=&quot;noopener noreferrer&quot;&gt;duktape&lt;/a&gt; has a garbage-collected heap, which &lt;a href=&quot;https://duktape.org/guide#type-memory-allocations&quot; rel=&quot;noopener noreferrer&quot;&gt;makes allocations&lt;/a&gt; for strings, buffers, and (notably) objects. duktape also &lt;a href=&quot;https://duktape.org/guide#type-pointer-stability&quot; rel=&quot;noopener noreferrer&quot;&gt;never moves the top-level allocations for those&lt;/a&gt;, so it would seem as though we could grab the heap pointer for the array, and push it back onto the stack later when we need it again. but in reality, we crash.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;unsafe {}&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;// done with the array for now!
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;let squares = duk_get_heapptr(ctx, -1);&lt;/strong&gt;
duk_pop(ctx);

assert_eq!(duk_get_top(ctx), 0);
// TODO: some other work
assert_eq!(duk_get_top(ctx), 0);

duk_get_global_string(ctx, c&quot;print&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;duk_push_heapptr(ctx, squares);&lt;/strong&gt;
duk_call(ctx, 1);
duk_pop(ctx);

println!();
duk_destroy_heap(ctx);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
Segmentation fault
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;we no longer store the object in a variable, so it’s vulnerable to garbage collection as soon as we &lt;code&gt;duk_pop()&lt;/code&gt;. what we need is a rust value that:&lt;/p&gt;
&lt;ol style=&quot;list-style: lower-alpha;&quot;&gt;
&lt;li&gt;can access a javascript value at any time, and
&lt;/li&gt;&lt;li&gt;keeps that javascript value alive for as long as it lives.
&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;that’s a handle! we can achieve those two things by:&lt;/p&gt;
&lt;ol style=&quot;list-style: lower-alpha;&quot;&gt;
&lt;li&gt;&lt;p&gt;saving the heap pointer in the handle with &lt;code&gt;duk_get_heapptr()&lt;/code&gt;, then restoring it later with &lt;code&gt;duk_push_heapptr()&lt;/code&gt; whenever we need to access it.
&lt;/p&gt;&lt;/li&gt;&lt;li&gt;creating a strong reference to the object in javascript, by setting a property of a global variable, then deleting that property when the handle is dropped.
&lt;/li&gt;&lt;/ol&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;unsafe {}&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;duk_eval_string(ctx, cr#&quot;
    var HANDLES = {};
    var NEXT_HANDLE_KEY = 0;
&quot;#.as_ptr());
duk_pop(ctx);

/// rooting reference to a javascript object.
#[derive(Debug)]
struct Handle {
    /// heap pointer of the object, to access it in [`Handle::push()`]
    ptr: *mut c_void,

    /// handle key, to release the handle in [`Drop`]
    ctx: *mut duk_context,
    key: duk_uint_t,
}

&lt;strong style=&quot;color:var(--not-black)&quot;&gt;/// unroot the object, allowing it to be garbage collected.&lt;/strong&gt;
impl Drop for Handle {
    fn drop(&amp;amp;mut self) {
        unsafe {
            &lt;strong style=&quot;color:var(--not-black)&quot;&gt;// `delete HANDLES[rust self.key]`
            duk_get_global_string(self.ctx, c&quot;HANDLES&quot;.as_ptr());
            duk_del_prop_index(self.ctx, -1, self.key);&lt;/strong&gt;
            duk_pop(self.ctx);
        }
    }
}

impl Handle {
    &lt;strong style=&quot;color:var(--not-black)&quot;&gt;/// create a handle for the object at `index` on the stack,
    /// rooting it to prevent garbage collection.&lt;/strong&gt;
    fn new_from_stack(ctx: *mut duk_context, index: duk_idx_t) -&amp;gt; Handle {
        unsafe {
            &lt;strong style=&quot;color:var(--not-black)&quot;&gt;// `HANDLES[NEXT_HANDLE_KEY++] = stack[rust index]`
            let index = duk_normalize_index(ctx, index);
            duk_get_global_string(ctx, c&quot;HANDLES&quot;.as_ptr());
            duk_eval_string(ctx, c&quot;NEXT_HANDLE_KEY++&quot;.as_ptr());
            let key = duk_get_uint(ctx, -1);
            duk_dup(ctx, index);
            duk_put_prop(ctx, -3);&lt;/strong&gt;
            duk_pop(ctx);

            let ptr = duk_get_heapptr(ctx, index);
            Handle { ptr, ctx, key }
        }
    }

    &lt;strong style=&quot;color:var(--not-black)&quot;&gt;/// push the object onto the value stack.&lt;/strong&gt;
    fn push(&amp;amp;self) {
        unsafe {
            &lt;strong style=&quot;color:var(--not-black)&quot;&gt;duk_push_heapptr(self.ctx, self.ptr);&lt;/strong&gt;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;these handles work a bit better, but the Drop impl causes a crash if it happens after &lt;code&gt;duk_destroy_heap()&lt;/code&gt;, which is true for &lt;code&gt;let&lt;/code&gt; bindings in the same scope.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;unsafe {}&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;// done with the array for now!
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;let squares = Handle::new_from_stack(ctx, -1);&lt;/strong&gt;
duk_pop(ctx);

assert_eq!(duk_get_top(ctx), 0);
// TODO: some other work
assert_eq!(duk_get_top(ctx), 0);

duk_get_global_string(ctx, c&quot;print&quot;.as_ptr());
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;squares.push();&lt;/strong&gt;
duk_call(ctx, 1);
duk_pop(ctx);

println!();
duk_destroy_heap(ctx);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
0,1,4,9
Segmentation fault
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2&gt;7. the js heap must outlive all handles&lt;/h2&gt;
&lt;p&gt;we need to ensure that the duktape heap and context aren’t destroyed before we run the Drop impl of any Handle, because dropping a handle requires running instructions in the context. but &lt;code&gt;ctx&lt;/code&gt; is a &lt;a href=&quot;https://doc.rust-lang.org/1.94.0/std/primitive.pointer.html&quot; rel=&quot;noopener noreferrer&quot;&gt;raw pointer&lt;/a&gt; (&lt;code&gt;*mut duk_context&lt;/code&gt;), which has no lifetime.&lt;/p&gt;
&lt;p&gt;to give the context a lifetime, we need to use references. but raw pointers are Copy, which means we can trivially sneak them out of any reference, so we’ll need to create a non-Copy wrapper struct…&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/context.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;use std::{ffi::{CStr, c_char, c_void}, ptr::null_mut};

use crate::sys::*;

#[derive(Debug)]
&lt;strong style=&quot;color:var(--not-black)&quot;&gt;pub struct Context {
    ctx: *mut duk_context,
}&lt;/strong&gt;

impl Drop for Context {
    fn drop(&amp;amp;mut self) {
        unsafe {
            &lt;strong style=&quot;color:var(--not-black)&quot;&gt;duk_destroy_heap(self.ctx);&lt;/strong&gt;
        }
    }
}

impl Context {
    pub fn create() -&amp;gt; Self {
        unsafe {
            &lt;strong style=&quot;color:var(--not-black)&quot;&gt;let ctx = duk_create_heap(None, None, None, null_mut(), Some(fatal));
            Self { ctx }&lt;/strong&gt;
        }
    }
}

macro_rules! fn_wrapper {
    ($call_name:ident, $method_name:ident(ctx: *mut duk_context $(, $arg_name:ident: $arg_ty:ty)* $(,)?) $(-&amp;gt; $ret_ty:ty)?) =&amp;gt; {
        #[allow(unused)]
        pub unsafe fn $method_name(&amp;amp;self $(, $arg_name: $arg_ty)*) $(-&amp;gt; $ret_ty)? {
            unsafe {
                $call_name(self.ctx $(, $arg_name)*)
            }
        }
    };
}

impl Context {
    // wrappers for functions not supported by bindgen
    fn_wrapper!(duk_eval_string, eval_string(ctx: *mut duk_context, src: *const c_char));

    // wrappers mechanically derived from bindgen output for `duktape.h`
    fn_wrapper!(duk_push_undefined, push_undefined(ctx: *mut duk_context));
    fn_wrapper!(duk_push_null, push_null(ctx: *mut duk_context));
    fn_wrapper!(duk_push_boolean, push_boolean(ctx: *mut duk_context, val: duk_bool_t));
    fn_wrapper!(duk_push_true, push_true(ctx: *mut duk_context));
    fn_wrapper!(duk_push_false, push_false(ctx: *mut duk_context));
    fn_wrapper!(duk_push_number, push_number(ctx: *mut duk_context, val: duk_double_t));
    fn_wrapper!(duk_push_nan, push_nan(ctx: *mut duk_context));
    fn_wrapper!(duk_push_int, push_int(ctx: *mut duk_context, val: duk_int_t));
    fn_wrapper!(duk_push_uint, push_uint(ctx: *mut duk_context, val: duk_uint_t));
    fn_wrapper!(duk_push_string, push_string(ctx: *mut duk_context, str_: *const ::std::os::raw::c_char, ) -&amp;gt; *const ::std::os::raw::c_char);
    fn_wrapper!(duk_push_lstring, push_lstring(ctx: *mut duk_context, str_: *const ::std::os::raw::c_char, len: duk_size_t, ) -&amp;gt; *const ::std::os::raw::c_char);
    fn_wrapper!(duk_push_pointer, push_pointer(ctx: *mut duk_context, p: *mut ::std::os::raw::c_void));
    // ...
}

extern &quot;C&quot; fn fatal(_: *mut c_void, message: *const c_char) {
    unsafe {
        panic!(&quot;{:?}&quot;, CStr::from_ptr(message));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;…so we can borrow it in Handle. unfortunately (for my brevity) this also means we’ll have to rewrite all of the duktape calls from &lt;code&gt;duk_foo(ctx, ...)&lt;/code&gt; to &lt;code&gt;ctx.foo(...)&lt;/code&gt;.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;unsafe {}&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code style=&quot;color:var(--gray2)&quot;&gt;&lt;strong style=&quot;color:var(--not-black)&quot;&gt;let ctx = Context::create();&lt;/strong&gt;
ctx.push_c_function(Some(print), DUK_VARARGS);
ctx.put_global_string(c&quot;print&quot;.as_ptr());

ctx.push_array();
ctx.push_int(0*0);
ctx.put_prop_index(-2, 0);
ctx.push_int(1*1);
ctx.put_prop_index(-2, 1);
ctx.push_int(2*2);
ctx.put_prop_index(-2, 2);
ctx.push_int(3*3);
ctx.put_prop_index(-2, 3);

ctx.eval_string(cr#&quot;
    var HANDLES = {};
    var NEXT_HANDLE_KEY = 0;
&quot;#.as_ptr());
ctx.pop();

/// rooting reference to a javascript object.
#[derive(Debug)]
struct Handle&lt;strong style=&quot;color:var(--not-black)&quot;&gt;&amp;lt;&#x27;ctx&amp;gt;&lt;/strong&gt; {
    /// heap pointer of the object, to access it in [`Handle::push()`]
    ptr: *mut c_void,

    /// handle key, to release the handle in [`Drop`]
    &lt;strong style=&quot;color:var(--not-black)&quot;&gt;ctx: &amp;amp;&#x27;ctx Context,&lt;/strong&gt;
    key: duk_uint_t,
}

/// unroot the object, allowing it to be garbage collected.
impl Drop for Handle&lt;strong style=&quot;color:var(--not-black)&quot;&gt;&amp;lt;&#x27;_&amp;gt;&lt;/strong&gt; {
    fn drop(&amp;amp;mut self) {
        unsafe {
            // `delete HANDLES[rust self.key]`
            self.ctx.get_global_string(c&quot;HANDLES&quot;.as_ptr());
            self.ctx.del_prop_index(-1, self.key);
            self.ctx.pop();
        }
    }
}

impl Handle&lt;strong style=&quot;color:var(--not-black)&quot;&gt;&amp;lt;&#x27;_&amp;gt;&lt;/strong&gt; {
    /// create a handle for the object at `index` on the stack,
    /// rooting it to prevent garbage collection.
    fn new_from_stack(ctx: &lt;strong style=&quot;color:var(--not-black)&quot;&gt;&amp;amp;Context&lt;/strong&gt;, index: duk_idx_t) -&amp;gt; Handle&lt;strong style=&quot;color:var(--not-black)&quot;&gt;&amp;lt;&#x27;_&amp;gt;&lt;/strong&gt; {
        unsafe {
            // `HANDLES[NEXT_HANDLE_KEY++] = stack[rust index]`
            let index = ctx.normalize_index(index);
            ctx.get_global_string(c&quot;HANDLES&quot;.as_ptr());
            ctx.eval_string(c&quot;NEXT_HANDLE_KEY++&quot;.as_ptr());
            let key = ctx.get_uint(-1);
            ctx.dup(index);
            ctx.put_prop(-3);
            ctx.pop();

            let ptr = ctx.get_heapptr(index);
            Handle { ptr, ctx, key }
        }
    }

    /// push the object onto the value stack.
    fn push(&amp;amp;self) {
        unsafe {
            self.ctx.push_heapptr(self.ptr);
        }
    }
}

// done with the array for now!
let squares = Handle::new_from_stack(&amp;amp;ctx, -1);
ctx.pop();

assert_eq!(ctx.get_top(), 0);
// TODO: some other work
assert_eq!(ctx.get_top(), 0);

ctx.get_global_string(c&quot;print&quot;.as_ptr());
squares.push();
ctx.call(1);
ctx.pop();

println!();
// impl Drop for Handle will run first
// then impl Drop for Context will call duk_destroy_heap()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;with the Handle borrowing Context, rust ensures that the Context is dropped after the Handle when they both go out of scope. voilà!&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
0,1,4,9
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2&gt;stay tuned!&lt;/h2&gt;
&lt;p&gt;the Handle impl shown above is very inefficient, taking over 1500ns to create and destroy a single handle. next time, we’ll optimise it, down to less than 80ns.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rust-lang&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000400.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000400.html"/>
<published>2026-07-08T02:52:40.952Z</published>
<title>embedding a javascript runtime in rust, part 2: mixing languages</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="rust-lang" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000400.md&quot; id=&quot;thread-10000400.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;&lt;a href=&quot;/posts/10000399.html&quot; rel=&quot;noopener noreferrer&quot;&gt;previously&lt;/a&gt; we created some rust bindings for &lt;code&gt;((o)&lt;/code&gt; &lt;a href=&quot;https://duktape.org&quot; rel=&quot;noopener noreferrer&quot;&gt;duktape&lt;/a&gt;, a javascript runtime that turned out to be pretty easy to embed.&lt;/p&gt;
&lt;p&gt;we want to be able to pass structs from rust to javascript, so that scripts can act on the same information as the rest of the program.&lt;/p&gt;
&lt;p&gt;say we have a struct that represents the author of a blog post.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;struct Author {
    name: String,
    link_url: String,
    avatar_url: String,
    email_address: String,
    phone_number: String,
    pronouns: String,
    bio: String,
}

let author = Author {
    name: &quot;shuppy&quot;.to_owned(),
    link_url: &quot;https://shuppy.org&quot;.to_owned(),
    avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
    email_address: &quot;test@shuppy.org&quot;.to_owned(),
    phone_number: &quot;+61 491 570 006&quot;.to_owned(),
    pronouns: &quot;she/her&quot;.to_owned(),
    bio: &quot;
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
        incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
        exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
        dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
        Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
        mollit anim id est laborum.
    &quot;.repeat(100),
};

unsafe {
    todo!();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;h2&gt;4. calling js functions in rust&lt;/h2&gt;
&lt;p&gt;we want to let the user write a &lt;code&gt;linkify_author()&lt;/code&gt; function to customise the markdown we generate for links to the author. first we create a heap and context, then we run the user’s custom code, then we call the user’s function.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;let ctx = duk_create_heap(None, None, None, null_mut(), None);
duk_push_c_function(ctx, Some(print), DUK_VARARGS);
duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

// run the user’s custom code
duk_eval_string(ctx, cr#&quot;
    function linkify_author(author) {
        return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
    }
&quot;#.as_ptr());
duk_pop(ctx);

// TODO: call the user’s function

// `result = pop()`
println!(&quot;{:?}&quot;, CStr::from_ptr(duk_get_string(ctx, -1)));
duk_pop(ctx);

duk_destroy_heap(ctx);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;duktape’s interpreter is a stack machine. we interact with that machine by feeding it instructions that push values onto, pop values off, read from, or reorder the stack.&lt;/p&gt;
&lt;p&gt;to call &lt;code&gt;linkify_author()&lt;/code&gt;, we push the function, then the arguments (the author), then call that function with one argument, &lt;a href=&quot;https://duktape.org/api#duk_call&quot; rel=&quot;noopener noreferrer&quot;&gt;leaving the return value on the stack&lt;/a&gt;. but the author is a rust struct, so how do we push that?&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt; &amp;gt; &lt;code&gt;// TODO: call the user’s function&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// `push(linkify_author(rust author))`
duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
// TODO: duk_push_something(rust author);
duk_call(ctx, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;h2&gt;5. exporting rust structs to js&lt;/h2&gt;
&lt;p&gt;we need to build an object in javascript representing the Author value we have in rust.&lt;/p&gt;
&lt;p&gt;let’s reorder our instructions a bit, to untangle building the object from calling the function. this is more readable, but probably less efficient.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt; &amp;gt; &lt;code&gt;// TODO: call the user’s function&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// TODO: duk_push_something(rust author);

// `push(linkify_author(rust author))`
duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
duk_pull(ctx, -2);
duk_call(ctx, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;the obvious approach would be to build a javascript object that has all of the same fields as the rust Author, converted to javascript types.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt; &amp;gt; &lt;code&gt;// TODO: duk_push_something(rust author);&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// `author = {};`
// `for (key in author) author[key] = convert rust author[key];`
// `push(author)`
duk_push_object(ctx);
duk_push_lstring(ctx, author.name.as_ptr() as *const _, author.name.len());
duk_put_prop_string(ctx, -2, c&quot;name&quot;.as_ptr());
duk_push_lstring(ctx, author.link_url.as_ptr() as *const _, author.link_url.len());
duk_put_prop_string(ctx, -2, c&quot;link_url&quot;.as_ptr());
duk_push_lstring(ctx, author.avatar_url.as_ptr() as *const _, author.avatar_url.len());
duk_put_prop_string(ctx, -2, c&quot;avatar_url&quot;.as_ptr());
duk_push_lstring(ctx, author.email_address.as_ptr() as *const _, author.email_address.len());
duk_put_prop_string(ctx, -2, c&quot;email_address&quot;.as_ptr());
duk_push_lstring(ctx, author.phone_number.as_ptr() as *const _, author.phone_number.len());
duk_put_prop_string(ctx, -2, c&quot;phone_number&quot;.as_ptr());
duk_push_lstring(ctx, author.pronouns.as_ptr() as *const _, author.pronouns.len());
duk_put_prop_string(ctx, -2, c&quot;pronouns&quot;.as_ptr());
duk_push_lstring(ctx, author.bio.as_ptr() as *const _, author.bio.len());
duk_put_prop_string(ctx, -2, c&quot;bio&quot;.as_ptr());
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;this works!&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
&quot;[shuppy](https://shuppy.org)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;but if the javascript code doesn’t end up reading all of the fields, then we’ve wasted time copying a bunch of strings for nothing.&lt;/p&gt;
&lt;p&gt;what if we instead build an object that wraps an “inner” rust pointer, with getters for each field that only copy strings into javascript when needed?&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt; &amp;gt; &lt;code&gt;// TODO: duk_push_something(rust author);&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// `author = {inner: rust author};`
// `for (key in author) Object.defineProperty(author, key, {get});`
// `push(author)`
duk_push_object(ctx);
duk_push_pointer(ctx, &amp;amp;author as *const _ /* 🗞️ bad */ as *mut _);
duk_put_prop_string(ctx, -2, c&quot;inner&quot;.as_ptr());
macro_rules! define_author_getter {
    ($ctx:ident, $key:literal, |author| author.$field:ident) =&amp;gt; {{
        duk_push_string($ctx, $key.as_ptr());
        duk_push_c_function($ctx, Some(get), 1);
        duk_def_prop($ctx, -3, DUK_DEFPROP_HAVE_GETTER);
        extern &quot;C&quot; fn get(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
            unsafe {
                duk_push_this(ctx);
                duk_get_prop_string(ctx, -1, c&quot;inner&quot;.as_ptr());
                let author = duk_get_pointer(ctx, -1) as *const Author;
                duk_pop_2(ctx);
                let result = &amp;amp;(*author).$field;
                duk_push_lstring(ctx, result.as_ptr() as *const _, result.len());
            }
            1
        }
    }};
}
define_author_getter!(ctx, c&quot;name&quot;, |author| author.name);
define_author_getter!(ctx, c&quot;link_url&quot;, |author| author.link_url);
define_author_getter!(ctx, c&quot;avatar_url&quot;, |author| author.avatar_url);
define_author_getter!(ctx, c&quot;email_address&quot;, |author| author.email_address);
define_author_getter!(ctx, c&quot;phone_number&quot;, |author| author.phone_number);
define_author_getter!(ctx, c&quot;pronouns&quot;, |author| author.pronouns);
define_author_getter!(ctx, c&quot;bio&quot;, |author| author.bio);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;we’re defining getters for each field on the instance, and if we have more than one instance, that’s quite a few instructions per instance. let’s define them on a &lt;strong&gt;prototype&lt;/strong&gt;, which many instances can cheaply reuse.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs &amp;gt; &lt;code&gt;fn main()&lt;/code&gt; &amp;gt; &lt;code&gt;todo!()&lt;/code&gt; &amp;gt; &lt;code&gt;// TODO: duk_push_something(rust author);&lt;/code&gt;&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;duk_eval_string(ctx, cr#&quot;
    function Author(inner) {
        this.inner = inner;
    }
&quot;#.as_ptr());
duk_pop(ctx);

// `for (key in author) Object.defineProperty(Author.prototype, key, {get})`
duk_eval_string(ctx, c&quot;Author.prototype&quot;.as_ptr());
macro_rules! define_author_getter {
    ($ctx:ident, $key:literal, |author| author.$field:ident) =&amp;gt; {{
        duk_push_string($ctx, $key.as_ptr());
        duk_push_c_function($ctx, Some(get), 1);
        duk_def_prop($ctx, -3, DUK_DEFPROP_HAVE_GETTER);
        extern &quot;C&quot; fn get(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
            unsafe {
                duk_push_this(ctx);
                duk_get_prop_string(ctx, -1, c&quot;inner&quot;.as_ptr());
                let author = duk_get_pointer(ctx, -1) as *const Author;
                duk_pop_2(ctx);
                let result = &amp;amp;(*author).$field;
                duk_push_lstring(ctx, result.as_ptr() as *const _, result.len());
            }
            1
        }
    }};
}
define_author_getter!(ctx, c&quot;name&quot;, |author| author.name);
define_author_getter!(ctx, c&quot;link_url&quot;, |author| author.link_url);
define_author_getter!(ctx, c&quot;avatar_url&quot;, |author| author.avatar_url);
define_author_getter!(ctx, c&quot;email_address&quot;, |author| author.email_address);
define_author_getter!(ctx, c&quot;phone_number&quot;, |author| author.phone_number);
define_author_getter!(ctx, c&quot;pronouns&quot;, |author| author.pronouns);
define_author_getter!(ctx, c&quot;bio&quot;, |author| author.bio);
duk_pop(ctx);

// `push(new Author(rust author))`
duk_get_global_string(ctx, c&quot;Author&quot;.as_ptr());
duk_push_pointer(ctx, &amp;amp;author as *const _ /* 🗞️ bad */ as *mut _);
duk_new(ctx, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;indeed this is the fastest approach! if we subtract the ~360ns of work that is common to all three approaches, they take ~1167ns, ~595ns, and ~251ns respectively.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo +nightly bench --features bench
test empty_object     ... bench:         360.78 ns/iter (+/- 24.98)
test obvious_approach ... bench:       1,527.54 ns/iter (+/- 11.06)
test with_getters     ... bench:         955.30 ns/iter (+/- 10.53)
test with_prototype   ... bench:         611.16 ns/iter (+/- 6.16)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;h2&gt;stay tuned!&lt;/h2&gt;
&lt;p&gt;next time, we’ll look at building some abstractions over these bindings, plus some tools to help us manage the interpreter’s &lt;a href=&quot;https://duktape.org/guide#programming.4&quot; rel=&quot;noopener noreferrer&quot;&gt;value stack&lt;/a&gt; correctly.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;full src/main.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;mod sys;

use std::{ffi::CStr, io::{Write, stdout}, ptr::null_mut};

use crate::sys::*;

fn main() {
    struct Author {
        name: String,
        link_url: String,
        avatar_url: String,
        email_address: String,
        phone_number: String,
        pronouns: String,
        bio: String,
    }

    let author = Author {
        name: &quot;shuppy&quot;.to_owned(),
        link_url: &quot;https://shuppy.org&quot;.to_owned(),
        avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
        email_address: &quot;test@shuppy.org&quot;.to_owned(),
        phone_number: &quot;+61 491 570 006&quot;.to_owned(),
        pronouns: &quot;she/her&quot;.to_owned(),
        bio: &quot;
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
            dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
            mollit anim id est laborum.
        &quot;.repeat(100),
    };

    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        // run the user’s custom code
        duk_eval_string(ctx, cr#&quot;
            function linkify_author(author) {
                return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        duk_eval_string(ctx, cr#&quot;
            function Author(inner) {
                this.inner = inner;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        // `for (key in author) Object.defineProperty(Author.prototype, key, {get})`
        duk_eval_string(ctx, c&quot;Author.prototype&quot;.as_ptr());
        macro_rules! define_author_getter {
            ($ctx:ident, $key:literal, |author| author.$field:ident) =&amp;gt; {{
                duk_push_string($ctx, $key.as_ptr());
                duk_push_c_function($ctx, Some(get), 1);
                duk_def_prop($ctx, -3, DUK_DEFPROP_HAVE_GETTER);
                extern &quot;C&quot; fn get(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
                    unsafe {
                        duk_push_this(ctx);
                        duk_get_prop_string(ctx, -1, c&quot;inner&quot;.as_ptr());
                        let author = duk_get_pointer(ctx, -1) as *const Author;
                        duk_pop_2(ctx);
                        let result = &amp;amp;(*author).$field;
                        duk_push_lstring(ctx, result.as_ptr() as *const _, result.len());
                    }
                    1
                }
            }};
        }
        define_author_getter!(ctx, c&quot;name&quot;, |author| author.name);
        define_author_getter!(ctx, c&quot;link_url&quot;, |author| author.link_url);
        define_author_getter!(ctx, c&quot;avatar_url&quot;, |author| author.avatar_url);
        define_author_getter!(ctx, c&quot;email_address&quot;, |author| author.email_address);
        define_author_getter!(ctx, c&quot;phone_number&quot;, |author| author.phone_number);
        define_author_getter!(ctx, c&quot;pronouns&quot;, |author| author.pronouns);
        define_author_getter!(ctx, c&quot;bio&quot;, |author| author.bio);
        duk_pop(ctx);

        // `push(new Author(rust author))`
        duk_get_global_string(ctx, c&quot;Author&quot;.as_ptr());
        duk_push_pointer(ctx, &amp;amp;author as *const _ /* 🗞️ bad */ as *mut _);
        duk_new(ctx, 1);

        // `push(linkify_author(rust author))`
        duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
        duk_pull(ctx, -2);
        duk_call(ctx, 1);

        // `result = pop()`
        println!(&quot;{:?}&quot;, CStr::from_ptr(duk_get_string(ctx, -1)));
        duk_pop(ctx);

        duk_destroy_heap(ctx);
    }
}

extern &quot;C&quot; fn print(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
    unsafe {
        duk_push_string(ctx, c&quot; &quot;.as_ptr());
        duk_insert(ctx, 0);
        duk_join(ctx, duk_get_top(ctx) - 1);
        let text = duk_safe_to_string(ctx, -1);
        let text = CStr::from_ptr(text);
        let _ = stdout().write_all(text.to_bytes());
    }
    0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;extra code for cargo bench&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;#![cfg_attr(feature = &quot;bench&quot;, feature(test))]
#[cfg(feature = &quot;bench&quot;)]
extern crate test;

#[cfg(feature = &quot;bench&quot;)]
#[bench]
fn empty_object(bencher: &amp;amp;mut test::bench::Bencher) {
    struct Author {
        name: String,
        link_url: String,
        avatar_url: String,
        email_address: String,
        phone_number: String,
        pronouns: String,
        bio: String,
    }

    let author = Author {
        name: &quot;shuppy&quot;.to_owned(),
        link_url: &quot;https://shuppy.org&quot;.to_owned(),
        avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
        email_address: &quot;test@shuppy.org&quot;.to_owned(),
        phone_number: &quot;+61 491 570 006&quot;.to_owned(),
        pronouns: &quot;she/her&quot;.to_owned(),
        bio: &quot;
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
            dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
            mollit anim id est laborum.
        &quot;.repeat(100),
    };

    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        // run the user’s custom code
        duk_eval_string(ctx, cr#&quot;
            function linkify_author(author) {
                return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        bencher.iter(|| {
            // `push({})`
            duk_push_object(ctx);

            // `push(linkify_author(rust author))`
            duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
            duk_pull(ctx, -2);
            duk_call(ctx, 1);

            // `result = pop()`
            let result = CStr::from_ptr(duk_get_string(ctx, -1));
            duk_pop(ctx);
            result
        });

        duk_destroy_heap(ctx);
    }
}

#[cfg(feature = &quot;bench&quot;)]
#[bench]
fn obvious_approach(bencher: &amp;amp;mut test::bench::Bencher) {
    struct Author {
        name: String,
        link_url: String,
        avatar_url: String,
        email_address: String,
        phone_number: String,
        pronouns: String,
        bio: String,
    }

    let author = Author {
        name: &quot;shuppy&quot;.to_owned(),
        link_url: &quot;https://shuppy.org&quot;.to_owned(),
        avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
        email_address: &quot;test@shuppy.org&quot;.to_owned(),
        phone_number: &quot;+61 491 570 006&quot;.to_owned(),
        pronouns: &quot;she/her&quot;.to_owned(),
        bio: &quot;
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
            dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
            mollit anim id est laborum.
        &quot;.repeat(100),
    };

    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        // run the user’s custom code
        duk_eval_string(ctx, cr#&quot;
            function linkify_author(author) {
                return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        bencher.iter(|| {
            // `author = {};`
            // `for (key in author) author[key] = convert rust author[key];`
            // `push(author)`
            duk_push_object(ctx);
            duk_push_lstring(ctx, author.name.as_ptr() as *const _, author.name.len());
            duk_put_prop_string(ctx, -2, c&quot;name&quot;.as_ptr());
            duk_push_lstring(ctx, author.link_url.as_ptr() as *const _, author.link_url.len());
            duk_put_prop_string(ctx, -2, c&quot;link_url&quot;.as_ptr());
            duk_push_lstring(ctx, author.avatar_url.as_ptr() as *const _, author.avatar_url.len());
            duk_put_prop_string(ctx, -2, c&quot;avatar_url&quot;.as_ptr());
            duk_push_lstring(ctx, author.email_address.as_ptr() as *const _, author.email_address.len());
            duk_put_prop_string(ctx, -2, c&quot;email_address&quot;.as_ptr());
            duk_push_lstring(ctx, author.phone_number.as_ptr() as *const _, author.phone_number.len());
            duk_put_prop_string(ctx, -2, c&quot;phone_number&quot;.as_ptr());
            duk_push_lstring(ctx, author.pronouns.as_ptr() as *const _, author.pronouns.len());
            duk_put_prop_string(ctx, -2, c&quot;pronouns&quot;.as_ptr());
            duk_push_lstring(ctx, author.bio.as_ptr() as *const _, author.bio.len());
            duk_put_prop_string(ctx, -2, c&quot;bio&quot;.as_ptr());

            // `push(linkify_author(rust author))`
            duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
            duk_pull(ctx, -2);
            duk_call(ctx, 1);

            // `result = pop()`
            let result = CStr::from_ptr(duk_get_string(ctx, -1));
            duk_pop(ctx);
            result
        });

        duk_destroy_heap(ctx);
    }
}

#[cfg(feature = &quot;bench&quot;)]
#[bench]
fn with_getters(bencher: &amp;amp;mut test::bench::Bencher) {
    struct Author {
        name: String,
        link_url: String,
        avatar_url: String,
        email_address: String,
        phone_number: String,
        pronouns: String,
        bio: String,
    }

    let author = Author {
        name: &quot;shuppy&quot;.to_owned(),
        link_url: &quot;https://shuppy.org&quot;.to_owned(),
        avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
        email_address: &quot;test@shuppy.org&quot;.to_owned(),
        phone_number: &quot;+61 491 570 006&quot;.to_owned(),
        pronouns: &quot;she/her&quot;.to_owned(),
        bio: &quot;
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
            dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
            mollit anim id est laborum.
        &quot;.repeat(100),
    };

    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        // run the user’s custom code
        duk_eval_string(ctx, cr#&quot;
            function linkify_author(author) {
                return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        bencher.iter(|| {
            // `author = {inner: rust author};`
            // `for (key in author) Object.defineProperty(author, key, {get});`
            // `push(author)`
            duk_push_object(ctx);
            duk_push_pointer(ctx, &amp;amp;author as *const _ /* 🗞️ bad */ as *mut _);
            duk_put_prop_string(ctx, -2, c&quot;inner&quot;.as_ptr());
            macro_rules! define_author_getter {
                ($ctx:ident, $key:literal, |author| author.$field:ident) =&amp;gt; {{
                    duk_push_string($ctx, $key.as_ptr());
                    duk_push_c_function($ctx, Some(get), 1);
                    duk_def_prop($ctx, -3, DUK_DEFPROP_HAVE_GETTER);
                    extern &quot;C&quot; fn get(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
                        unsafe {
                            duk_push_this(ctx);
                            duk_get_prop_string(ctx, -1, c&quot;inner&quot;.as_ptr());
                            let author = duk_get_pointer(ctx, -1) as *const Author;
                            duk_pop_2(ctx);
                            let result = &amp;amp;(*author).$field;
                            duk_push_lstring(ctx, result.as_ptr() as *const _, result.len());
                        }
                        1
                    }
                }};
            }
            define_author_getter!(ctx, c&quot;name&quot;, |author| author.name);
            define_author_getter!(ctx, c&quot;link_url&quot;, |author| author.link_url);
            define_author_getter!(ctx, c&quot;avatar_url&quot;, |author| author.avatar_url);
            define_author_getter!(ctx, c&quot;email_address&quot;, |author| author.email_address);
            define_author_getter!(ctx, c&quot;phone_number&quot;, |author| author.phone_number);
            define_author_getter!(ctx, c&quot;pronouns&quot;, |author| author.pronouns);
            define_author_getter!(ctx, c&quot;bio&quot;, |author| author.bio);

            // `push(linkify_author(rust author))`
            duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
            duk_pull(ctx, -2);
            duk_call(ctx, 1);

            // `result = pop()`
            let result = CStr::from_ptr(duk_get_string(ctx, -1));
            duk_pop(ctx);
            result
        });

        duk_destroy_heap(ctx);
    }
}

#[cfg(feature = &quot;bench&quot;)]
#[bench]
fn with_prototype(bencher: &amp;amp;mut test::bench::Bencher) {
    struct Author {
        name: String,
        link_url: String,
        avatar_url: String,
        email_address: String,
        phone_number: String,
        pronouns: String,
        bio: String,
    }

    let author = Author {
        name: &quot;shuppy&quot;.to_owned(),
        link_url: &quot;https://shuppy.org&quot;.to_owned(),
        avatar_url: &quot;https://shuppy.org/avatar.png&quot;.to_owned(),
        email_address: &quot;test@shuppy.org&quot;.to_owned(),
        phone_number: &quot;+61 491 570 006&quot;.to_owned(),
        pronouns: &quot;she/her&quot;.to_owned(),
        bio: &quot;
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
            exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
            dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
            Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
            mollit anim id est laborum.
        &quot;.repeat(100),
    };

    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        // run the user’s custom code
        duk_eval_string(ctx, cr#&quot;
            function linkify_author(author) {
                return &quot;[&quot; + author.name + &quot;](&quot; + author.link_url + &quot;)&quot;;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        duk_eval_string(ctx, cr#&quot;
            function Author(inner) {
                this.inner = inner;
            }
        &quot;#.as_ptr());
        duk_pop(ctx);

        // `for (key in author) Object.defineProperty(Author.prototype, key, {get})`
        duk_eval_string(ctx, c&quot;Author.prototype&quot;.as_ptr());
        macro_rules! define_author_getter {
            ($ctx:ident, $key:literal, |author| author.$field:ident) =&amp;gt; {{
                duk_push_string($ctx, $key.as_ptr());
                duk_push_c_function($ctx, Some(get), 1);
                duk_def_prop($ctx, -3, DUK_DEFPROP_HAVE_GETTER);
                extern &quot;C&quot; fn get(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
                    unsafe {
                        duk_push_this(ctx);
                        duk_get_prop_string(ctx, -1, c&quot;inner&quot;.as_ptr());
                        let author = duk_get_pointer(ctx, -1) as *const Author;
                        duk_pop_2(ctx);
                        let result = &amp;amp;(*author).$field;
                        duk_push_lstring(ctx, result.as_ptr() as *const _, result.len());
                    }
                    1
                }
            }};
        }
        define_author_getter!(ctx, c&quot;name&quot;, |author| author.name);
        define_author_getter!(ctx, c&quot;link_url&quot;, |author| author.link_url);
        define_author_getter!(ctx, c&quot;avatar_url&quot;, |author| author.avatar_url);
        define_author_getter!(ctx, c&quot;email_address&quot;, |author| author.email_address);
        define_author_getter!(ctx, c&quot;bio&quot;, |author| author.bio);
        duk_pop(ctx);

        bencher.iter(|| {
            // `push(new Author(rust author))`
            duk_get_global_string(ctx, c&quot;Author&quot;.as_ptr());
            duk_push_pointer(ctx, &amp;amp;author as *const _ /* 🗞️ bad */ as *mut _);
            duk_new(ctx, 1);

            // `push(linkify_author(rust author))`
            duk_get_global_string(ctx, c&quot;linkify_author&quot;.as_ptr());
            duk_pull(ctx, -2);
            duk_call(ctx, 1);

            // `result = pop()`
            let result = CStr::from_ptr(duk_get_string(ctx, -1));
            duk_pop(ctx);
            result
        });

        duk_destroy_heap(ctx);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rust-lang&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000399.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000399.html"/>
<published>2026-07-07T16:26:32.950Z</published>
<title>embedding a javascript runtime in rust, part 1: duktape bindings</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="rust-lang" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000399.md&quot; id=&quot;thread-10000399.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;say you write a program in rust, with all of the benefits that entails (performance, type safety, etc), but you want to make parts of it programmable by the user without recompiling. you want to embed some kind of interpreted language, like lua or javascript, but surely that would be a pain? it’s not as bad as you might think!&lt;/p&gt;
&lt;h2&gt;which language? which runtime?&lt;/h2&gt;
&lt;p&gt;lua is famously embeddable, but i honestly don’t enjoy writing lua. that might be because i haven’t done it much. javascript is also embeddable, and after twenty years of writing javascript, i find it… tolerable. so let’s go with javascript.&lt;/p&gt;
&lt;p&gt;there are many, many javascript runtimes out there. &lt;a href=&quot;https://v8.dev&quot; rel=&quot;noopener noreferrer&quot;&gt;V8&lt;/a&gt; and &lt;a href=&quot;https://spidermonkey.dev&quot; rel=&quot;noopener noreferrer&quot;&gt;spidermonkey&lt;/a&gt; are cutting-edge options, very fast, but also very complex.&lt;/p&gt;
&lt;p&gt;spidermonkey is what firefox and &lt;a href=&quot;https://servo.org&quot; rel=&quot;noopener noreferrer&quot;&gt;servo&lt;/a&gt; use, and it even has &lt;a href=&quot;https://github.com/servo/mozjs&quot; rel=&quot;noopener noreferrer&quot;&gt;rust bindings&lt;/a&gt;, so you might think this is what i would pick. after all, servo is my job, and i’ve used &lt;a href=&quot;https://crates.io/crates/html5ever&quot; rel=&quot;noopener noreferrer&quot;&gt;html5ever&lt;/a&gt; and &lt;a href=&quot;https://crates.io/crates/cssparser&quot; rel=&quot;noopener noreferrer&quot;&gt;cssparser&lt;/a&gt; in hobby projects, and they’re great!&lt;/p&gt;
&lt;p&gt;but i’ve never embedded an interpreter before, and the five months i had working on &lt;a href=&quot;https://github.com/servo/servo/issues/36027&quot; rel=&quot;noopener noreferrer&quot;&gt;something adjacent&lt;/a&gt; to spidermonkey in servo tells me that a newbie like me should cut my teeth on something simpler.&lt;/p&gt;
&lt;h2&gt;enter: duktape&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;((o)&lt;/code&gt; &lt;a href=&quot;https://duktape.org&quot; rel=&quot;noopener noreferrer&quot;&gt;duktape&lt;/a&gt; is a javascript runtime that’s about as easy to embed as it gets: one &lt;code&gt;.c&lt;/code&gt; file, one &lt;code&gt;.h&lt;/code&gt; file, and a &lt;a href=&quot;https://duktape.org/guide&quot; rel=&quot;noopener noreferrer&quot;&gt;straightforward&lt;/a&gt; &lt;a href=&quot;https://duktape.org/api&quot; rel=&quot;noopener noreferrer&quot;&gt;C API&lt;/a&gt;. but there are some big drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;it only really supports ES5. this is (or was) a common target for compilers like babel, but if you’re writing it by hand, that means &lt;a href=&quot;https://compat-table.github.io/compat-table/es5/&quot; rel=&quot;noopener noreferrer&quot;&gt;you can use things&lt;/a&gt; like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind&quot; rel=&quot;noopener noreferrer&quot;&gt;bind() on Function&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map&quot; rel=&quot;noopener noreferrer&quot;&gt;map() on Array&lt;/a&gt;, but &lt;a href=&quot;https://compat-table.github.io/compat-table/es6/&quot; rel=&quot;noopener noreferrer&quot;&gt;not things&lt;/a&gt; like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;const&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions&quot; rel=&quot;noopener noreferrer&quot;&gt;arrow functions&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it has no JIT, so it’s much, &lt;a href=&quot;https://zoo.js.org&quot; rel=&quot;noopener noreferrer&quot;&gt;much slower&lt;/a&gt; than V8 and spidermonkey.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;xkcd &lt;a href=&quot;https://xkcd.com/927/&quot; rel=&quot;noopener noreferrer&quot;&gt;standards&lt;/a&gt; but it’s crates.io&lt;/h3&gt;
&lt;p&gt;i &lt;a href=&quot;https://crates.io/search?q=duktape&quot; rel=&quot;noopener noreferrer&quot;&gt;searched for “duktape”&lt;/a&gt; on &lt;a href=&quot;https://crates.io&quot; rel=&quot;noopener noreferrer&quot;&gt;crates.io&lt;/a&gt; and found 25 packages, most of which appeared to be unmaintained or immature. one even admits:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Aren&#x27;t there about a dozen of these crates?&lt;/strong&gt;&lt;br&gt;
Yes. They all seem to address slightly different needs so I&#x27;ve added to the pile. Sorry.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;so we’re shit outta luck right? not necessarily! we can roll our own duktape bindings without an unreasonable amount of effort. let’s do that here, in this post!&lt;/p&gt;
&lt;p&gt;the code below is compatible with &lt;a href=&quot;https://duktape.org/duktape-2.7.0.tar.xz&quot; rel=&quot;noopener noreferrer&quot;&gt;duktape 2.7.0&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;1. low-level bindings&lt;/h2&gt;
&lt;p&gt;let’s create a rust program first, then grab a copy of duktape. while the duktape releases include the actual sources (&lt;em&gt;not&lt;/em&gt; all in a single &lt;code&gt;.c&lt;/code&gt; and &lt;code&gt;.h&lt;/code&gt;) and build scripts we can use to create a customised flavour of duktape, we can just use the default flavour in &lt;code&gt;src&lt;/code&gt; this time:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo new duktest
$ cd duktest
$ curl -O https://duktape.org/duktape-2.7.0.tar.xz
$ tar xf duktape-2.7.0.tar.xz
$ ls duktape-2.7.0/src
duk_config.h  duk_source_meta.json  duktape.c  duktape.h
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;we’re gonna use &lt;a href=&quot;https://crates.io/crates/bindgen&quot; rel=&quot;noopener noreferrer&quot;&gt;bindgen&lt;/a&gt; to generate most of our low-level bindings.&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;Cargo.toml&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;[build-dependencies]
bindgen = &quot;0.72.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;bindgen will use libclang to parse &lt;code&gt;duktape.h&lt;/code&gt;. with nix, that looks like this:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;shell.nix&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;with import &amp;lt;nixpkgs&amp;gt; {};
(mkShell.override { stdenv = clangStdenv; }) {
  name = &quot;duktest-shell&quot;;
  LIBCLANG_PATH = lib.makeLibraryPath [ llvmPackages.clang-unwrapped.lib ];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;the build script is almost entirely based on &lt;a href=&quot;https://rust-lang.github.io/rust-bindgen/non-system-libraries.html&quot; rel=&quot;noopener noreferrer&quot;&gt;this example&lt;/a&gt; in the &lt;a href=&quot;https://rust-lang.github.io/rust-bindgen/&quot; rel=&quot;noopener noreferrer&quot;&gt;bindgen docs&lt;/a&gt;, but one important thing is to tell bindgen to only generate bindings for &lt;code&gt;duktape.h&lt;/code&gt;, not a bunch of other standard library headers that will just cause us problems:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;build.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;// `error[E0428]: the name `FP_NAN` is defined multiple times`
// `error[E0428]: the name `FP_INFINITE` is defined multiple times`
// `error[E0428]: the name `FP_ZERO` is defined multiple times`
// `error[E0428]: the name `FP_SUBNORMAL` is defined multiple times`
// `error[E0428]: the name `FP_NORMAL` is defined multiple times`
// `warning: `extern` block uses type `u128`, which is not FFI-safe`
.allowlist_file(&quot;.*/duktape[.]h&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;full build.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;use std::env;
use std::path::PathBuf;

fn main() {
    let libdir_path = PathBuf::from(&quot;duktape-2.7.0/src&quot;)
        .canonicalize()
        .expect(&quot;cannot canonicalize path&quot;);

    let h_path = libdir_path.join(&quot;duktape.h&quot;);
    let c_path = libdir_path.join(&quot;duktape.c&quot;);
    let obj_path = libdir_path.join(&quot;duktape.o&quot;);
    let lib_path = libdir_path.join(&quot;libduktape.a&quot;);
    let (h_path, c_path, obj_path, lib_path) = (
        h_path.to_str().expect(&quot;unsupported path&quot;),
        c_path.to_str().expect(&quot;unsupported path&quot;),
        obj_path.to_str().expect(&quot;unsupported path&quot;),
        lib_path.to_str().expect(&quot;unsupported path&quot;),
    );

    // Tell cargo to look for shared libraries in the specified directory
    println!(&quot;cargo:rustc-link-search={}&quot;, libdir_path.to_str().unwrap());

    // Tell cargo to tell rustc to link our `duktape` library. Cargo will
    // automatically know it must look for a `libduktape.a` file.
    println!(&quot;cargo:rustc-link-lib=duktape&quot;);

    // Run `clang` to compile the `duktape.c` file into a `duktape.o` object file.
    if !std::process::Command::new(&quot;clang&quot;)
        .args([&quot;-c&quot;, &quot;-o&quot;, obj_path, c_path])
        .output()
        .expect(&quot;failed to spawn `clang`&quot;)
        .status
        .success()
    {
        panic!(&quot;failed to compile object file&quot;);
    }

    // Run `ar` to generate the `libhello.a` file from the `hello.o` file.
    if !std::process::Command::new(&quot;ar&quot;)
        .args([&quot;rcs&quot;, lib_path, obj_path])
        .output()
        .expect(&quot;failed to spawn `ar`&quot;)
        .status
        .success()
    {
        panic!(&quot;failed to emit library file&quot;);
    }

    let bindings = bindgen::Builder::default()
        // `error[E0428]: the name `FP_NAN` is defined multiple times`
        // `error[E0428]: the name `FP_INFINITE` is defined multiple times`
        // `error[E0428]: the name `FP_ZERO` is defined multiple times`
        // `error[E0428]: the name `FP_SUBNORMAL` is defined multiple times`
        // `error[E0428]: the name `FP_NORMAL` is defined multiple times`
        // `warning: `extern` block uses type `u128`, which is not FFI-safe`
        .allowlist_file(&quot;.*/duktape[.]h&quot;)
        .header(h_path)
        // Tell cargo to invalidate the built crate whenever any of the
        // included header files changed.
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect(&quot;failed to generate bindings&quot;);

    // Write the bindings to the $OUT_DIR/bindings.rs file.
    let out_path = PathBuf::from(env::var(&quot;OUT_DIR&quot;).unwrap());
    bindings
        .write_to_file(out_path.join(&quot;bindings.rs&quot;))
        .expect(&quot;failed to write bindings!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;now we pull in those bindings from &lt;code&gt;$OUT_DIR/bindings.rs&lt;/code&gt; into a module:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/sys.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;#![allow(unused)]
#![allow(nonstandard_style)]
include!(concat!(env!(&quot;OUT_DIR&quot;), &quot;/bindings.rs&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;mod sys;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;h2&gt;2. missing pieces&lt;/h2&gt;
&lt;p&gt;bindgen is great, but there are some things it just can’t do. for these things, we’ll have to write the bindings for by hand, translating from C to rust.&lt;/p&gt;
&lt;p&gt;a big one is &lt;strong&gt;function-like macros&lt;/strong&gt;. duktape uses a modest amount of these, thankfully not too many as of version 2.7.0, but they also &lt;a href=&quot;https://duktape.org/guide#duktape-api&quot; rel=&quot;noopener noreferrer&quot;&gt;reserve the right&lt;/a&gt; to change any API to a macro (or back to a real function) in any release.&lt;/p&gt;
&lt;p&gt;so far, i’ve had to translate &lt;code&gt;duk_eval&lt;/code&gt;, &lt;code&gt;duk_eval_noresult&lt;/code&gt;, &lt;code&gt;duk_peval&lt;/code&gt;, &lt;code&gt;duk_peval_noresult&lt;/code&gt;, &lt;code&gt;duk_eval_string&lt;/code&gt;, &lt;code&gt;duk_peval_string&lt;/code&gt;, &lt;code&gt;duk_eval_lstring&lt;/code&gt;, &lt;code&gt;duk_peval_lstring&lt;/code&gt;, and &lt;code&gt;duk_safe_to_string&lt;/code&gt;. here are a few of those:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;duktape-2.7.0/src/duktape.h&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;#define duk_eval_string(ctx,src)  \
    ((void) duk_eval_raw((ctx), (src), 0, 0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NOFILENAME))

#define duk_peval_lstring(ctx,buf,len)  \
    (duk_eval_raw((ctx), buf, len, 0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE | DUK_COMPILE_NOFILENAME))

#define duk_safe_to_string(ctx,idx) \
    duk_safe_to_lstring((ctx), (idx), NULL)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/sys.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;use std::{ffi::c_char, ptr::null_mut};

pub unsafe fn duk_eval_string(ctx: *mut duk_context, src: *const c_char) {
    unsafe {
        duk_eval_raw(
            ctx,
            src,
            0,
            0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_STRLEN | DUK_COMPILE_NOFILENAME,
        );
    }
}

pub unsafe fn duk_peval_lstring(ctx: *mut duk_context, buf: *const c_char, len: duk_size_t) -&amp;gt; duk_int_t {
    unsafe {
        duk_eval_raw(ctx, buf, len, 0 /*args*/ | DUK_COMPILE_EVAL | DUK_COMPILE_NOSOURCE | DUK_COMPILE_SAFE | DUK_COMPILE_NOFILENAME)
    }
}

pub unsafe fn duk_safe_to_string(ctx: *mut duk_context, idx: duk_idx_t) -&amp;gt; *const c_char {
    unsafe {
        duk_safe_to_lstring(ctx, idx, null_mut())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;p&gt;bindgen &lt;em&gt;does&lt;/em&gt; support simple preprocessor constants, but for reasons unclear to me, not all of them make it through. the only one i’ve run into so far is &lt;code&gt;DUK_VARARGS&lt;/code&gt;:&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;duktape-2.7.0/src/duktape.h&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;/* Indicates that a native function does not have a fixed number of args,
 * and the argument stack should not be capped/extended at all.
 */
#define DUK_VARARGS                       ((duk_int_t) (-1))
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/sys.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;pub const DUK_VARARGS: duk_int_t = -1;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;h2&gt;3. hello world&lt;/h2&gt;
&lt;p&gt;with this, we have enough to start running some javascript!&lt;/p&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;details open=&quot;&quot;&gt;
&lt;summary style=&quot;margin: -0.25em; padding: 0.25em; color: white; background: rgb(var(--color-cherry)); cursor: pointer; user-select: none;&quot;&gt;&lt;figcaption style=&quot;display: inline;&quot;&gt;src/main.rs&lt;/figcaption&gt;&lt;/summary&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;mod sys;

use std::{ffi::CStr, io::{Write, stdout}, ptr::null_mut};

use crate::sys::*;

fn main() {
    unsafe {
        let ctx = duk_create_heap(None, None, None, null_mut(), None);
        duk_push_c_function(ctx, Some(nop), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;nop&quot;.as_ptr());
        duk_push_c_function(ctx, Some(print), DUK_VARARGS);
        duk_put_global_string(ctx, c&quot;print&quot;.as_ptr());

        duk_eval_string(ctx, c&quot;nop()&quot;.as_ptr());
        duk_eval_string(ctx, cr#&quot;print(&quot;hello js world\n&quot;)&quot;#.as_ptr());

        duk_pop(ctx);
        duk_destroy_heap(ctx);

        println!(&quot;goodbye rust world&quot;);
    }
}

extern &quot;C&quot; fn nop(_: *mut duk_context) -&amp;gt; duk_ret_t {
    0
}

extern &quot;C&quot; fn print(ctx: *mut duk_context) -&amp;gt; duk_ret_t {
    unsafe {
        duk_push_string(ctx, c&quot; &quot;.as_ptr());
        duk_insert(ctx, 0);
        duk_join(ctx, duk_get_top(ctx) - 1);
        let text = duk_safe_to_string(ctx, -1);
        let text = CStr::from_ptr(text);
        let _ = stdout().write_all(text.to_bytes());
    }
    0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;&lt;/figure&gt;
&lt;figure style=&quot;margin: 1em 0; padding: 0.25em; border: thin solid rgb(var(--color-cherry));&quot;&gt;
&lt;div style=&quot;overflow: scroll;&quot;&gt;
&lt;pre&gt;&lt;code&gt;$ cargo run
hello js world
goodbye rust world
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/figure&gt;
&lt;h2&gt;stay tuned!&lt;/h2&gt;
&lt;p&gt;next time, we’ll look at building some abstractions over these bindings, plus some tools to help us manage the interpreter’s &lt;a href=&quot;https://duktape.org/guide#programming.4&quot; rel=&quot;noopener noreferrer&quot;&gt;value stack&lt;/a&gt; correctly.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rust-lang&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000251.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000251.html"/>
<published>2025-08-22T12:11:17.079Z</published>
<title>it’s only nix if it comes from the utrecht region of the netherlands. otherwise it’s just sparkling input hashing</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" /><category term="indieweb" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000251.md&quot; id=&quot;thread-10000251.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;how do we add caching to a static site generator? quoth &lt;a href=&quot;/posts/10000250.html&quot; rel=&quot;noopener noreferrer&quot;&gt;last episode&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;we need a way to know if a post has changed, or any of its attachments have changed, since it was last rendered or its metadata was last cached, and we also need to know what tag pages will need to be rerendered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;after a bunch of experimentation, i think &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Nix_(package_manager)&amp;amp;oldid=1289477412&quot; rel=&quot;noopener noreferrer&quot;&gt;nix&lt;/a&gt; is pretty much what we want, but nix doesn’t really work on windows, so it can’t be nix, so let’s build our own nix :3&lt;/p&gt;
&lt;h2&gt;disclaimer again&lt;/h2&gt;
&lt;p&gt;none of this has landed on &lt;a href=&quot;https://github.com/delan/autost/tree/main&quot; rel=&quot;noopener noreferrer&quot;&gt;the main branch&lt;/a&gt; yet, for good reason. it’s probably buggy as shit, so don’t run it on your real blog yet.&lt;/p&gt;
&lt;h2&gt;juicy numbers&lt;/h2&gt;
&lt;p&gt;in the &lt;a href=&quot;https://github.com/delan/autost/pull/54&quot; rel=&quot;noopener noreferrer&quot;&gt;autost#54&lt;/a&gt; patch as of &lt;a href=&quot;https://github.com/delan/autost/pull/54/changes/218670e77f08e86b3d62970773a5ed20cd2cc8f9&quot; rel=&quot;noopener noreferrer&quot;&gt;218670e77f08e&lt;/a&gt;:&lt;/p&gt;
&lt;h3&gt;this blog (4803 threads)&lt;/h3&gt;
&lt;p&gt;this blog is my cohost archive plus a couple hundred newer posts. that’s 4803 posts (9816 if you count posts referenced by replies), and ~4.7 GB of attachments.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;no cache&lt;/th&gt;
&lt;th&gt;cold cache&lt;/th&gt;
&lt;th&gt;warm cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rendering the site&lt;/td&gt;
&lt;td&gt;828 ms&lt;/td&gt;
&lt;td&gt;1731 ms&lt;/td&gt;
&lt;td&gt;385 ms (−53.5%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rendering after edit&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;438 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;querying a tag&lt;/td&gt;
&lt;td&gt;236 ms&lt;/td&gt;
&lt;td&gt;1045 ms&lt;/td&gt;
&lt;td&gt;90 ms (−61.8%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 0 0.5rem 1em&quot;&gt;&lt;small&gt;note: percentages are relative to “no cache”&lt;/small&gt;&lt;/div&gt;
&lt;details&gt;&lt;summary&gt;details: rendering the site&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ export RUST_LOG=autost=warn
$ time autost render --skip-attachments
9.36s user 1.30s system 1287% cpu 0.828 total
$ time autost render --skip-attachments --use-cache
10.26s user 3.97s system 821% cpu 1.731 total
$ time autost render --skip-attachments --use-cache
1.02s user 1.53s system 661% cpu 0.385 total
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;details: rendering again after editing tags in one of the posts&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ unset RUST_LOG
$ time autost render --skip-attachments --use-cache
INFO build{function=ReadFile id=b0143e6e233ed...}: autost::cache: building
INFO build{function=RenderMarkdown id=e0c542ce051e2...}: autost::cache: building
INFO build{function=FilteredPost id=5ad850559c540...}: autost::cache: building
INFO build{function=Thread id=799f2c033be6a...}: autost::cache: building
INFO build{function=RenderedThread id=844c6195470bd...}: autost::cache: building
INFO build{function=Thread id=9dda7da0c21ae...}: autost::cache: building
INFO build{function=RenderedThread id=eac9c7f17c6f8...}: autost::cache: building
INFO autost::cache: writing cache pack 5ad
INFO autost::cache: writing cache pack 9dd
INFO autost::cache: writing cache pack 799
INFO autost::cache: writing cache pack e0c
INFO autost::cache: writing cache pack eac
INFO autost::cache: writing cache pack 844
INFO autost::cache: writing cache pack b01
1.27s user 1.72s system 681% cpu 0.438 total
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;details: querying a tag&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ unset RUST_LOG
$ time autost cache test --list-threads-in-tag usb3sun --use-cache
37 threads in tag &quot;usb3sun&quot;:
- &quot;2023-01-06T18:08:35.092Z&quot;, &quot;posts/787278.html&quot;, &quot;SPARCstations have a unique serial-like interface &quot;
- &quot;2023-06-23T17:15:54.361Z&quot;, &quot;posts/1742287.html&quot;, &quot;usb3sun is an adapter that lets you connect usb ke&quot;
- &quot;2023-06-24T17:50:23.538Z&quot;, &quot;posts/1650431.html&quot;, &quot;SPARCstations have a unique serial-like interface &quot;
[...]
INFO autost::cache: writing cache pack 372
0.49s user 0.93s system 410% cpu 0.347 total
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ rm -R cache
$ export RUST_LOG=autost=warn
$ time autost cache test --list-threads-in-tag usb3sun
1.77s user 0.27s system 864% cpu 0.236 total
$ time autost cache test --list-threads-in-tag usb3sun --use-cache
2.39s user 2.77s system 493% cpu 1.045 total
$ time autost cache test --list-threads-in-tag usb3sun --use-cache
0.07s user 0.30s system 412% cpu 0.090 total
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;big archive (146752 threads)&lt;/h3&gt;
&lt;p&gt;ok but what if i merged the cohost archives of everyone i followed into one very big archive? that’s 146752 posts (309140 if you count posts referenced by replies), and ~64 GB of attachments.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;no cache&lt;/th&gt;
&lt;th&gt;cold cache&lt;/th&gt;
&lt;th&gt;warm cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rendering the site&lt;/td&gt;
&lt;td&gt;24.90 s&lt;/td&gt;
&lt;td&gt;35.75 s&lt;/td&gt;
&lt;td&gt;11.18 s (−55.1%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;querying a tag&lt;/td&gt;
&lt;td&gt;5.551 s&lt;/td&gt;
&lt;td&gt;11.67 s&lt;/td&gt;
&lt;td&gt;1.547 s (−72.1%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 0 0.5rem 1em&quot;&gt;&lt;small&gt;note: percentages are relative to “no cache”&lt;/small&gt;&lt;/div&gt;
&lt;details&gt;&lt;summary&gt;details: rendering the site&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ export RUST_LOG=autost=warn
$ time autost render --skip-attachments
258.96s user 49.31s system 1237% cpu 24.902 total
$ time autost render --skip-attachments --use-cache
309.85s user 72.00s system 1067% cpu 35.755 total
$ time autost render --skip-attachments --use-cache
33.57s user 44.87s system 701% cpu 11.188 total
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;details&gt;&lt;summary&gt;details: querying a tag&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ unset RUST_LOG
$ time autost cache test --list-threads-in-tag usb3sun
37 threads in tag &quot;usb3sun&quot;:
- &quot;2023-01-06T18:08:35.092Z&quot;, &quot;posts/787278.html&quot;, &quot;SPARCstations have a unique serial-like interface &quot;
- &quot;2023-06-23T17:15:54.361Z&quot;, &quot;posts/1742287.html&quot;, &quot;usb3sun is an adapter that lets you connect usb ke&quot;
- &quot;2023-06-24T17:50:23.538Z&quot;, &quot;posts/1650431.html&quot;, &quot;SPARCstations have a unique serial-like interface &quot;
[...]
2025-08-28T09:39:27.095295Z  INFO autost::cache: writing cache pack 857
14.77s user 18.37s system 381% cpu 8.692 total
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ rm -R cache
$ export RUST_LOG=autost=warn
$ time autost cache test --list-threads-in-tag usb3sun
34.12s user 5.86s system 720% cpu 5.551 total
$ time autost cache test --list-threads-in-tag usb3sun --use-cache
57.26s user 32.47s system 768% cpu 11.673 total
$ time autost cache test --list-threads-in-tag usb3sun --use-cache
1.47s user 5.72s system 464% cpu 1.547 total
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;p&gt;anyway! i repeat, how do we add caching to a static site generator?&lt;/p&gt;
&lt;h2&gt;bad answer: timestamps&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Make_(software)&amp;amp;oldid=1305073461&quot; rel=&quot;noopener noreferrer&quot;&gt;make&lt;/a&gt; is an incremental build system, where “incremental” means it only rebuilds things that have changed. it does this with file timestamps and a dependency graph: a file needs to be rebuilt if any of its dependencies are newer than it.&lt;/p&gt;
&lt;p&gt;this is inadequate, because you can easily change the timestamp without changing the contents or change the contents without changing the timestamp. and good luck if you want to simultaneously cache more than one version of a build product.&lt;/p&gt;
&lt;p&gt;this is also unnecessarily frugal, except for attachments, as we’ll see shortly.&lt;/p&gt;
&lt;h2&gt;better answer: hashes&lt;/h2&gt;
&lt;p&gt;nowadays reading files is pretty fast, as long as they’re stored on an ssd. let’s write a little microbenchmark to see just how fast it can be. all of these results were taken on my framework 13 with an AMD 7840U and a charger connected.&lt;/p&gt;
&lt;aside style=&quot;border-left:solid rgb(var(--color-cherry));padding-left:1em&quot;&gt;
&lt;p&gt;for the choice of hash, we will later need something that can be used as a unique key to store and retrieve them, so we at least want a cryptographic hash with good collision and second preimage resistance. newer general-purpose cryptographic hashes &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=SHA-3&amp;amp;oldid=1307436042#Comparison_of_SHA_functions&quot; rel=&quot;noopener noreferrer&quot;&gt;are often somewhat slower&lt;/a&gt; than MD5 and SHA-1, since we’re often willing to trade a bit of the perf gain of newer computer hardware to fulfill our greater expectations for their security, and in many cases you’re limited by i/o anyway.&lt;/p&gt;
&lt;p&gt;but even in the current generation, there are alternatives like &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=BLAKE_(hash_function)&amp;amp;oldid=1304665371#BLAKE3&quot; rel=&quot;noopener noreferrer&quot;&gt;blake3&lt;/a&gt; and &lt;a href=&quot;https://keccak.team/kangarootwelve.html&quot; rel=&quot;noopener noreferrer&quot;&gt;kangarootwelve&lt;/a&gt; that can run much, &lt;a href=&quot;https://bench.cr.yp.to/graph/amd64-hertz-hash.pdf&quot; rel=&quot;noopener noreferrer&quot;&gt;much faster&lt;/a&gt; by taking advantage of simd and multiprocessing. but blake3 is the fastest, and it was a SHA-3 finalist, so i think it’s good enough for autost.&lt;/p&gt;
&lt;/aside&gt;
&lt;details&gt;&lt;summary&gt;details: running the microbenchmark&lt;/summary&gt;
&lt;pre&gt;&lt;code&gt;$ autost cache benchmark &amp;lt;posts|posts-recursive|attachments&amp;gt; sum-paths-len &amp;lt;10..=100&amp;gt;
$ autost cache benchmark &amp;lt;posts|posts-recursive|attachments&amp;gt; sum-read-len &amp;lt;10..=100&amp;gt;
$ autost cache benchmark &amp;lt;posts|posts-recursive|attachments&amp;gt; blake3 &amp;lt;10..=100&amp;gt;
$ autost cache benchmark &amp;lt;posts|posts-recursive|attachments&amp;gt; blake3-mmap-rayon &amp;lt;10..=100&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;this blog (4803 threads)&lt;/h3&gt;
&lt;p&gt;it looks like reading and hashing all of the posts doesn’t even double the time taken to walk the tree, but doing all of the attachments would incur an over 30x time penalty.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;time to walk&lt;/th&gt;
&lt;th&gt;and read&lt;/th&gt;
&lt;th&gt;and blake3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4803 posts&lt;/td&gt;
&lt;td&gt;5.62 ms&lt;/td&gt;
&lt;td&gt;9.69 ms&lt;/td&gt;
&lt;td&gt;9.94 ms (1.76x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9816 posts (recursive)&lt;/td&gt;
&lt;td&gt;11.32 ms&lt;/td&gt;
&lt;td&gt;18.24 ms&lt;/td&gt;
&lt;td&gt;19.21 ms (1.69x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5520 attachments&lt;/td&gt;
&lt;td&gt;14.48 ms&lt;/td&gt;
&lt;td&gt;458.0 ms&lt;/td&gt;
&lt;td&gt; 508.9 ms (35.1x)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 0 0.5rem 1em&quot;&gt;&lt;small&gt;note: multipliers are relative to “time to walk”&lt;/small&gt;&lt;/div&gt;
&lt;h3&gt;big archive (146752 threads)&lt;/h3&gt;
&lt;p&gt;again it’s less than a 2x penalty if we read and hash the posts, but an over 30x penalty for attachments.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;time to walk&lt;/th&gt;
&lt;th&gt;and read&lt;/th&gt;
&lt;th&gt;and blake3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;146752 posts&lt;/td&gt;
&lt;td&gt;197.6 ms&lt;/td&gt;
&lt;td&gt;298.2 ms&lt;/td&gt;
&lt;td&gt;313.5 ms (1.58x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;309140 posts (recursive)&lt;/td&gt;
&lt;td&gt;388.9 ms&lt;/td&gt;
&lt;td&gt;612.6 ms&lt;/td&gt;
&lt;td&gt;630.3 ms (1.62x)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;126626 attachments&lt;/td&gt;
&lt;td&gt;326.4 ms&lt;/td&gt;
&lt;td&gt;9417 ms&lt;/td&gt;
&lt;td&gt;11027 ms (33.7x)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 0 0.5rem 1em&quot;&gt;&lt;small&gt;note: multipliers are relative to “time to walk”&lt;/small&gt;&lt;/div&gt;
&lt;p&gt;another way of looking at it is, if we’re gonna have to read a bunch of files, we might as well hash them so we can easily check if they’ve changed. again this doesn’t make sense for attachments, whose contents are never actually read by &lt;code&gt;autost render&lt;/code&gt;. i’m not sure i have a good solution for attachments yet.&lt;/p&gt;
&lt;h2&gt;best answer: nix?&lt;/h2&gt;
&lt;p&gt;rendering your site involves many steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;making a list of posts&lt;/li&gt;
&lt;li&gt;rendering the posts
&lt;ul&gt;
&lt;li&gt;reading the post sources&lt;/li&gt;
&lt;li&gt;for posts in markdown, rendering the markdown&lt;/li&gt;
&lt;li&gt;parsing the post html into a dom tree&lt;/li&gt;
&lt;li&gt;extracting metadata
&lt;ul&gt;
&lt;li&gt;and rendering the referenced posts (if any)&lt;/li&gt;
&lt;li&gt;and scanning for references to attachments&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;applying transformations (e.g. making all images lazy loaded)&lt;/li&gt;
&lt;li&gt;serialising the dom tree back to html&lt;/li&gt;
&lt;li&gt;sanitising the html to “safe” tags and attributes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;writing the output files
&lt;ul&gt;
&lt;li&gt;copying the static files (e.g. style.css, script.js)&lt;/li&gt;
&lt;li&gt;hard linking the referenced attachments&lt;/li&gt;
&lt;li&gt;writing a page for each thread, containing that thread&lt;/li&gt;
&lt;li&gt;writing a page for each tag, containing all of its threads&lt;/li&gt;
&lt;li&gt;writing an atom feed for each tag, containing all of its threads&lt;/li&gt;
&lt;li&gt;writing a page for each built-in collection (e.g. index.html)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;we don’t just want to cache the final build outputs (html pages and atom feeds), because the intermediate build steps are also useful for things like querying metadata. so how do we build a cache for all of the different kinds of build steps without descending into cache invalidation and ad-hoc serialisation hell?&lt;/p&gt;
&lt;p&gt;many of these steps are pretty much functions (in the mathematical sense) that take some input and transform it to some output. in fact, &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Church%E2%80%93Turing_thesis&amp;amp;oldid=1304952422&quot; rel=&quot;noopener noreferrer&quot;&gt;all of them are&lt;/a&gt;, although some of them are easier to describe that way than others.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Nix&amp;amp;oldid=1304058200&quot; rel=&quot;noopener noreferrer&quot;&gt;nix&lt;/a&gt; is a build system and package manager that uses this observation to build the &lt;a href=&quot;https://repology.org/repositories/graphs&quot; rel=&quot;noopener noreferrer&quot;&gt;largest ever single repository of software&lt;/a&gt; with efficient binary caching. how can we apply its ideas here?&lt;/p&gt;
&lt;p&gt;imagine the process of loading a thread, just enough to know its metadata like its tags and what attachments it references:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for the last post in the thread
&lt;ul&gt;
&lt;li&gt;read the post sources&lt;/li&gt;
&lt;li&gt;if the post is in markdown, render the markdown&lt;/li&gt;
&lt;li&gt;parse the html and extract metadata&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;repeat for each of the posts it references&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;now let’s say we described each of these steps as a function. note that for the caching to work correctly, it’s very important that aside from readFile(), all of the other functions are &lt;em&gt;pure&lt;/em&gt;. they can only use their input arguments, which is why makeThread() can’t take one post and load all of the other posts it references from disk.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readFile(&lt;em&gt;path&lt;/em&gt;, &lt;em&gt;hash&lt;/em&gt;) → &lt;em&gt;content&lt;/em&gt; reads &lt;em&gt;path&lt;/em&gt; with expected hash &lt;em&gt;hash&lt;/em&gt;, and returns the &lt;em&gt;content&lt;/em&gt; of that file with that hash (more on the &lt;em&gt;hash&lt;/em&gt; later)&lt;/li&gt;
&lt;li&gt;renderMarkdown(&lt;em&gt;markdown&lt;/em&gt;) → &lt;em&gt;html&lt;/em&gt; renders the given &lt;em&gt;markdown&lt;/em&gt;, and returns the rendered &lt;em&gt;html&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;loadPost(&lt;em&gt;html&lt;/em&gt;) → &lt;em&gt;post&lt;/em&gt; (&lt;em&gt;html&lt;/em&gt;, &lt;em&gt;metadata&lt;/em&gt;) parses the given &lt;em&gt;html&lt;/em&gt; into a dom tree, extracts the metadata, and returns a &lt;em&gt;post&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;makeThread(&lt;em&gt;posts&lt;/em&gt;…) → &lt;em&gt;thread&lt;/em&gt; (&lt;em&gt;posts&lt;/em&gt;…) takes the given &lt;em&gt;posts&lt;/em&gt;, combines them into a thread, and returns that &lt;em&gt;thread&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;each of those functions just Builds The Thing. to make them cacheable and allow us to run them with maximum parallelism, we want to be able to make a “build plan” that completely describes how to Build The Thing without doing all of the work of actually Building The Thing.&lt;/p&gt;
&lt;aside style=&quot;border-left:solid rgb(var(--color-cherry));padding-left:1em&quot;&gt;
&lt;p&gt;for more details about why separating build plans from builds is important to nix, see § 2.4 of dolstra (2006), &lt;a href=&quot;https://edolstra.github.io/pubs/phd-thesis.pdf&quot; rel=&quot;noopener noreferrer&quot;&gt;The Purely Functional Software Deployment Model&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;let’s describe the build planning as a second set of functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readFilePlan(&lt;em&gt;path&lt;/em&gt;) → “readFile(&lt;em&gt;path&lt;/em&gt;, &lt;em&gt;hash&lt;/em&gt;)”&lt;/li&gt;
&lt;li&gt;renderMarkdownPlan(&lt;em&gt;path&lt;/em&gt;) → “renderMarkdown(readFile(&lt;em&gt;path&lt;/em&gt;, &lt;em&gt;hash&lt;/em&gt;))”&lt;/li&gt;
&lt;li&gt;loadPostPlan(&lt;em&gt;path&lt;/em&gt;) →
&lt;ul&gt;
&lt;li&gt;| “loadPost(renderMarkdown(readFile(&lt;em&gt;path&lt;/em&gt;, &lt;em&gt;hash&lt;/em&gt;))” if markdown&lt;/li&gt;
&lt;li&gt;| “loadPost(readFile(&lt;em&gt;path&lt;/em&gt;, &lt;em&gt;hash&lt;/em&gt;))” if html&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;makeThreadPlan(&lt;em&gt;path&lt;/em&gt;) → “makeThread(loadPost(…), loadPost(…), …)”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;one problem we run into with makeThreadPlan() is that we need the metadata of one of the posts to know what other loadPost() calls to include in the result. this is unfortunate, because it means we can’t entirely avoid Building The Thing when doing build planning.&lt;/p&gt;
&lt;aside style=&quot;border-left:solid rgb(var(--color-cherry));padding-left:1em&quot;&gt;
&lt;p&gt;nix calls this &lt;a href=&quot;https://nix.dev/manual/nix/2.30/language/import-from-derivation&quot; rel=&quot;noopener noreferrer&quot;&gt;“import from derivation”&lt;/a&gt;, and using IFD slows everything down because now your build planning (“evaluation” or “instantiation”) is blocked by a nested build process (“realisation”).&lt;/p&gt;
&lt;/aside&gt;
&lt;h2&gt;now here’s the magic!&lt;/h2&gt;
&lt;p&gt;let’s say we’re building the thread for &lt;a href=&quot;/posts/10000216.html&quot; rel=&quot;noopener noreferrer&quot;&gt;10000216.md&lt;/a&gt;, which replies to &lt;a href=&quot;/posts/10000215.html&quot; rel=&quot;noopener noreferrer&quot;&gt;10000215.md&lt;/a&gt;. makeThreadPlan(10000216.md) returns the build plan below. it was relatively easy to compute, and it completely describes how to load the thread from the files that make up the thread.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“makeThread(loadPost(renderMarkdown(readFile(10000216.md, 44062ae08bb67…))), loadPost(renderMarkdown(readFile(10000215.md, 8adba2770e58c…))))”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;aside style=&quot;border-left:solid rgb(var(--color-cherry));padding-left:1em&quot;&gt;
&lt;p&gt;for simplicity, i did not mention some inputs that can affect the output of this build, like your autost.toml and which version of autost you’re using. those inputs need to be described in the build plan too, probably with readFile(), otherwise we will fail to invalidate cached builds when they change.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;remember the “(more on &lt;em&gt;hash&lt;/em&gt; later)” from earlier? if the build plan for a thread includes the &lt;em&gt;hash&lt;/em&gt; of each of its posts, then we can hash the build plan and get an ID that changes &lt;em&gt;if and only if&lt;/em&gt; any of the posts have changed.&lt;/p&gt;
&lt;p&gt;and with that kind of unique ID for each thing we build, caching is easy! since the contents of the build plan for a given ID can’t possibly change (by definition), and the contents of the output really shouldn’t change (if we wrote our build logic correctly), we never need to delete or update anything, &lt;a href=&quot;/posts/10000016.html&quot; rel=&quot;noopener noreferrer&quot;&gt;except maybe to free up disk space&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;to me, this is the essence of nix, and i had great fun capturing that essence in our funny little static site generator. if you wanna see how this ersatz nix works, check out &lt;a href=&quot;https://github.com/delan/autost/blob/218670e77f08e86b3d62970773a5ed20cd2cc8f9/src/cache.rs&quot; rel=&quot;noopener noreferrer&quot;&gt;src/cache.rs&lt;/a&gt; and &lt;a href=&quot;https://github.com/delan/autost/blob/218670e77f08e86b3d62970773a5ed20cd2cc8f9/src/cache/drv.rs&quot; rel=&quot;noopener noreferrer&quot;&gt;src/cache/drv.rs&lt;/a&gt; ^w^&lt;/p&gt;
&lt;p&gt;that’s the post. but if you’re hungry for even more detail, read on…&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;performant store&lt;/h2&gt;
&lt;p&gt;processing thousands or even hundreds of thousands of posts is hard work. some things that helped with cache storage bottlenecks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;staring at hundreds of &lt;strong&gt;&lt;a href=&quot;https://github.com/flamegraph-rs/flamegraph&quot; rel=&quot;noopener noreferrer&quot;&gt;flamegraphs&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;switching from sqlite to &lt;strong&gt;plain old files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;switching from json to &lt;strong&gt;&lt;a href=&quot;https://crates.io/crates/bincode&quot; rel=&quot;noopener noreferrer&quot;&gt;bincode&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;switching from &lt;a href=&quot;https://crates.io/crates/tokio&quot; rel=&quot;noopener noreferrer&quot;&gt;tokio&lt;/a&gt; async to &lt;strong&gt;&lt;a href=&quot;https://crates.io/crates/rayon&quot; rel=&quot;noopener noreferrer&quot;&gt;rayon&lt;/a&gt; sync&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;dedicated rayon &lt;strong&gt;thread pools for writing files&lt;/strong&gt; with 4x the threads, since they will spend most of their time in syscalls waiting for i/o&lt;/li&gt;
&lt;li&gt;explicitly creating a &lt;strong&gt;thread pool for normal work&lt;/strong&gt;, since rayon otherwise just uses the thread pool of the innermost fork-join scope&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caching build products in memory&lt;/strong&gt;, then writing that to disk
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;storing native rust types&lt;/strong&gt; in the memory cache, so we can take build product serialisation off the critical path&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;offloading writes to dedicated thread pools&lt;/strong&gt;, so we can take build product file writing off the critical path&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;moving from one file per build product, to &lt;strong&gt;4096 “packs”&lt;/strong&gt; of cache data
&lt;ul&gt;
&lt;li&gt;splitting the memory cache into 4096 “packs” as well&lt;/li&gt;
&lt;li&gt;splitting the dirty bit into 4096 dirty bits as well&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;reducing overheads of syncing memory caches with the disk store
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;switching from &lt;a href=&quot;https://crates.io/crates/dashmap&quot; rel=&quot;noopener noreferrer&quot;&gt;DashMap&lt;/a&gt; to HashMap&lt;/strong&gt;, because it’s cheaper when we have thousands of them, and still performs well enough when sharded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lazy deserialisation&lt;/strong&gt; of cache items, loading as &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; until needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;tag indexes&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;autost cache test --list-threads-in-tag&lt;/code&gt; builds a “tag index” to more efficiently look up posts having the given tag, but it was hard to do this in a way that wasn’t &lt;em&gt;slower&lt;/em&gt; than just computing the metadata from scratch. &lt;strong&gt;two things made it possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;the build plans themselves were expensive, because for each evaluation we had to deserialise a big tree of inputs from ThreadDrv to FilteredPostDrv to RenderMarkdownDrv and finally down to ReadFileDrv. plus load their outputs into the memory cache for no reason. we fixed this by &lt;strong&gt;hiding everything other than ReadFileDrv from the build plan&lt;/strong&gt;, inferring them within the TagIndexDrv builder. idk about this… it feels like cheating or subverting how nix is supposed to work?&lt;/p&gt;
&lt;p&gt;the obvious representation of the tag index is a HashMap or BTreeMap, but this has to be deserialised in full on first use without any parallelism, which is a waste unless our query involves the whole dataset. instead we can &lt;strong&gt;build a tiny sqlite database&lt;/strong&gt; and serialise it to &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt;, then serialise &lt;em&gt;that&lt;/em&gt; as the build output. since sqlite can obviously query a table without building a whole map in memory, this is much faster.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;indieweb&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000250.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000250.html"/>
<published>2025-08-16T03:44:10.012Z</published>
<title>static site generators can have little a database, as a treat</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" /><category term="indieweb" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000250.md&quot; id=&quot;thread-10000250.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;i’ve started working on &lt;a href=&quot;https://github.com/delan/autost&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; again, with the hope i can tackle some of the problems &lt;a href=&quot;/posts/10000165.html&quot; rel=&quot;noopener noreferrer&quot;&gt;i wrote about in april&lt;/a&gt;. so far, i’ve been cautiously experimenting with adding a database, which again, seems to be unavoidable if we want autost to be anything more than a &lt;a href=&quot;https://shuppy.org/posts/7848210.html&quot; rel=&quot;noopener noreferrer&quot;&gt;Cohost Archive You Can Keep Posting To&lt;/a&gt;. but this can solve other problems too!&lt;/p&gt;
&lt;h2&gt;disclaimer&lt;/h2&gt;
&lt;p&gt;none of this has landed on &lt;a href=&quot;https://github.com/delan/autost/tree/main&quot; rel=&quot;noopener noreferrer&quot;&gt;the main branch&lt;/a&gt; yet, for good reason. it’s probably buggy as shit, so don’t run it on your real blog yet.&lt;/p&gt;
&lt;h2&gt;post ids&lt;/h2&gt;
&lt;p&gt;to publish a new post, we need to choose a new post id, and &lt;a href=&quot;https://github.com/delan/autost/blob/1a98b9df3df26b7891df16fef932e5cc1cdc3465/src/command/server.rs#L109-L117&quot; rel=&quot;noopener noreferrer&quot;&gt;our current approach&lt;/a&gt; is pretty minimalist (derogatory):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;let &lt;code&gt;id&lt;/code&gt; = 10000000&lt;/li&gt;
&lt;li&gt;try to create &lt;code&gt;posts/{id}.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if we can’t because it already exists:
&lt;ol&gt;
&lt;li&gt;let &lt;code&gt;id&lt;/code&gt; = &lt;code&gt;id&lt;/code&gt; + 1&lt;/li&gt;
&lt;li&gt;go to step 2&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;this is obviously inefficient, because it gets slower and slower the more posts you’ve made, and it &lt;a href=&quot;https://github.com/delan/autost/issues/45&quot; rel=&quot;noopener noreferrer&quot;&gt;doesn’t even work correctly&lt;/a&gt; if your last post was a &lt;code&gt;.html&lt;/code&gt; post.&lt;/p&gt;
&lt;p&gt;we could cache the last post id in a text file, and that sounds easy at first, but storing data in text files gets complicated fast. now you have to worry about things like&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how to &lt;a href=&quot;https://crates.io/crates/atomic-write-file&quot; rel=&quot;noopener noreferrer&quot;&gt;avoid data loss&lt;/a&gt; if a power outage happens while writing&lt;/li&gt;
&lt;li&gt;how to &lt;a href=&quot;https://crates.io/crates/atomic-file&quot; rel=&quot;noopener noreferrer&quot;&gt;version and migrate the file format&lt;/a&gt; when you need to extend it&lt;/li&gt;
&lt;li&gt;how to make related changes to multiple files together or not at all&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and all of these are &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=ACID&amp;amp;oldid=1282075795&quot; rel=&quot;noopener noreferrer&quot;&gt;solved&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Schema_migration&amp;amp;oldid=1254337725&quot; rel=&quot;noopener noreferrer&quot;&gt;problems&lt;/a&gt; if we use an actual database. &lt;a href=&quot;https://github.com/delan/autost/pull/53/changes/9a92040f72cd4b969fca8466eba4e4e5f9981fa9&quot; rel=&quot;noopener noreferrer&quot;&gt;here’s a simple sqlite database&lt;/a&gt; that solves our post id problems:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE &quot;post&quot; (
    &quot;post_id&quot; INTEGER PRIMARY KEY AUTOINCREMENT  -- e.g. 10000250
    , &quot;path&quot; TEXT NOT NULL                       -- e.g. &#x27;10000250.md&#x27;
    , &quot;rendered_path&quot; TEXT NULL UNIQUE           -- e.g. &#x27;10000250.html&#x27;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;since the &lt;code&gt;&quot;rendered_path&quot;&lt;/code&gt; is &lt;code&gt;UNIQUE&lt;/code&gt;, we can’t accidentally create a &lt;code&gt;&quot;path&quot; = &#x27;10000250.md&#x27;&lt;/code&gt; and a &lt;code&gt;&quot;path&quot; = &#x27;10000250.html&#x27;&lt;/code&gt; that both have &lt;code&gt;&quot;rendered_path&quot; = &#x27;10000250.html&#x27;&lt;/code&gt;. and &lt;a href=&quot;https://github.com/delan/autost/pull/53/changes/1930b4d785817fca772b90622c7cad6b189f6c65&quot; rel=&quot;noopener noreferrer&quot;&gt;choosing the next post id&lt;/a&gt; so we can publish a new post is easy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BEGIN;
-- no &quot;post_id&quot;, so we generate it with AUTOINCREMENT.
-- empty &quot;path&quot;, because we don’t know it yet.
INSERT INTO &quot;post&quot; (&quot;path&quot;) VALUES (&#x27;&#x27;);
-- now we check the “last insert id”, which is done outside sql.
-- let’s say it’s 10000250.
UPDATE &quot;post&quot; SET &quot;path&quot; = &#x27;10000250.md&#x27; WHERE &quot;post_id&quot; = 10000250;
COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;check out &lt;a href=&quot;https://github.com/delan/autost/pull/53&quot; rel=&quot;noopener noreferrer&quot;&gt;autost#53&lt;/a&gt; for the complete patch.&lt;/p&gt;
&lt;h2&gt;caching&lt;/h2&gt;
&lt;p&gt;right now, answering questions like “what posts are tagged &lt;a href=&quot;/posts/tagged/birds.html&quot; rel=&quot;noopener noreferrer&quot;&gt;#birds&lt;/a&gt;?” takes about as long as rendering your whole site, because in both cases we have to read all of your posts, parse the html, and extract the metadata. and when you publish or edit a post, we have to render your whole site from scratch.&lt;/p&gt;
&lt;p&gt;this is generally how &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Static_site_generator&amp;amp;oldid=1294468788&quot; rel=&quot;noopener noreferrer&quot;&gt;static site generators&lt;/a&gt; work, and Computer Touchers like that because having your whole blog be a bunch of text files is (a) elegant and (b) works well with version control. but the average Computer Toucher also has one (1) blog post, titled “New Year, New Blog, or: How I Learned To Stop Worrying and Switch From Twelvety to Sext.js”, so we should take their opinions with a grain of salt.&lt;/p&gt;
&lt;p&gt;static site generators do have other benefits though. they’ve got good &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=Data_portability&amp;amp;oldid=1304372661&quot; rel=&quot;noopener noreferrer&quot;&gt;data portability&lt;/a&gt;, and static sites are easy to host on any web server, even web servers that are &lt;a href=&quot;https://en.wikipedia.org/w/index.php?title=GitHub&amp;amp;oldid=1305898104#GitHub_Pages&quot; rel=&quot;noopener noreferrer&quot;&gt;free because Big Coding is using them as a loss leader&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;so maybe we want something in between, where your post files are still the primary source of truth, but we cache the metadata that we extract from them in the database, or we cache the fact that the post has already been rendered.&lt;/p&gt;
&lt;p&gt;that will leave us with one of the &lt;a href=&quot;https://martinfowler.com/bliki/TwoHardThings.html&quot; rel=&quot;noopener noreferrer&quot;&gt;two hard problems in computer science&lt;/a&gt;: now we need a way to know if a post has changed, or any of its attachments have changed, since it was last rendered or its metadata was last cached, and we also need to know what tag pages will need to be rerendered. &lt;del&gt;hmm. is this just nix?&lt;/del&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;indieweb&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000165.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000165.html"/>
<published>2025-04-19T07:39:11.582Z</published>
<title>what is an interesting post? the greatest question in the history of autost, locked after 12239 commits of organic growth</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" /><category term="indieweb" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000165.md&quot; id=&quot;thread-10000165.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; was written in a month-long daze after cohost announced their shutdown. &lt;a href=&quot;https://shuppy.org/posts/7848210.html&quot; rel=&quot;noopener noreferrer&quot;&gt;it started out&lt;/a&gt; as a way for me to archive my own chosts on &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt;. since the plan all along was for me to continue chosting from there, &lt;a href=&quot;https://shuppy.org/posts/7878145.html&quot; rel=&quot;noopener noreferrer&quot;&gt;that came next&lt;/a&gt;. then it gained the ability to &lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.0.0&quot; rel=&quot;noopener noreferrer&quot;&gt;import posts from other blogs&lt;/a&gt;, &lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.1.0&quot; rel=&quot;noopener noreferrer&quot;&gt;archive the chosts of everyone you follow&lt;/a&gt;, and &lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.3.0&quot; rel=&quot;noopener noreferrer&quot;&gt;archive chosts liked by you and your friends&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;buried deep within the core of autost is some gnarly logic that decides which posts should be rendered, which posts should be unlisted or publicly visible (“interesting” posts), and which tags should have their own tag pages (“interesting” tags).&lt;/p&gt;
&lt;p&gt;the problems i wanted to solve initially were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i want to archive my own chosts and continue chosting on the same site&lt;/li&gt;
&lt;li&gt;i want to curate which of my archived chosts are publicly visible&lt;/li&gt;
&lt;li&gt;we need a way to store the posts that are being replied to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;but the way i solved them was a bunch of hardcoded rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;posts you make with autost are publicly visible
&lt;ul&gt;
&lt;li&gt;in practice, this means posts authored by your &lt;code&gt;[self_author] href&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;your own archived chosts in “interesting” tags are public too
&lt;ul&gt;
&lt;li&gt;in practice, this means posts authored by your &lt;code&gt;other_self_authors&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it’s “your own” if you authored the last post in the thread&lt;/li&gt;
&lt;li&gt;the “last post” does not include transparent shares&lt;/li&gt;
&lt;li&gt;“interesting” tags are those linked in the top nav section&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;specific archived chosts can be made public or unlisted&lt;/li&gt;
&lt;li&gt;posts in subdirectories are ignored except for rendering replies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;there are so many problems with this model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you can’t put any posts in subdirectories&lt;/li&gt;
&lt;li&gt;you can’t opt out of the rules for archived chosts. &lt;code&gt;autost cohost-archive&lt;/code&gt; hacks around this by setting the &lt;code&gt;[self_author] href&lt;/code&gt; of each archive to a cohost url&lt;/li&gt;
&lt;li&gt;you can’t organise posts in any way other than tags. &lt;code&gt;autost cohost-archive&lt;/code&gt; hacks around this by making a separate site for each project you follow&lt;/li&gt;
&lt;li&gt;there’s no clear way to know why a post exists, or what you want to do with it, other than this big pile of emergent behaviour&lt;/li&gt;
&lt;li&gt;since every site has to abide by the same rules, any changes to the rules could break existing sites or change how they behave&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;we need to move away from these hardcoded rules. i think it’s time to sit down and actually define what we want autost to be capable of, and design a data model around that. this will require changes to existing sites, but we can build automated migrations or migration tools to upgrade those sites.&lt;/p&gt;
&lt;p&gt;when you render your site, we should ask questions like “do we render this post?”, “is this post publicly visible?”, and “which tags have tag pages?”, but let whoever created the post (&lt;code&gt;autost server&lt;/code&gt;, &lt;code&gt;autost cohost2autost&lt;/code&gt;, &lt;code&gt;autost import&lt;/code&gt;, or you) decide the answers.&lt;/p&gt;
&lt;p&gt;we’ll probably want to store those answers in a database. it’s all files right now, which is like a database, except harder to extend and harder to safely mix human changes and machine changes. having a database may make it easier to extend autost to do other things with archived chosts, like organise the chosts of everyone you follow into a single browsable archive, or generate a page of your liked chosts.&lt;/p&gt;
&lt;p&gt;we may even want to move posts that only exist for rendering replies out of the &lt;code&gt;posts&lt;/code&gt; directory and into a separate store, like the attachment store. this will require changes to the &lt;code&gt;&amp;lt;link rel=references&amp;gt;&lt;/code&gt; in existing posts though, so it would need to be done with extra care.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;indieweb&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000137.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000137.html"/>
<published>2025-03-02T13:00:50.213Z</published>
<title>autost 1.4.0: akkoma importer, lazy loading, and a windows fix</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000137.md&quot; id=&quot;thread-10000137.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;





&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.4.0&quot; rel=&quot;noopener noreferrer&quot;&gt;this update&lt;/a&gt; (&lt;a href=&quot;https://github.com/delan/autost/blob/1.4.0/CHANGELOG.md#140-2025-03-02&quot; rel=&quot;noopener noreferrer&quot;&gt;full changelog&lt;/a&gt;) includes several minor improvements that landed after the big cohost shutdown scramble (rip eggbug). most notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;all &amp;lt;img&amp;gt; elements&lt;/strong&gt; are now &lt;strong&gt;lazy loaded&lt;/strong&gt;, massively reducing the size of large threads pages&lt;/li&gt;
&lt;li&gt;you can now &lt;strong&gt;reblog posts from akkoma instances&lt;/strong&gt;, including those on &lt;a href=&quot;https://websiteleague.org&quot; rel=&quot;noopener noreferrer&quot;&gt;the website league&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;display: flex; margin: 0 2em;&quot;&gt;
&lt;a href=&quot;/posts/attachments/b3b7d562-cb43-4a03-9052-4d2d1ad035c2/bell_miner.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img alt=&quot;screenshot of a post reblogged from the website league&quot; src=&quot;/posts/attachments/b3b7d562-cb43-4a03-9052-4d2d1ad035c2/bell_miner.png&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autost server&lt;/code&gt; now lets you &lt;strong&gt;click [+] on any tag to post in it&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autost server&lt;/code&gt; is &lt;strong&gt;no longer broken on windows&lt;/strong&gt; (&lt;a href=&quot;https://github.com/ar1a&quot; rel=&quot;noopener noreferrer&quot;&gt;@ar1a&lt;/a&gt;, &lt;a href=&quot;https://github.com/delan/autost/pull/39&quot; rel=&quot;noopener noreferrer&quot;&gt;#39&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;display: flex; margin: 0 2em;&quot;&gt;
&lt;a href=&quot;/posts/attachments/1658a452-5186-4ccc-94ad-7bf24b2cb274/plus_buttons.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img alt=&quot;screenshot of the nav on shuppy.org under `autost server`, with a new orange plus button next to each of the tags&quot; src=&quot;/posts/attachments/1658a452-5186-4ccc-94ad-7bf24b2cb274/plus_buttons.png&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;so… real talk. a handful of you actually use (or are considering using) autost for more than just archiving your chosts, and i think that’s wonderful. but maybe at this point you’re wondering if the project’s days are numbered.&lt;/p&gt;
&lt;p&gt;development has been pretty slow the past couple of months, because i fell in love with &lt;a href=&quot;https://srxl.me&quot; rel=&quot;noopener noreferrer&quot;&gt;a fox&lt;/a&gt;, and i needed a break after &lt;a href=&quot;https://github.com/delan/autost/compare/55751790d6881977c7e9657d176d555b8c54359c...a97d82d6a07a077210c2b889106ab711ed555754&quot; rel=&quot;noopener noreferrer&quot;&gt;the last crunch&lt;/a&gt;. &lt;strong&gt;but i don’t consider autost finished&lt;/strong&gt;, and i intend to keep working on it, just at a healthier pace.&lt;/p&gt;
&lt;p&gt;higher priorities include being able to &lt;strong&gt;upload images from the post composer&lt;/strong&gt; and &lt;strong&gt;compose posts from another device&lt;/strong&gt;. but longer term, i wonder if there’s a niche for autost to be an easier kind of blog engine.&lt;/p&gt;
&lt;p&gt;most blogging solutions are either saas, complex self-hosted (wordpress), or require you to be a computer-toucher and have frontend knowledge (static site generators). what if you could just run an app on your computer and get an editor for your blog? what if you could compose posts, follow and reblog your friends, and deploy your blog to neocities or some other host, all without touching the command line?&lt;/p&gt;
&lt;p&gt;if that interests you, let me know at &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt; or &lt;a href=&quot;https://posting.isincredibly.gay/shuppy&quot; rel=&quot;noopener noreferrer&quot;&gt;on the league&lt;/a&gt; :)&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000136.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000136.html"/>
<published>2025-03-02T12:36:03.196Z</published>
<title>polycule-driven development</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="polyamory" /><category term="webdev" /><category term="github" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000136.md&quot; id=&quot;thread-10000136.md&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/2728949-polycule-driven-deve&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2023-09-04T16:19:21.424Z&quot;&gt;2023-09-04T16:19:21.424Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        polycule-driven development
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;











&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/1209cd67-3693-4ee8-bede-6a4bd3ce2716/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/1209cd67-3693-4ee8-bede-6a4bd3ce2716&quot; src=&quot;/posts/attachments/thumbs/1209cd67-3693-4ee8-bede-6a4bd3ce2716/image.png&quot; alt=&quot;screenshot of github issue for delan/charming, reported by ar1a in 2019, fixed by the6p4c in 2023&quot; width=&quot;480&quot; height=&quot;660&quot;&gt;&lt;/a&gt;

&lt;p&gt;(&lt;a href=&quot;https://cohost.org/ariashark&quot; rel=&quot;noopener noreferrer&quot;&gt;@ariashark&lt;/a&gt; + &lt;a href=&quot;https://cohost.org/bark&quot; rel=&quot;noopener noreferrer&quot;&gt;@bark&lt;/a&gt;)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;polyamory&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;webdev&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;charming&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;









&lt;p&gt;&lt;a href=&quot;/posts/attachments/ef4fe168-738b-46f2-9e11-3208ddff48d7/pdd.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;/posts/attachments/ef4fe168-738b-46f2-9e11-3208ddff48d7/pdd.png&quot; alt=&quot;Contributors (5): @delan, @Sorixelle, @the6p4c, @ar1a, @echowritescode&quot; loading=&quot;lazy&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;polyamory&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;webdev&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000078.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000078.html"/>
<published>2024-12-29T08:32:56.792Z</published>
<title>archive your chosts and the chosts you care about</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="Cohost Shutdown" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000078.md&quot; id=&quot;thread-10000078.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;only a couple days left until cohost goes down! &lt;strong&gt;why not archive the chosts you care about, so you can look back on them without relying on wayback machine?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cohost.org/blep/post/7639936-cohost-dl&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-dl&lt;/a&gt; will do your chosts plus &lt;em&gt;your liked chosts&lt;/em&gt;, while &lt;a href=&quot;https://github.com/delan/autost&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; will do both of those plus &lt;em&gt;chosts by people you follow&lt;/em&gt;! here’s how you can use autost for that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost/releases&quot; rel=&quot;noopener noreferrer&quot;&gt;install autost&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;log in to cohost, press F12, then grab your “connect.sid” cookie (see screenshot or consult someone with stickers on their laptop)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if you own more than one page, switch to the one you want to archive&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost/blob/f4d4274eeb587517ffb5bb2deb63454963a25c88/README.md#how-to-quickly-archive-chosts-by-you-and-everyone-you-follow&quot; rel=&quot;noopener noreferrer&quot;&gt;get archiving!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;repeat from step 3 until you’ve archived all of your pages&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;please email me on &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt; if you have any questions! good luck ^w^&lt;/p&gt;
&lt;div style=&quot;display: flex;&quot;&gt;&lt;div&gt;
&lt;img src=&quot;/posts/attachments/667c1dcf-56d3-4671-860c-61d6658bf36a/a10bf5ab5f98ec7e75a86d0f1a6c336213c5f0e7fb1e79a09798b99592f95515.png&quot; loading=&quot;lazy&quot;&gt;
&lt;/div&gt;&lt;div&gt;
&lt;img src=&quot;/posts/attachments/46970b2f-f79c-4d13-a5ae-a6af3e2a7d6f/3dd4b494860454da43ac1b8552918b763942768ed7f4d73676f1be84c47b6f00.png&quot; loading=&quot;lazy&quot;&gt;
&lt;img src=&quot;/posts/attachments/18807085-681f-416c-95e2-e3ba0e670a14/447ce803fa42200542df5d8a81d49c0b24de62ae448bfeb0c669af1b4d86e935.png&quot; alt=&quot;firefox devtools: Storage tab &gt; Cookies &gt; cohost.org &gt; connect.sid &gt; copy the value. chrome devtools: Application tab &gt; Cookies &gt; cohost.org &gt; connect.sid &gt; copy the value. don’t have safari sorry&quot; loading=&quot;lazy&quot;&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000004.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000004.html"/>
<published>2024-10-11T11:50:24.625Z</published>
<title>autost 1.0, now rebloggable with h-entry</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000004.md&quot; id=&quot;thread-10000004.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;





&lt;div style=&quot;text-align: center;&quot;&gt;&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.0.0&quot; rel=&quot;noopener noreferrer&quot;&gt;download&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;[&lt;a href=&quot;https://github.com/delan/autost/blob/1.0.0/README.md&quot; rel=&quot;noopener noreferrer&quot;&gt;readme&lt;/a&gt;] [&lt;a href=&quot;https://github.com/delan/autost/releases/tag/1.0.0&quot; rel=&quot;noopener noreferrer&quot;&gt;changes in 1.0.0&lt;/a&gt;]&lt;/div&gt;
&lt;p&gt;autost is a &lt;a href=&quot;https://cohost.org/delan/post/7848210-autost-a-cohost-com&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-compatible archive and blog engine&lt;/a&gt;, which i use right here on &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt;. for another autost blog, check out &lt;a href=&quot;https://lottemakesstuff.pink/&quot; rel=&quot;noopener noreferrer&quot;&gt;LotteMakesStuff dot PINK&lt;/a&gt; :D&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;your posts are now rebloggable&lt;/strong&gt;, thanks to the microformats2 &lt;a href=&quot;https://microformats.org/wiki/h-entry&quot; rel=&quot;noopener noreferrer&quot;&gt;h-entry&lt;/a&gt; format! for the most consistent experience, be sure to set your &lt;code&gt;[self_author] display_handle&lt;/code&gt; setting to a domain name.&lt;/p&gt;
&lt;p&gt;you can now &lt;strong&gt;reblog posts&lt;/strong&gt; with &lt;code&gt;autost import&lt;/code&gt;. for example, to reblog &lt;a href=&quot;https://nex-3.com&quot; rel=&quot;noopener noreferrer&quot;&gt;Natalie&lt;/a&gt;’s post &lt;a href=&quot;https://nex-3.com/blog/reblogging-posts-with-h-entry/&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;em&gt;Reblogging posts with h-entry&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ autost import https://nex-3.com/blog/reblogging-posts-with-h-entry/
2024-10-03T15:30:33.221087Z  INFO run_migrations: autost::migrations: hard linking attachments out of site/attachments
2024-10-03T15:30:33.411823Z  INFO autost::command::import: writing RelativePath { inner: &quot;posts/imported/1.html&quot;, kind: Other }
2024-10-03T15:30:33.422458Z  INFO autost::command::import: click here to reply: http://[::1]:8420/posts/compose?reply_to=imported/1.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;you can now &lt;strong&gt;create attachments from local files&lt;/strong&gt; with &lt;code&gt;autost attach&lt;/code&gt;. for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ autost attach ~/Downloads/atom{1,2}.png
2024-10-11T11:47:39.031943Z  INFO run_migrations: autost::migrations: hard linking attachments out of site/attachments
2024-10-11T11:47:39.137328Z  INFO autost::command::attach: created attachment: &amp;lt;attachments/d9c42006-07ca-4027-8abb-dfa670a7e637/atom1.png&amp;gt;
2024-10-11T11:47:39.137477Z  INFO autost::command::attach: created attachment: &amp;lt;attachments/887df41f-a1ce-4e66-8a9f-09ef1f6fedca/atom2.png&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;warning:&lt;/strong&gt; &lt;code&gt;autost attach&lt;/code&gt; does not strip any exif data yet, so be careful not to leak your gps location in any photos you upload! use &lt;code&gt;exiftool&lt;/code&gt; or a photo editor to check.&lt;/p&gt;
&lt;p&gt;the atom output is also way better now! most importantly, threads with multiple posts are no longer an unreadable mess:&lt;/p&gt;
&lt;div style=&quot;display: flex; flex-flow: row nowrap; align-items: center;&quot;&gt;
&lt;figure style=&quot;margin: 0; flex: 1 1 0; display: flex; flex-flow: column nowrap;&quot;&gt;&lt;img alt=&quot;before: an unreadable mess&quot; src=&quot;/posts/attachments/d9c42006-07ca-4027-8abb-dfa670a7e637/atom1.png&quot; style=&quot;margin: 0; width: 100%;&quot; loading=&quot;lazy&quot;&gt;&lt;figcaption style=&quot;text-align: center;&quot;&gt;before&lt;/figcaption&gt;&lt;/figure&gt;
&lt;span style=&quot;font-size: 2em;&quot;&gt;→&lt;/span&gt;
&lt;figure style=&quot;margin: 0; flex: 1 1 0; display: flex; flex-flow: column nowrap;&quot;&gt;&lt;img alt=&quot;after: clearly delineating where each post starts and ends&quot; src=&quot;/posts/attachments/887df41f-a1ce-4e66-8a9f-09ef1f6fedca/atom2.png&quot; style=&quot;margin: 0; width: 100%;&quot; loading=&quot;lazy&quot;&gt;&lt;figcaption style=&quot;text-align: center;&quot;&gt;after&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;note that your subscribers will need to convince their clients to redownload your posts, or unsubscribe and resubscribe, to see these effects on existing posts.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://shuppy.org/posts/tagged/autost.html&quot; rel=&quot;noopener noreferrer&quot;&gt;subscribe here for updates&lt;/a&gt;, or email me on &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;/a&gt;&lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt; if you have any questions. enjoy :3&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000001.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000001.html"/>
<published>2024-10-03T15:31:32.511Z</published>
<title>untitled post by shuppy.org</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000001.md&quot; id=&quot;thread-10000001.md&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://nex-3.com/&quot;&gt;Natalie&lt;/a&gt; (&lt;a href=&quot;https://nex-3.com/&quot; class=&quot;handle&quot;&gt;nex-3.com&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://nex-3.com/blog/reblogging-posts-with-h-entry/&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-27T07:38:00Z&quot;&gt;2024-09-27T07:38:00Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        Reblogging posts with h-entry
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










          &lt;blockquote style=&quot;
              padding: 0.75rem;
              margin: 1rem 0.2rem 1.15rem;
              border-radius: 0.5rem;
              box-shadow:
                0px 4px 5px rgba(0, 0, 0, 0.14),
                0px 1px 10px rgba(0, 0, 0, 0.12),
                0px 2px 4px rgba(0, 0, 0, 0.2);
            &quot;&gt;
            &lt;p&gt;
              &lt;strong&gt;&lt;data&gt;&lt;/data&gt;&lt;a href=&quot;https://nex-3.com/&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;span&gt;Natalie&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;
              wrote:
            &lt;/p&gt;
            &lt;data&gt;&lt;/data&gt;
            &lt;div&gt;
              &lt;p&gt;
                Once I add the ability to embed arbitrary blog posts from other
                blogs on here it&#x27;s over. I&#x27;m gonna be reblogging like a wild
                animal. Y&#x27;all are gonna have your eyes blown clean outta your
                heads.
              &lt;/p&gt;
            &lt;/div&gt;
          &lt;/blockquote&gt;

          &lt;p&gt;
            Thrilled to announce that I now have this up and running, at least
            in its most basic aspect. The embed above is automatically generated
            &lt;em&gt;and&lt;/em&gt; pulled down directly from the source post. Nothing in
            this is specific to my blog; I can also do it with someone else&#x27;s.
            By way of example, please enjoy this post from my beautiful wife:
          &lt;/p&gt;
          &lt;blockquote style=&quot;
              padding: 0.75rem;
              margin: 1rem 0.2rem 1.15rem;
              border-radius: 0.5rem;
              box-shadow:
                0px 4px 5px rgba(0, 0, 0, 0.14),
                0px 1px 10px rgba(0, 0, 0, 0.12),
                0px 2px 4px rgba(0, 0, 0, 0.2);
            &quot;&gt;
            &lt;p&gt;
              &lt;strong&gt;&lt;data&gt;&lt;/data&gt;&lt;a href=&quot;https://seaslug.garden/&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;span&gt;Liz&lt;/span&gt;&lt;/a&gt;&lt;/strong&gt;
              wrote:
            &lt;/p&gt;
            &lt;data&gt;&lt;/data&gt;
            &lt;div&gt;
              &lt;p&gt;
                there is a very specific feeling of relief upon realizing I
                don’t need to hurry to finish a library book before it’s due,
                because I &lt;em&gt;definitely&lt;/em&gt; will want to buy a copy for future
                reference and cross-checking.
              &lt;/p&gt;
              &lt;p&gt;
                (the book in question is
                &lt;em&gt;&lt;a href=&quot;https://press.uchicago.edu/ucp/books/book/chicago/G/bo37630225.html&quot; rel=&quot;noopener noreferrer&quot;&gt;Gossip Men: J. Edgar Hoover, Joe McCarthy, Roy Cohn, and
                    the Politics of Insinuation&lt;/a&gt;&lt;/em&gt;
                by Christopher M. Elias)
              &lt;/p&gt;
              &lt;p&gt;&lt;/p&gt;
            &lt;/div&gt;
          &lt;/blockquote&gt;

          &lt;h2&gt;Injecting embeds&lt;/h2&gt;
          &lt;p&gt;Here&#x27;s what the embed looks like in my blog source right now:&lt;/p&gt;
          &lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{%&lt;/span&gt; genericPost &lt;span&gt;&quot;https://nex-3.com/blog/once-i-add-the/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    time&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;2024-09-20T07:06:00Z&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    tags&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;#meta&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    author&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Natalie&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    authorUrl&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    authorAvatar&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;/assets/avatar.webp&quot;&lt;/span&gt; &lt;span&gt;%}&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    Once I add the ability to embed arbitrary blog posts from other blogs on
    here it&#x27;s over. I&#x27;m gonna be reblogging like a wild animal. Y&#x27;all are gonna
    have your eyes blown clean outta your heads.
  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{%&lt;/span&gt; endgenericPost &lt;span&gt;%}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
          &lt;p&gt;
            I have a template for the embed, some CSS to style it, and a little
            custom Liquid tag to bring it all together. But the real magic is in
            how I generate the &lt;code&gt;genericPost&lt;/code&gt; in the first place.
            Here&#x27;s what the original source looks like before I run my
            &lt;a href=&quot;https://github.com/nex3/nex3.github.io/blob/main/tool/inject-embeds.js&quot; rel=&quot;noopener noreferrer&quot;&gt;embed injector&lt;/a&gt;
            on it:
          &lt;/p&gt;
          &lt;pre&gt;&lt;code&gt; 
https://nex-3.com/blog/once-i-add-the/

&lt;/code&gt;&lt;/pre&gt;
          &lt;p&gt;
            That&#x27;s it! Just a URL surrounded by empty lines. The injector pulls
            down the webpage, extracts critical information about the blog post,
            and replaces the URL with a call to &lt;code&gt;genericPost&lt;/code&gt;. My
            Letterboxd and Cohost embeds work the same way, with their own
            custom templates and metadata that let me match the style of the
            original websites.
          &lt;/p&gt;
          &lt;h2&gt;Structured post data with h-entry&lt;/h2&gt;
          &lt;p&gt;
            But I did say that this wasn&#x27;t specific to my blog. With Letterboxd
            and Cohost, I&#x27;ve just hard-coded their HTML structure. I can&#x27;t rely
            on that if I want to get information from any old blog, though. They
            all use different HTML structures!
          &lt;/p&gt;
          &lt;p&gt;
            So instead, I&#x27;m making use of the
            &lt;a href=&quot;https://microformats.org/wiki/h-entry&quot; rel=&quot;noopener noreferrer&quot;&gt;h-entry microformat&lt;/a&gt;. This is a tiny little specification that defines a way to mark up
            an existing post to indicate metadata in the existing HTML
            structure. At its simplest, it&#x27;s just a few class names annotated
            with the HTML. Here&#x27;s the simplified HTML for the post above:
          &lt;/p&gt;
          &lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;h-entry&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;attribution&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span&gt;href&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;/blog/once-i-add-the/&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;rel&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;canonical&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;u-url&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
      Posted
      &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;time&lt;/span&gt; &lt;span&gt;datetime&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;2024-09-20T07:06:00Z&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;dt-published&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
        20 September 2024
      &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;time&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
      by
      &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;p-author h-card&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;p-name&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;Natalie&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;u-url&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;/&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;data&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;data&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;p-logo&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;/assets/avatar.webp&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;data&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;e-content&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
      Once I add the ability to embed arbitrary blog posts from other
      blogs on here it&#x27;s over. I&#x27;m gonna be reblogging like a wild animal.
      Y&#x27;all are gonna have your eyes blown clean outta your heads.
    &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;ol&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;tags&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span&gt;href&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;/tag/meta&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;p-category&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;meta&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;ol&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
          &lt;p&gt;Here are the special class names I&#x27;m using:&lt;/p&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;code&gt;h-entry&lt;/code&gt; wraps the entire thing, indicating that it&#x27;s
              a post.
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;u-url&lt;/code&gt; goes on a link and indicates the post&#x27;s
              canonical URL.
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;dt-published&lt;/code&gt; goes on a
              &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt; element&lt;/a&gt;
              and indicates when the post was made.
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;p-author&lt;/code&gt; can just be the author&#x27;s name, but I&#x27;ve made
              it into a whole
              &lt;a href=&quot;https://microformats.org/wiki/h-card&quot; rel=&quot;noopener noreferrer&quot;&gt;h-card&lt;/a&gt; with the
              following metadata:
              &lt;ul&gt;
                &lt;li&gt;&lt;code&gt;p-name&lt;/code&gt; is my name.&lt;/li&gt;
                &lt;li&gt;
                  &lt;code&gt;u-url&lt;/code&gt; is my personal URL, which is to say this
                  website.
                &lt;/li&gt;
                &lt;li&gt;
                  &lt;code&gt;p-logo&lt;/code&gt; is the URL of my avatar, so people
                  reblogging me have something to show by my posts.
                &lt;/li&gt;
              &lt;/ul&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;p-name&lt;/code&gt; isn&#x27;t used here because this post is untitled,
              but if it had a title that would be the class for it.
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;e-content&lt;/code&gt; is the HTML content of the post itself.
            &lt;/li&gt;
            &lt;li&gt;
              &lt;code&gt;p-category&lt;/code&gt; is the name of a tag associated with the
              post.
            &lt;/li&gt;
          &lt;/ul&gt;
          &lt;p&gt;
            Of these, only &lt;code&gt;h-entry&lt;/code&gt;, &lt;code&gt;u-url&lt;/code&gt;, and
            &lt;code&gt;e-content&lt;/code&gt; are &lt;em&gt;really&lt;/em&gt; critical. There are a
            handful of other features defined in the spec, including a way to
            indicate which post you&#x27;re replying to, that you should check out if
            you&#x27;re interested. But these are the most critical.
          &lt;/p&gt;
          &lt;h2&gt;Make your posts rebloggable!&lt;/h2&gt;
          &lt;p&gt;
            You should do this too! If you can edit your blog&#x27;s post layout,
            it&#x27;s extremely easy. Just add those classes to the appropriate
            places, and you&#x27;re off to go. If you don&#x27;t already have an HTML
            element for some piece of information, you can add invisible
            &lt;code&gt;&amp;lt;data&amp;gt;&lt;/code&gt; tags like I did above to provide the info
            to consumers without changing the way your post looks to readers.
          &lt;/p&gt;
          &lt;p&gt;
            Even if your blog doesn&#x27;t allow you to edit the layout, if you can
            add HTML to the posts you can do this by hand. There&#x27;s no need to
            use the specific &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; or
            &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; tags I do above... just divs will work. You
            can even make your rebloggable content different than the original,
            which I&#x27;m planning to do to avoid having my embeds look too weird if
            they get reblogged.
          &lt;/p&gt;
          &lt;p&gt;
            If you end up adding h-entry support, or even better if you end up
            making use of mine, drop me a comment and let me know!
          &lt;/p&gt;
        &lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;





&lt;p&gt;i’ve started &lt;a href=&quot;https://github.com/delan/autost/issues/16&quot; rel=&quot;noopener noreferrer&quot;&gt;adding h-entry support&lt;/a&gt; to autost! still need to rewrite relative urls and cache embedded images, but i can already import and share/reply to posts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ autost import https://nex-3.com/blog/reblogging-posts-with-h-entry/
2024-10-03T15:30:33.221087Z  INFO run_migrations: autost::migrations: hard linking attachments out of site/attachments
2024-10-03T15:30:33.411823Z  INFO autost::command::import: writing RelativePath { inner: &quot;posts/imported/1.html&quot;, kind: Other }
2024-10-03T15:30:33.422458Z  INFO autost::command::import: click here to reply: http://[::1]:8420/posts/compose?reply_to=imported/1.html
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7927582.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7927582.html"/>
<published>2024-10-01T07:31:13.085Z</published>
<title>autost: archive your chosts, write new posts, reply to posts</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="The Cohost Global Feed" /><category term="cohost utilities" /><category term="Cohost Shutdown" /><category term="data export" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7927582.html&quot; id=&quot;thread-7927582.html&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/6c34009f-0912-449c-92d4-d4b24ae55f3a/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/6c34009f-0912-449c-92d4-d4b24ae55f3a&quot; src=&quot;/posts/attachments/thumbs/6c34009f-0912-449c-92d4-d4b24ae55f3a/image.png&quot; alt=&quot;&quot; width=&quot;672&quot; height=&quot;832&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/10bdb2f1-f33b-4f96-a51f-7262d6bcb542/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/10bdb2f1-f33b-4f96-a51f-7262d6bcb542&quot; src=&quot;/posts/attachments/thumbs/10bdb2f1-f33b-4f96-a51f-7262d6bcb542/image.png&quot; alt=&quot;&quot; width=&quot;672&quot; height=&quot;832&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://github.com/delan/autost/releases/tag/0.3.0&quot; rel=&quot;noopener noreferrer&quot;&gt;download&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;[&lt;a href=&quot;https://github.com/delan/autost/blob/0.3.0/README.md&quot; rel=&quot;noopener noreferrer&quot;&gt;readme&lt;/a&gt;] [&lt;a href=&quot;https://github.com/delan/autost/blob/0.3.0/CHANGELOG.md#030-2024-10-01&quot; rel=&quot;noopener noreferrer&quot;&gt;changes in 0.3.0&lt;/a&gt;]&lt;/div&gt;
&lt;p&gt;autost is a &lt;a href=&quot;https://cohost.org/delan/post/7848210-autost-a-cohost-com&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-compatible archive and blog engine&lt;/a&gt;. it’s pretty much a static site generator that can import chosts, but i plan to eventually add a rss/atom feed reader and chronological timeline.&lt;/p&gt;
&lt;p&gt;you no longer need to know how to use &lt;code&gt;git&lt;/code&gt; or &lt;code&gt;cargo&lt;/code&gt; to use it! and now you can write posts and reply to posts in the composer!&lt;/p&gt;
&lt;p&gt;cohost is shutting down soon, so &lt;a href=&quot;https://shuppy.org/posts/tagged/autost.html&quot; rel=&quot;noopener noreferrer&quot;&gt;go here for updates&lt;/a&gt; or email me on &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt; if you have any questions. enjoy :3&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7886901.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7886901.html"/>
<published>2024-09-29T23:45:20.039Z</published>
<title>untitled post by @delan</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="The Cohost Global Feed" /><category term="cohost utilities" /><category term="Cohost Shutdown" /><category term="data export" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7886901.html&quot; id=&quot;thread-7886901.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7848210-autost-a-cohost-com&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-27T04:03:45.836Z&quot;&gt;2024-09-27T04:03:45.836Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        autost: a cohost-compatible archive and blog engine
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;h4 style=&quot;text-align: right;&quot;&gt;…and hopefully feed reader?&lt;/h4&gt;
&lt;p&gt;want to &lt;strong&gt;archive your chosts on your website&lt;/strong&gt; but have too many for the &lt;a href=&quot;https://cohost.org/astral/post/7796845-div-style-position&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost web component&lt;/a&gt;? want something like &lt;a href=&quot;https://cohost.org/blep/post/7639936-cohost-dl&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-dl&lt;/a&gt; except &lt;strong&gt;you can keep posting&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost&quot; style=&quot;font-size: 1.5em;&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; is my take on that. you can see what it looks like &lt;a href=&quot;https://shuppy.org/posts/&quot; rel=&quot;noopener noreferrer&quot;&gt;on shuppy.org&lt;/a&gt;:&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/7191aaa0-930a-4498-ac83-4fd435c50e79&quot; src=&quot;/posts/attachments/thumbs/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/342f6e12-13e7-4728-af5c-98719e4496fe&quot; src=&quot;/posts/attachments/thumbs/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;p&gt;it’s pretty much a static site generator that can import chosts. some features:&lt;/p&gt;&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;perfect html rendering of your chosts and css crimes&lt;br&gt;
&lt;small&gt;(css things like bounce and spin work too, but not yet 100%)&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;include and exclude specific tags or chosts in your archive&lt;/li&gt;
&lt;li&gt;tag your archived chosts without editing them on cohost&lt;/li&gt;
&lt;li&gt;automatically rename and add tags based on the existing tags&lt;/li&gt;
&lt;li&gt;make new posts with mostly-cohost-compatible markdown&lt;br&gt;
&lt;small&gt;(there’s even a post composer with live preview! but it’s very much a wip)&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;i’ve only had a couple weeks to work on this, so it’s not the most polished, and for now you need a bit of command line knowledge to get it going.&lt;/p&gt;
&lt;p&gt;but my goal here is to make a blog engine with the same posting &lt;em&gt;and reading&lt;/em&gt; experience as cohost, where you can follow people with rss/atom feeds, see their posts on a chronological timeline, and share their posts on yours.&lt;/p&gt;
&lt;p&gt;does that vibe with anyone? should i keep working on this? let me know!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7878145-autost-0-2-0-it-has&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-29T12:12:41.252Z&quot;&gt;2024-09-29T12:12:41.252Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        autost 0.2.0: it has a compose button now
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;p&gt;some big usability improvements in this one, plus a couple of breaking changes!&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/89d8eaa9-6a12-45ba-b991-0202a113e486/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/89d8eaa9-6a12-45ba-b991-0202a113e486&quot; src=&quot;/posts/attachments/thumbs/89d8eaa9-6a12-45ba-b991-0202a113e486/image.png&quot; alt=&quot;&quot; width=&quot;960&quot; height=&quot;800&quot;&gt;&lt;/a&gt;

&lt;p&gt;&lt;strong&gt;breaking changes:&lt;/strong&gt; &lt;code&gt;cohost2autost&lt;/code&gt; no longer takes the &lt;code&gt;./posts&lt;/code&gt; and &lt;code&gt;site/attachments&lt;/code&gt; arguments, and &lt;code&gt;render&lt;/code&gt; no longer takes the &lt;code&gt;site&lt;/code&gt; argument. these paths were always &lt;code&gt;./posts&lt;/code&gt;, &lt;code&gt;site/attachments&lt;/code&gt;, and &lt;code&gt;site&lt;/code&gt; (&lt;a href=&quot;https://github.com/delan/autost/issues/8&quot; rel=&quot;noopener noreferrer&quot;&gt;#8&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;when viewing your site with the &lt;code&gt;server&lt;/code&gt;, there’s a &lt;strong&gt;compose&lt;/strong&gt; button now (&lt;a href=&quot;https://github.com/delan/autost/issues/7&quot; rel=&quot;noopener noreferrer&quot;&gt;#7&lt;/a&gt;).&lt;/p&gt;&lt;hr&gt;
&lt;p&gt;in the &lt;code&gt;server&lt;/code&gt;, you also get a link to your site in the terminal now, and you no longer get a &lt;code&gt;NotFound&lt;/code&gt; error if your &lt;code&gt;base_url&lt;/code&gt; setting is not &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;you no longer have to type &lt;code&gt;RUST_LOG=info&lt;/code&gt; to see what’s happening (&lt;a href=&quot;https://github.com/delan/autost/issues/5&quot; rel=&quot;noopener noreferrer&quot;&gt;#5&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;attachment filenames containing &lt;code&gt;%&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; are now handled correctly (&lt;a href=&quot;https://github.com/delan/autost/issues/4&quot; rel=&quot;noopener noreferrer&quot;&gt;#4&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;questions and contributions are welcome!&lt;/strong&gt; if you run into any problems, or want to help out, let me know &lt;a href=&quot;https://github.com/delan/autost/issues&quot; rel=&quot;noopener noreferrer&quot;&gt;on github&lt;/a&gt; or email me on &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;oh and &lt;a href=&quot;https://shuppy.org/posts/tagged/autost.html&quot; rel=&quot;noopener noreferrer&quot;&gt;go here for updates&lt;/a&gt; once cohost noposts :)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;














&lt;p&gt;rebug for the morning crew! please let me know if autost works or doesn&#x27;t work for you, and &lt;a href=&quot;https://shuppy.org/posts/tagged/autost.html&quot; rel=&quot;noopener noreferrer&quot;&gt;go here for updates&lt;/a&gt; ^w^&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7878145.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7878145.html"/>
<published>2024-09-29T12:12:41.252Z</published>
<title>autost 0.2.0: it has a compose button now</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="The Cohost Global Feed" /><category term="cohost utilities" /><category term="Cohost Shutdown" /><category term="data export" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7878145.html&quot; id=&quot;thread-7878145.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7848210-autost-a-cohost-com&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-27T04:03:45.836Z&quot;&gt;2024-09-27T04:03:45.836Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        autost: a cohost-compatible archive and blog engine
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;h4 style=&quot;text-align: right;&quot;&gt;…and hopefully feed reader?&lt;/h4&gt;
&lt;p&gt;want to &lt;strong&gt;archive your chosts on your website&lt;/strong&gt; but have too many for the &lt;a href=&quot;https://cohost.org/astral/post/7796845-div-style-position&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost web component&lt;/a&gt;? want something like &lt;a href=&quot;https://cohost.org/blep/post/7639936-cohost-dl&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-dl&lt;/a&gt; except &lt;strong&gt;you can keep posting&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost&quot; style=&quot;font-size: 1.5em;&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; is my take on that. you can see what it looks like &lt;a href=&quot;https://shuppy.org/posts/&quot; rel=&quot;noopener noreferrer&quot;&gt;on shuppy.org&lt;/a&gt;:&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/7191aaa0-930a-4498-ac83-4fd435c50e79&quot; src=&quot;/posts/attachments/thumbs/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/342f6e12-13e7-4728-af5c-98719e4496fe&quot; src=&quot;/posts/attachments/thumbs/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;p&gt;it’s pretty much a static site generator that can import chosts. some features:&lt;/p&gt;&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;perfect html rendering of your chosts and css crimes&lt;br&gt;
&lt;small&gt;(css things like bounce and spin work too, but not yet 100%)&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;include and exclude specific tags or chosts in your archive&lt;/li&gt;
&lt;li&gt;tag your archived chosts without editing them on cohost&lt;/li&gt;
&lt;li&gt;automatically rename and add tags based on the existing tags&lt;/li&gt;
&lt;li&gt;make new posts with mostly-cohost-compatible markdown&lt;br&gt;
&lt;small&gt;(there’s even a post composer with live preview! but it’s very much a wip)&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;i’ve only had a couple weeks to work on this, so it’s not the most polished, and for now you need a bit of command line knowledge to get it going.&lt;/p&gt;
&lt;p&gt;but my goal here is to make a blog engine with the same posting &lt;em&gt;and reading&lt;/em&gt; experience as cohost, where you can follow people with rss/atom feeds, see their posts on a chronological timeline, and share their posts on yours.&lt;/p&gt;
&lt;p&gt;does that vibe with anyone? should i keep working on this? let me know!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7854077-empty&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-27T15:18:30.415Z&quot;&gt;2024-09-27T15:18:30.415Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;














&lt;p&gt;some big usability improvements in this one, plus a couple of breaking changes!&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/89d8eaa9-6a12-45ba-b991-0202a113e486/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/89d8eaa9-6a12-45ba-b991-0202a113e486&quot; src=&quot;/posts/attachments/thumbs/89d8eaa9-6a12-45ba-b991-0202a113e486/image.png&quot; alt=&quot;&quot; width=&quot;960&quot; height=&quot;800&quot;&gt;&lt;/a&gt;

&lt;p&gt;&lt;strong&gt;breaking changes:&lt;/strong&gt; &lt;code&gt;cohost2autost&lt;/code&gt; no longer takes the &lt;code&gt;./posts&lt;/code&gt; and &lt;code&gt;site/attachments&lt;/code&gt; arguments, and &lt;code&gt;render&lt;/code&gt; no longer takes the &lt;code&gt;site&lt;/code&gt; argument. these paths were always &lt;code&gt;./posts&lt;/code&gt;, &lt;code&gt;site/attachments&lt;/code&gt;, and &lt;code&gt;site&lt;/code&gt; (&lt;a href=&quot;https://github.com/delan/autost/issues/8&quot; rel=&quot;noopener noreferrer&quot;&gt;#8&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;when viewing your site with the &lt;code&gt;server&lt;/code&gt;, there’s a &lt;strong&gt;compose&lt;/strong&gt; button now (&lt;a href=&quot;https://github.com/delan/autost/issues/7&quot; rel=&quot;noopener noreferrer&quot;&gt;#7&lt;/a&gt;).&lt;/p&gt;&lt;hr&gt;
&lt;p&gt;in the &lt;code&gt;server&lt;/code&gt;, you also get a link to your site in the terminal now, and you no longer get a &lt;code&gt;NotFound&lt;/code&gt; error if your &lt;code&gt;base_url&lt;/code&gt; setting is not &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;you no longer have to type &lt;code&gt;RUST_LOG=info&lt;/code&gt; to see what’s happening (&lt;a href=&quot;https://github.com/delan/autost/issues/5&quot; rel=&quot;noopener noreferrer&quot;&gt;#5&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;attachment filenames containing &lt;code&gt;%&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt; are now handled correctly (&lt;a href=&quot;https://github.com/delan/autost/issues/4&quot; rel=&quot;noopener noreferrer&quot;&gt;#4&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;questions and contributions are welcome!&lt;/strong&gt; if you run into any problems, or want to help out, let me know &lt;a href=&quot;https://github.com/delan/autost/issues&quot; rel=&quot;noopener noreferrer&quot;&gt;on github&lt;/a&gt; or email me on &lt;a href=&quot;mailto:autost@shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;autost@shuppy.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;oh and &lt;a href=&quot;https://shuppy.org/posts/tagged/autost.html&quot; rel=&quot;noopener noreferrer&quot;&gt;go here for updates&lt;/a&gt; once cohost noposts :)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7848210.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7848210.html"/>
<published>2024-09-27T04:03:45.836Z</published>
<title>autost: a cohost-compatible archive and blog engine</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="The Cohost Global Feed" /><category term="cohost utilities" /><category term="Cohost Shutdown" /><category term="data export" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7848210.html&quot; id=&quot;thread-7848210.html&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;h4 style=&quot;text-align: right;&quot;&gt;…and hopefully feed reader?&lt;/h4&gt;
&lt;p&gt;want to &lt;strong&gt;archive your chosts on your website&lt;/strong&gt; but have too many for the &lt;a href=&quot;https://cohost.org/astral/post/7796845-div-style-position&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost web component&lt;/a&gt;? want something like &lt;a href=&quot;https://cohost.org/blep/post/7639936-cohost-dl&quot; rel=&quot;noopener noreferrer&quot;&gt;cohost-dl&lt;/a&gt; except &lt;strong&gt;you can keep posting&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/delan/autost&quot; style=&quot;font-size: 1.5em;&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt; is my take on that. you can see what it looks like &lt;a href=&quot;https://shuppy.org/posts/&quot; rel=&quot;noopener noreferrer&quot;&gt;on shuppy.org&lt;/a&gt;:&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/7191aaa0-930a-4498-ac83-4fd435c50e79&quot; src=&quot;/posts/attachments/thumbs/7191aaa0-930a-4498-ac83-4fd435c50e79/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/342f6e12-13e7-4728-af5c-98719e4496fe&quot; src=&quot;/posts/attachments/thumbs/342f6e12-13e7-4728-af5c-98719e4496fe/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;p&gt;it’s pretty much a static site generator that can import chosts. some features:&lt;/p&gt;&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;perfect html rendering of your chosts and css crimes&lt;br&gt;
&lt;small&gt;(css things like bounce and spin work too, but not yet 100%)&lt;/small&gt;&lt;/li&gt;
&lt;li&gt;include and exclude specific tags or chosts in your archive&lt;/li&gt;
&lt;li&gt;tag your archived chosts without editing them on cohost&lt;/li&gt;
&lt;li&gt;automatically rename and add tags based on the existing tags&lt;/li&gt;
&lt;li&gt;make new posts with mostly-cohost-compatible markdown&lt;br&gt;
&lt;small&gt;(there’s even a post composer with live preview! but it’s very much a wip)&lt;/small&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;i’ve only had a couple weeks to work on this, so it’s not the most polished, and for now you need a bit of command line knowledge to get it going.&lt;/p&gt;
&lt;p&gt;but my goal here is to make a blog engine with the same posting &lt;em&gt;and reading&lt;/em&gt; experience as cohost, where you can follow people with rss/atom feeds, see their posts on a chronological timeline, and share their posts on yours.&lt;/p&gt;
&lt;p&gt;does that vibe with anyone? should i keep working on this? let me know!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;The Cohost Global Feed&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost utilities&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;data export&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7831949.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7831949.html"/>
<published>2024-09-25T16:02:42.280Z</published>
<title>active ingredients: path manipulation 70% w/w</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7831949.html&quot; id=&quot;thread-7831949.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;p&gt;i gotta pay down this tech debt asafp or this thing is gonna burst into treats.&lt;/p&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/46b908a2-144e-40b1-a5ba-84eb49b94065/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/46b908a2-144e-40b1-a5ba-84eb49b94065&quot; src=&quot;/posts/attachments/thumbs/46b908a2-144e-40b1-a5ba-84eb49b94065/image.png&quot; alt=&quot;&quot; width=&quot;417&quot; height=&quot;265&quot;&gt;&lt;/a&gt;

&lt;p&gt;also, clicking “publish” in the composer redirects you to the post now, but i figured this would be funnier than posting a screenshot of a redirect.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7831277.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7831277.html"/>
<published>2024-09-25T14:44:31.092Z</published>
<title>farewell programmer art, hello cohost colors</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7831277.html&quot; id=&quot;thread-7831277.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/cd9b0d49-8b80-4bf3-b6d6-8ca929770cf9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/cd9b0d49-8b80-4bf3-b6d6-8ca929770cf9&quot; src=&quot;/posts/attachments/thumbs/cd9b0d49-8b80-4bf3-b6d6-8ca929770cf9/image.png&quot; alt=&quot;&quot; width=&quot;665&quot; height=&quot;706&quot;&gt;&lt;/a&gt;

&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7821634.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7821634.html"/>
<published>2024-09-24T14:49:50.330Z</published>
<title>shuppy.org now works in fluent-reader</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" /><category term="rss" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7821634.html&quot; id=&quot;thread-7821634.html&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;











&lt;article style=&quot;padding: 1em; border: 1px solid black;&quot;&gt;
    &lt;header&gt;
        &lt;div&gt;
            
            eggbug
            
            asked…
        &lt;/div&gt;
    &lt;/header&gt;
    &lt;p&gt;I use (fluent-reader)[https://github.com/yang991178/fluent-reader]&lt;/p&gt;

&lt;/article&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/df87d369-3fdd-43e8-89cf-49dc5a7b5ff0/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/df87d369-3fdd-43e8-89cf-49dc5a7b5ff0&quot; src=&quot;/posts/attachments/thumbs/df87d369-3fdd-43e8-89cf-49dc5a7b5ff0/image.png&quot; alt=&quot;before: images don’t load&quot; width=&quot;992&quot; height=&quot;740&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e284fd63-224f-4587-8950-ea509209b4be/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e284fd63-224f-4587-8950-ea509209b4be&quot; src=&quot;/posts/attachments/thumbs/e284fd63-224f-4587-8950-ea509209b4be/image.png&quot; alt=&quot;after: images do load&quot; width=&quot;992&quot; height=&quot;740&quot;&gt;&lt;/a&gt;

&lt;p&gt;thanks, i’ve fixed that now!&lt;/p&gt;
&lt;p&gt;fluent-reader seems to need a &amp;lt;base&amp;gt; tag in each &amp;lt;content&amp;gt; to tell it how to resolve relative urls. it doesn’t support &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc4287#section-2&quot; rel=&quot;noopener noreferrer&quot;&gt;xml:base&lt;/a&gt;, which i’ve now &lt;a href=&quot;https://github.com/yang991178/fluent-reader/issues/692&quot; rel=&quot;noopener noreferrer&quot;&gt;reported to the devs&lt;/a&gt;, but it turns out my feeds didn’t have that either. my bad!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rss&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7809475.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7809475.html"/>
<published>2024-09-23T09:52:01.631Z</published>
<title>yes!! this is what we need!!</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7809475.html&quot; id=&quot;thread-7809475.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/nex3&quot;&gt;Natalie&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/nex3&quot; class=&quot;handle&quot;&gt;@nex3&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/nex3/post/7808603-reblogging-as-a-ve&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-23T06:19:47.460Z&quot;&gt;2024-09-23T06:19:47.460Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        &quot;Reblogging&quot; as a Verb on the Modern Web
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;p&gt;One of my main goals with &lt;a href=&quot;https://nex-3.com/&quot; rel=&quot;noopener noreferrer&quot;&gt;my new blog&lt;/a&gt; is to preserve the concept of &quot;reposting&quot; that&#x27;s both a successful and, I think, desirable aspect of the &quot;social network&quot; model. It&#x27;s a great way to spread visibility of other people&#x27;s work and engage in conversation across different blogs. It&#x27;s a big part of why I&#x27;m putting so much work into the concept of &quot;embeds&quot;, as for &lt;a href=&quot;https://nex-3.com/blog/forkie/&quot; rel=&quot;noopener noreferrer&quot;&gt;Cohost&lt;/a&gt; and &lt;a href=&quot;https://nex-3.com/blog/the-conformist/&quot; rel=&quot;noopener noreferrer&quot;&gt;Letterboxd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These embeds are quite automated: I just drop a link in my blog and as long as it&#x27;s one of the supported sites, &lt;a href=&quot;https://github.com/nex3/nex3.github.io/blob/main/tool/inject-embeds.js&quot; rel=&quot;noopener noreferrer&quot;&gt;a little post-processing tool&lt;/a&gt; automatically fetches the information and &lt;a href=&quot;https://github.com/nex3/nex3.github.io/blob/d65f009d0fd005b319cd293039101ee79378a1ea/source/blog/010-the-conformist.md?plain=1#L10-L21&quot; rel=&quot;noopener noreferrer&quot;&gt;adds it as template params&lt;/a&gt;. All in all, I&#x27;m pretty happy with this flow, but there&#x27;s one critical problem: it requires me to write a separate scraper for every website.&lt;/p&gt;
&lt;p&gt;The great strength of independent websites for social interaction is that everyone&#x27;s can be as different as they&#x27;d like, but that also means that there&#x27;s no standard way to understand or interact with them. Even something as simple as &quot;what is the text of the post at this URL&quot; is difficult to answer in general. We&#x27;d all hope that everyone would use semantic HTML so perfectly that you could just read the contents of the outermost &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; tag on the page, but the real world is never so pristine. We need a more explicit way to indicate the critical prose and metadata for something that&#x27;s considered a &quot;post&quot;.&lt;/p&gt;
&lt;p&gt;I&#x27;ve been rolling this problem around in my head for the past week, and I have what is at least the germ of an idea. The core observation is that RSS and Atom feeds already have all the metadata that&#x27;s strictly necessary for something like this, but they suffer from being time-limited—their use-case is focused on syndication, so a website can only be expected to have its most recent posts available in such a nice format.&lt;/p&gt;
&lt;p&gt;So imagine if we co-opted this format for use in something more persistent. Something like:&lt;/p&gt;
&lt;pre style=&quot;background-color:rgb(0, 36, 81);color:rgb(255, 255, 255);position:relative;padding:0;line-height:1.2rem;margin-bottom:0&quot;&gt;&lt;code style=&quot;padding:0 16px;display:block;margin-bottom:9px;margin-top:9px&quot;&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;somens:reblog&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;post.xml&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;text-align: right; font-size: min(1.87vw, 70%); opacity: 0.7; margin-bottom: 1.7142857em;&quot;&gt;made with &lt;a href=&quot;https://cohost.org/nex3&quot; rel=&quot;noopener noreferrer&quot;&gt;@nex3&lt;/a&gt;&#x27;s &lt;a href=&quot;https://nex3.github.io/cohost-highlight&quot; rel=&quot;noopener noreferrer&quot;&gt;syntax highlighter&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;where the XML file looks like&lt;/p&gt;
&lt;pre style=&quot;background-color:rgb(0, 36, 81);color:rgb(255, 255, 255);position:relative;padding:0;line-height:1.2rem&quot;&gt;&lt;code style=&quot;padding:0 16px;display:block;margin-bottom:9px;margin-top:9px&quot;&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(255, 197, 143)&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;reblog&lt;/span&gt; &lt;span style=&quot;&quot;&gt;xmlns&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;https://nex-3.com/somens&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;xmlns:atom&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;http://www.w3.org/2005/Atom&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;https://nex-3.com/blog/once-i-add-the/post.xml&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;self&quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;https://nex-3.com/&quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;
  &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:entry&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;https://nex-3.com/blog/once-i-add-the/&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;alternate&quot;&lt;/span&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:id&lt;/span&gt;&amp;gt;&lt;/span&gt;https://nex-3.com/blog/once-i-add-the/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:id&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:published&lt;/span&gt;&amp;gt;&lt;/span&gt;2024-09-20T07:06:00Z&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:published&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:updated&lt;/span&gt;&amp;gt;&lt;/span&gt;2024-09-20T07:06:00Z&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:updated&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:author&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:name&lt;/span&gt;&amp;gt;&lt;/span&gt;Natalie&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:name&lt;/span&gt;&amp;gt;&lt;/span&gt;
      &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:uri&lt;/span&gt;&amp;gt;&lt;/span&gt;https://nex-3.com/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:uri&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:author&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:category&lt;/span&gt;&amp;gt;&lt;/span&gt;meta&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:category&lt;/span&gt;&amp;gt;&lt;/span&gt;
    &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:content&lt;/span&gt; &lt;span style=&quot;&quot;&gt;type&lt;/span&gt;=&lt;span style=&quot;color: rgb(209, 241, 169)&quot;&gt;&quot;html&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&amp;amp;lt;p&amp;amp;gt;Once I add the ability to embed arbitrary blog posts from other blogs on here it&amp;amp;#39;s over. I&amp;amp;#39;m gonna be reblogging like a wild animal. Y&amp;amp;#39;all are gonna have your eyes blown clean outta your heads.&amp;amp;lt;/p&amp;amp;gt;&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:content&lt;/span&gt;&amp;gt;&lt;/span&gt;
  &lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;atom:entry&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;&amp;lt;/&lt;span style=&quot;color: rgb(255, 157, 164)&quot;&gt;reblog&lt;/span&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you see my vision? This is &lt;em&gt;mostly&lt;/em&gt; just stealing structure from Atom, but with the explicit guarantee that any logical &quot;entries&quot; on the page in question will also exist in the &quot;reblog&quot; XML. Maybe there should be some sort of way to explicitly link the two as well, idk. But in the general case, a page with one post would link to a single-post XML file which could then be used as the source for a reblog. Wouldn&#x27;t that be neat?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Something like this &lt;a href=&quot;https://cohost.org/nex3/post/7812988-from-aaadelnrrv-in&quot; rel=&quot;noopener noreferrer&quot;&gt;already exists&lt;/a&gt;! Hooray!&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;rss&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;p&gt;i’ve been working on something similar that will rely on this (see &lt;a href=&quot;https://cohost.org/rc/tagged/shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;#shuppy.org&lt;/a&gt;, &lt;a href=&quot;https://github.com/delan/autost&quot; rel=&quot;noopener noreferrer&quot;&gt;autost&lt;/a&gt;). my posts are in an html-fragment-based format, which looks something like this:&lt;/p&gt;
&lt;pre style=&quot;background-color:rgb(27, 25, 24);color:rgb(168, 161, 159);position:relative;padding:0;line-height:1.2rem;margin-bottom:0&quot;&gt;&lt;code style=&quot;padding:0 16px;display:block;margin-bottom:9px;margin-top:9px&quot;&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;references&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;7801740/7742758.html&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;references&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;7801740/7800234.html&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;meta&lt;/span&gt; &lt;span style=&quot;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;title&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;content&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;the post hole works&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;meta&lt;/span&gt; &lt;span style=&quot;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;published&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;content&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;2024-09-22T14:24:07.963Z&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;link&lt;/span&gt; &lt;span style=&quot;&quot;&gt;rel&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;author&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;href&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;https://cohost.org/delan&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;shuppy (&lt;a href=&quot;https://cohost.org/delan&quot; rel=&quot;noopener noreferrer&quot;&gt;@delan&lt;/a&gt;)&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;&amp;lt;&lt;span style=&quot;color: rgb(242, 44, 64)&quot;&gt;meta&lt;/span&gt; &lt;span style=&quot;&quot;&gt;name&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;tags&quot;&lt;/span&gt; &lt;span style=&quot;&quot;&gt;content&lt;/span&gt;=&lt;span style=&quot;color: rgb(123, 151, 38)&quot;&gt;&quot;shuppy.org&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;

[post content in markdown or html here]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div style=&quot;text-align: right; font-size: min(1.87vw, 70%); opacity: 0.7; margin-bottom: 1.7142857em;&quot;&gt;made with &lt;a href=&quot;https://cohost.org/nex3&quot; rel=&quot;noopener noreferrer&quot;&gt;@nex3&lt;/a&gt;&#x27;s &lt;a href=&quot;https://nex3.github.io/cohost-highlight&quot; rel=&quot;noopener noreferrer&quot;&gt;syntax highlighter&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;this is not the best interchange format for others to reblog, though, because it relies on you rendering markdown the same way as me, and you would need to fetch the posts i’m replying to separately. your idea would really help with that!&lt;/p&gt;
&lt;p&gt;one suggestion: it would be nice if the format you came up with was also usable in the “whole blog” feed, where each &amp;lt;atom:entry&amp;gt; is a post, including the posts it replies to. i started thinking about atom-compatible ways of doing that in &lt;a href=&quot;https://cohost.org/delan/post/7760781-what-if-atom-had-a-w&quot; rel=&quot;noopener noreferrer&quot;&gt;this chost&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7801740.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7801740.html"/>
<published>2024-09-22T14:24:07.963Z</published>
<title>the post hole works</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7801740.html&quot; id=&quot;thread-7801740.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7800234-now-working-on-the-p&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-22T07:13:00.633Z&quot;&gt;2024-09-22T07:13:00.633Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        now working on the post hole
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;









&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/19d6b877-a005-4bca-bb1e-e1a5c3fd008c/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/19d6b877-a005-4bca-bb1e-e1a5c3fd008c&quot; src=&quot;/posts/attachments/thumbs/19d6b877-a005-4bca-bb1e-e1a5c3fd008c/image.png&quot; alt=&quot;&quot; width=&quot;659&quot; height=&quot;442&quot;&gt;&lt;/a&gt;

&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;











&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/194d4d5c-f106-434b-a196-136481a1b73f/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/194d4d5c-f106-434b-a196-136481a1b73f&quot; src=&quot;/posts/attachments/thumbs/194d4d5c-f106-434b-a196-136481a1b73f/image.png&quot; alt=&quot;&quot; width=&quot;660&quot; height=&quot;659&quot;&gt;&lt;/a&gt;

&lt;p&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/posts/10000000.html&quot; rel=&quot;noopener noreferrer&quot;&gt;https://shuppy.org/posts/10000000.html&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>10000000.html</id>
<link rel="alternate" href="https://shuppy.org/posts/10000000.html"/>
<published>2024-09-22T14:15:55.103Z</published>
<title>hello from autost</title>
<author>
<name>shuppy</name>
<uri>https://shuppy.org</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;10000000.md&quot; id=&quot;thread-10000000.md&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;






&lt;p&gt;is this thing on?&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7800234.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7800234.html"/>
<published>2024-09-22T07:13:00.633Z</published>
<title>now working on the post hole</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7800234.html&quot; id=&quot;thread-7800234.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/19d6b877-a005-4bca-bb1e-e1a5c3fd008c/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/19d6b877-a005-4bca-bb1e-e1a5c3fd008c&quot; src=&quot;/posts/attachments/thumbs/19d6b877-a005-4bca-bb1e-e1a5c3fd008c/image.png&quot; alt=&quot;&quot; width=&quot;659&quot; height=&quot;442&quot;&gt;&lt;/a&gt;

&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7773495.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7773495.html"/>
<published>2024-09-19T09:14:00.737Z</published>
<title>tag synonyms and tag implications</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7773495.html&quot; id=&quot;thread-7773495.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/0bb56ceb-6da7-49d0-8491-46cb2e09a0a7/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/0bb56ceb-6da7-49d0-8491-46cb2e09a0a7&quot; src=&quot;/posts/attachments/thumbs/0bb56ceb-6da7-49d0-8491-46cb2e09a0a7/image.png&quot; alt=&quot;&quot; width=&quot;960&quot; height=&quot;898&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/1c46e464-cacd-4c9b-842a-40a4874b0858/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/1c46e464-cacd-4c9b-842a-40a4874b0858&quot; src=&quot;/posts/attachments/thumbs/1c46e464-cacd-4c9b-842a-40a4874b0858/image.png&quot; alt=&quot;&quot; width=&quot;640&quot; height=&quot;659&quot;&gt;&lt;/a&gt;

&lt;p&gt;to better curate my chosts even after cohost shuts down, i can not only &lt;a href=&quot;https://cohost.org/delan/post/7764294-i-can-now-add-tags-t&quot; rel=&quot;noopener noreferrer&quot;&gt;tag chosts without editing them&lt;/a&gt;, but also rename tags and add related tags automatically. for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://shuppy.org/posts/tagged/photography.html&quot; rel=&quot;noopener noreferrer&quot;&gt;#photography&lt;/a&gt; now includes all of my birds and wildlife shots&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shuppy.org/posts/tagged/watching.html&quot; rel=&quot;noopener noreferrer&quot;&gt;#watching&lt;/a&gt; now includes all of my tv and film chosting&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shuppy.org/posts/tagged/reverse%20engineering.html&quot; rel=&quot;noopener noreferrer&quot;&gt;#reverse engineering&lt;/a&gt; now includes all of my ghidra chosts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(the blog software is &lt;a href=&quot;https://github.com/delan/autost&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;, if you wanna play around with it)&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7766329.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7766329.html"/>
<published>2024-09-18T16:32:29.265Z</published>
<title>untitled post by @delan</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7766329.html&quot; id=&quot;thread-7766329.html&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;article style=&quot;padding: 1em; border: 1px solid black;&quot;&gt;
    &lt;header&gt;
        &lt;div&gt;
            
            eggbug
            
            asked…
        &lt;/div&gt;
    &lt;/header&gt;
    &lt;p&gt;Just thought I would let you know that your rss page uses relative urls for the photos, which prevents my rss reader from being able to display them. I am not sure if this is an issue with my own rss reader or if this is something that affects all rss readers.&lt;/p&gt;

&lt;/article&gt;&lt;p&gt;thanks for the heads up! i&#x27;ve been testing with freshrss so far, if you can tell me what reader you use that would be really helpful&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7764294.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7764294.html"/>
<published>2024-09-18T12:10:39.594Z</published>
<title>i can now add tags to my chosts without editing them &gt;:3</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7764294.html&quot; id=&quot;thread-7764294.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7742758-made-a-website-so-i&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-16T16:11:32.249Z&quot;&gt;2024-09-16T16:11:32.249Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        made a website so i can continue chosting
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/2b81b2cb-7931-4143-9f6f-5554adb84edc/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/2b81b2cb-7931-4143-9f6f-5554adb84edc&quot; src=&quot;/posts/attachments/thumbs/2b81b2cb-7931-4143-9f6f-5554adb84edc/image.png&quot; alt=&quot;&quot; width=&quot;1462&quot; height=&quot;856&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/30a8309f-0dcb-439c-ae6a-ceecf447de55/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/30a8309f-0dcb-439c-ae6a-ceecf447de55&quot; src=&quot;/posts/attachments/thumbs/30a8309f-0dcb-439c-ae6a-ceecf447de55/image.png&quot; alt=&quot;&quot; width=&quot;1276&quot; height=&quot;1706&quot;&gt;&lt;/a&gt;

&lt;p&gt;next stop: tag synonyms and tag implications&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7760781.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7760781.html"/>
<published>2024-09-18T00:58:43.604Z</published>
<title>untitled post by @delan</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="i need this for my post-cohost post-hole" /><category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7760781.html&quot; id=&quot;thread-7760781.html&quot;&gt;


&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/jckarter&quot;&gt;jckarter&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/jckarter&quot; class=&quot;handle&quot;&gt;@jckarter&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/jckarter/post/7757786-what-if-rss-or-atom&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-17T19:56:12.434Z&quot;&gt;2024-09-17T19:56:12.434Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;







&lt;p&gt;what if rss or atom had a way to say that a post was in reply to another post from another feed. and feed readers let you display posts in threaded order&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;blockquote style=&quot;
    margin: 1rem;
    padding: 1rem;
    border: 1px solid #bfbab5;
    border-radius: 0.5rem;
    box-shadow: 0px 4px 5px #00000024, 0px 1px 10px #0000001f, 0px 2px 4px #0003;
&quot; class=&quot;post cohost h-entry&quot;&gt;
&lt;header&gt;
    &lt;div class=&quot;meta&quot;&gt;
        &lt;span class=&quot;p-author h-card&quot;&gt;&lt;a class=&quot;p-name u-url&quot; href=&quot;https://cohost.org/delan&quot;&gt;shuppy&lt;/a&gt; (&lt;a href=&quot;https://cohost.org/delan&quot; class=&quot;handle&quot;&gt;@delan&lt;/a&gt;)&lt;/span&gt;
        &lt;span class=&quot;gap&quot;&gt;—&lt;/span&gt;
        &lt;span&gt;
        &lt;a class=&quot;archived u-url&quot; href=&quot;https://cohost.org/delan/post/7760643-empty&quot;&gt;[archived]&lt;/a&gt;
        
        &lt;time class=&quot;dt-published&quot; datetime=&quot;2024-09-18T00:44:49.749Z&quot;&gt;2024-09-18T00:44:49.749Z&lt;/time&gt;
        
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;h1 class=&quot;p-name&quot;&gt;
        
        
        
    &lt;/h1&gt;
&lt;/header&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;












&lt;p&gt;what if atom had a way to embed a copy of the post you&#x27;re sharing or replying to, not just link to it? i would say just add a second &amp;lt;content&amp;gt;, but that’s &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc4287#section-4.1.2&quot; rel=&quot;noopener noreferrer&quot;&gt;forbidden&lt;/a&gt; :(&lt;/p&gt;
&lt;p&gt;edit: hmm how about nesting &amp;lt;entry&amp;gt;, or putting an &amp;lt;entry&amp;gt; inside the &amp;lt;link&amp;gt; (instead of self-closing &amp;lt;link/&amp;gt;). per &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc4287#section-6.4.2&quot; rel=&quot;noopener noreferrer&quot;&gt;§ 6.4.2&lt;/a&gt;, we may be able to nest &amp;lt;entry&amp;gt; as long as we have a foreign element in between, like &amp;lt;atom:entry&amp;gt;&amp;lt;cohost:reply&amp;gt;&amp;lt;atom:entry&amp;gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;i need this for my post-cohost post-hole&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

<entry>
<id>7742758.html</id>
<link rel="alternate" href="https://shuppy.org/posts/7742758.html"/>
<published>2024-09-16T16:11:32.249Z</published>
<title>made a website so i can continue chosting</title>
<author>
<name>shuppy (@delan)</name>
<uri>https://cohost.org/delan</uri>
</author>
<category term="cohost" /><category term="Cohost Shutdown" /><category term="shuppy.org" /><category term="autost" />
<content type="html" xml:base="https://shuppy.org/posts/">&lt;base href="https://shuppy.org/posts/"&gt;
&lt;article class=&quot;thread h-entry&quot; data-original-path=&quot;7742758.html&quot; id=&quot;thread-7742758.html&quot;&gt;


&lt;article class=&quot;post cohost&quot;&gt;

    
    &lt;div class=&quot;content&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;










&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/5a41b292-29ae-4180-94fd-8a5d2b88f0f9&quot; src=&quot;/posts/attachments/thumbs/5a41b292-29ae-4180-94fd-8a5d2b88f0f9/image.png&quot; alt=&quot;screenshot of the top of shuppy.org, with a bunch of tag links at the top&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;&lt;a target=&quot;_blank&quot; href=&quot;/posts/attachments/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img loading=&quot;lazy&quot; data-cohost-src=&quot;https://cohost.org/rc/attachment-redirect/e94f8e23-06ed-4dd1-bbae-ec3755204ef6&quot; src=&quot;/posts/attachments/thumbs/e94f8e23-06ed-4dd1-bbae-ec3755204ef6/image.png&quot; alt=&quot;screenshot of some other posts on shuppy.org, one of them showing tags&quot; width=&quot;700&quot; height=&quot;900&quot;&gt;&lt;/a&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org&quot; rel=&quot;noopener noreferrer&quot;&gt;shuppy.org&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
or add me to your feed reader!
&lt;div style=&quot;font-size: 1.5em;&quot;&gt;&amp;gt;&amp;gt;&amp;gt; &lt;a href=&quot;https://shuppy.org/feed.xml&quot; rel=&quot;noopener noreferrer&quot;&gt;atom feed&lt;/a&gt; &amp;lt;&amp;lt;&amp;lt;&lt;/div&gt;
(need a feed reader? &lt;a href=&quot;https://cohost.org/delan/post/7695899-what-rss-feed-reader#comments&quot; rel=&quot;noopener noreferrer&quot;&gt;here are a bunch&lt;/a&gt;)
&lt;br&gt;(it works well in FreshRSS and thunderbird)
&lt;/div&gt;
&lt;p&gt;oh and i’ve updated my contact details!! say hi and send me stuff :3&lt;/p&gt;
&lt;p&gt;i had an old website, but for a variety of reasons it really didn’t feel like a good successor to my cohost page. i feel like what i really want is to Continue Chosting next month, just on my own website? (well no, what i really want is cohost, but failing that…)&lt;/p&gt;
&lt;p&gt;so i made this! and many of my old chosts are there now too! i’ll need a bit more time to figure out how to actually make new posts (lol) and have them play nice with my old chosts, but hey, now you know where to find me once i do o/&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    
    &lt;footer&gt;&lt;div class=&quot;tags&quot;&gt;&lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;cohost&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;Cohost Shutdown&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;shuppy.org&lt;/span&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;span class=&quot;tag&quot;&gt;&lt;a class=&quot;tag&quot; href=&quot;/posts/tagged/autost.html&quot;&gt;#&lt;span class=&quot;p-category&quot;&gt;autost&lt;/span&gt;&lt;/a&gt;&lt;span class=&quot;actions&quot;&gt;&lt;/span&gt;
        &lt;/span&gt; 
    &lt;/div&gt;&lt;div class=&quot;actions&quot;&gt;&lt;/div&gt;&lt;/footer&gt;
&lt;/article&gt;

&lt;/article&gt;
</content>
</entry>

</feed>
