diff --git a/framework/mdx-hack.tsx b/framework/mdx-hack.tsx index 927416e6e57dfadea28f4540a996db8a239461cf..ffbe656306588a39ed90348050099dccca0a2088 100644 --- a/framework/mdx-hack.tsx +++ b/framework/mdx-hack.tsx @@ -3,7 +3,7 @@ export function useMDXComponents() { pre: async ( { filename, whitespace, tint, content: [, , { className, content }] }, ) => { - const lang = className.split("-")?.[1] ?? null; + const lang = className?.split("-")?.[1] ?? null; const code = ["tsx", "jsx", "json", "html", "ts", "js"].includes(lang) ? await highlight.highlightCode( content.trim(), diff --git a/package-lock.json b/package-lock.json index 4c8a6423c0be2cd6103a593308313280daae0dfb..e5e8344f03d06c86bbd8c97a499f55d88dd7a16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@types/node": "24.0.10", + "baseline-browser-mapping": "^2.9.14", "typescript": "5.8.3" } }, @@ -1454,9 +1455,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz", - "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==", + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" diff --git a/package.json b/package.json index 969236b5906e184ba0fef59bb1ebb9df50bbbf77..e9f9eba755cc2610f54e21cc837c4dcdaebdc7a9 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@types/node": "24.0.10", + "baseline-browser-mapping": "^2.9.14", "typescript": "5.8.3" }, "imports": { diff --git a/src/pages/index.marko b/src/pages/index.marko index fff9561d9678931f2309d2a2f267094a2bd2dc19..35db1efdc1a97160c5f3e4db23844da5724186d2 100644 --- a/src/pages/index.marko +++ b/src/pages/index.marko @@ -26,12 +26,13 @@ export const meta = {

song: waterfalls (2025-01-01)

things

questions and answers

+

recent activity

file browser

mailing list

links

-
+
email me - rss feed + donate

paper clover

diff --git a/src/pages/log.css b/src/pages/log.css new file mode 100644 index 0000000000000000000000000000000000000000..0f17f9eb7446ad8be34b04b77ce1641272536775 --- /dev/null +++ b/src/pages/log.css @@ -0,0 +1,16 @@ +h1 { + margin-top: 0; +} +body { + background-color: #ccddee; +} +pre:has(code) { + border: 1px solid black; + border-radius: 4px; + padding: 1rem; + overflow-x: auto; +} +blockquote { + border-left: 4px solid black; + padding-left: 8px; +} diff --git a/src/pages/log.mdx b/src/pages/log.mdx new file mode 100644 index 0000000000000000000000000000000000000000..4158f82557c6a032c237f95f307fd7e083a706ce --- /dev/null +++ b/src/pages/log.mdx @@ -0,0 +1,195 @@ +import './log.css'; +export default ({ content }) =>
{content}
+export const meta = { title: 'clover\'s log' }; + +[go back to the home page](/) + +[git]: https://git.paperclover.net/ +[home infra]: https://git.paperclover.net/clo/home-infra +[name paint bot]: https://git.paperclover.net/clo/discord-name-painter +[todo tracker]: https://git.paperclover.net/clo/todo-tracker +[ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector +[progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts + +# clover's log + +## 2026-01-12 + +tags: [album] + +i started more music work. i've gotten better at lyric writing, phrasing this +new song as a sort of "adventure". felt for one of the first times that i was +doing worldbuilding in a song. the imagery is that good. + +## 2026-01-11 + +tags: [progress.ts], [todo tracker] + +i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git +commits describe the tech better than me reiterating. + +> ## feat(lib/progress): implement streaming wire protocol +> resolves `#47` +> +> `encodeByteStream` converts these events into a `ReadableStream`. by +> batching events together, the stream contents remain small, that way the +> code that constructs progress nodes do not have to worry about calling +> many setters at once, it gets debounced be the serializer. stream +> backpressure causes larger time-gaps to be batched (smaller). this +> enables servers to respond with rich progress. +> +> ``` +> const root = new progress.Root(); +> doActionWithProgress(root).then(root.end, root.error); +> // streaming clients indicate a header +> if (req.headers.get("Accept")?.includes(progress.contentType)) +> return new Response(progress.encodeByteStream(root), { +> headers: { 'Content-Type': progress.contentType }, +> }); +> // to support non-streaming clients +> return Response.json(await root.asPromise()); +> ``` +> +> and `decodeByteStream` on the client: +> +> ``` +> const output = document.getElementById("output"); +> const res = await fetch(...); +> if (!res.ok) throw ...; +> const root = new progress.Root(); +> root.on("change", (active) => { +> output.innerText = ansi.strip(progress.formatAnsi( +> performance.now(), +> active, +> )); +> }); +> const result = await progress.decodeByteStream(res.body, root); +> output.innerText = JSON.stringify(result); +> ``` +> +> there is currently no document bindings, but i plan to. additionally, a +> React hook is very trivial to implement for this -- but that is +> unplanned for this repository. for transports that require JSON or +> UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of +> JSON objects which can be compressed at the developer's discretion. + +> ## feat(lib/progress): headless rendering + time estimation +> node signaling is done by providing a `progress.Root` to every node, +> dispatching events to it when the node changes. the root is connected to +> an observer to construct a UI out of it. there are two apis planned: +> +> - `attachToScreen` binds a root to a TTY screen (via the log.Widget API). +> the primary use of this is to implement the top level `progress.start`. +> +> - a serialization system that allows transmitting a `Root` over a wire. +> this commit was going to include this but it is an unexpectedly large +> component. +> +> - potentially a browser binding like `attachToDocument`. this will not +> be added in this patch. +> +> additionally, resolves #33 by implementing `estimatedTime` + +i also did a large part of the work to create a "code todo tracking" tool. i +would say it's about half done, since the second half is simply fixing all of +the little bugs there are. most of this code is currently ai-generated, but +with me manually coming in to write interfaces and the modular program +architecture. then i synthesize the code and the tests. this was basically just +going on ambiently while [progress.ts] was in progress. + +## 2026-01-09 + +tags: [home infra] + +finished SSO sub-project. im happy with the setup i used to protect internal +services, such as pgadmin and qbittorrent. it's a caddy snippet that i can +re-use very easily. + +``` +(reverse_proxy_auth) { + handle /snow.oauth2/* { + reverse_proxy "http://forward-auth" { + header_up X-Real-IP {remote_host} + header_up X-Forwarded-Uri {uri} + } + } + handle { + forward_auth "http://forward-auth" { + uri /snow.oauth2/auth + header_up X-Real-IP {remote_host} + @error status 401 + handle_response @error { + redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri} + } + @valid_group header X-Auth-Request-Groups *role:{args[1]}* + handle_response @valid_group { + method {method} + rewrite {uri} + reverse_proxy {args[0]} { + header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2" + {block} + } + } + handle_response { + rewrite /403.html + file_server { + status 403 + root /etc/caddy + } + } + } + } +} + +# usage +pg.{$HOME_DOMAIN} { + import reverse_proxy_auth "http://pgadmin" admin +} +qbt.{$HOME_DOMAIN} { + import reverse_proxy_auth "http://qbittorrent" media-manage +} +``` + + +## 2026-01-04 + +tags: [home infra] + +working on SSO for my internal services. for context, i have about 12 +self-hosted services running, half of which i allow my friends to access. +currently, this is done through manually creating an account on such service +(jellyfin, forgejo), but many are done through a caddy rule. in the interest of +making my password manager less confused (ip vs domain, subdomain etc), i'm +slowly reducing this setup to a single sign in page. + +to do this, i am using https://keycloak.org, which supports openid connect +(how i will configure forgejo and jellyfin), as well as a separate service to +provide forward auth proxying (how i protect services like copyparty, +syncthing, pgadmin, and many more). i tried authelia beforehand, but i really +do not recommend them due to how hard it is to configure, passkeys being +annoying to setup, and limited themes. i also dont recommend authentik, but i +couldnt figure out how to even start using it after i installed it. + +keycloak is a bit stupid on config. as all the config lies in the postgres +database, i can't use a config file to setup the primary realm. so instead, i +have this huge python script to use the API to upsert the configuration in. +this works pretty well, and means that for locally running the infrastructure +for testing, i can get the config to be the same (useful if you brick +keycloak, which is pretty easy to do). + +## 2026-01-02 + +tags: [home infra], [git], [name paint bot] show + +i deleted all my github repositories except four: my "readme", a bug +reproduction repo, the mirror for [ts lie detector], and a shared private repo +with someone that is load bearing. in this process, i've moved all the projects +to my [forgejo instance][git]. + +with this, [name paint bot], one of my few remaining projects that is still +active, moves to that forgejo instance using their github migrator. some of my +private projects, like my pet scripting language, were migrated as well. it +feels more alive on my site because of the theming and per-repo icons. + +after a year of forgejo, i am really happy with how it treats me. +