洪 民憙 (Hong Minhee) :nonbinary:
banner
hongminhee.hollo.social.ap.brid.gy
洪 民憙 (Hong Minhee) :nonbinary:
@hongminhee.hollo.social.ap.brid.gy
An intersectionalist, feminist, and socialist living in Seoul (UTC+09:00). @tokolovesme's spouse. Who's behind @fedify, @hollo, and @botkit. Write some free software […]

🌉 bridged from ⁂ https://hollo.social/@hongminhee, follow @ap.brid.gy to interact
I ordered the Keychron Q60 Max yesterday and it arrived today. I tried typing on it and it feels amazing! I'm so happy with it that I can't stop typing on Monkeytype. 😂
Keychron Q60 Max QMK/VIA Wireless Custom Mechanical Keyboard
Keychron Q60 Max is a fully customizable mechanical keyboard boasting 2.4 GHz wireless and Bluetooth 5.1 connectivity. The 60% layout is beneficial for users with limited desk space. Effortlessly Customize any key or create macro commands through VIA software. Compatible with Mac, Windows, and Linux.
www.keychron.com
December 23, 2025 at 6:16 PM
Found this helpful resource by Ben Boyter (@boyter): a collection of sequence diagrams explaining how #activitypub/#webfinger works in practice—covering post creation, follows, boosts, deletions, and user migration.

If you're trying to implement ActivityPub, the spec can be frustratingly vague […]
Original post on hollo.social
hollo.social
December 23, 2025 at 3:55 PM
Here's a #typescript API design challenge I'm working on: adding async support to #optique (CLI argument parser) without breaking the existing sync API.

The tricky part is combinators—when you compose parsers with `object()` or `or()`, the combined parser should automatically become async if […]
Original post on hollo.social
hollo.social
December 23, 2025 at 11:56 AM
오늘은 @nebuleto 님, @2chanhaeng 님, @z9mb1 님과 함께 西嶺(서령)에 왔다.
December 22, 2025 at 10:54 AM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
@hongminhee And like as if Chinese devs have some fundamental differences than "a programmer out of this community" 🤣
December 21, 2025 at 2:15 PM
Just had someone leave feedback on my F/OSS project saying “maybe that's fine if a product is focused on your Chinese community.”

I'm Korean. Every single piece of documentation is in English. There's nothing in Chinese anywhere in the project.

This kind of microaggression is exhausting. As a […]
Original post on hollo.social
hollo.social
December 21, 2025 at 2:07 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
### 보안 업데이트: Hollo 0.6.19 릴리스

Fedify의 HTML 파싱 코드에서 발견된 보안 취약점을 수정한 Hollo 0.6.19를 릴리스했습니다.

이 취약점(CVE-2025-68475)은 ReDoS(정규 표현식 서비스 거부) 문제로, 공격자가 연합 작업 중 특수하게 조작된 HTML 응답을 보내 서비스 장애를 유발할 수 있습니다. 악성 페이로드는 작지만(약 170바이트), Node.js 이벤트 루프를 장시간 차단할 수 있습니다.

모든 Hollo 운영자분들께 즉시 버전 0.6.19로 업그레이드하실 것을 강력히 […]
Original post on hollo.social
hollo.social
December 20, 2025 at 12:02 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
### Security Update: Hollo 0.6.19 Released

We have released Hollo 0.6.19 to address a security vulnerability in Fedify's HTML parsing code.

This vulnerability (CVE-2025-68475) is a ReDoS (Regular Expression Denial of Service) issue that could allow an attacker to cause service unavailability […]
Original post on hollo.social
hollo.social
December 20, 2025 at 12:00 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
### 🚨 Security Advisory: CVE-2025-68475

A ReDoS (Regular Expression Denial of Service) vulnerability has been discovered in Fedify's HTML parsing code. This vulnerability could allow a malicious federated server to cause denial of service by sending specially crafted HTML responses.

|
---|--- […]
Original post on hollo.social
hollo.social
December 20, 2025 at 5:16 AM
Is it just me, or is #npm's trusted publishing unnecessarily rigid? Only one workflow filename allowed per package. It's like they never imagined a project having multiple release branches or evolving CI structures. Moving from _build.yaml_ to _publish.yaml_ shouldn't be this annoying. 😩
December 19, 2025 at 12:21 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
Calling all #fediverse developers for help: I'm currently trying to implement a #reporting (#flag) feature for Hackers' Pub, an #ActivityPub-enabled community for software engineers. Is there a formal specification for how cross-instance reporting should work in ActivityPub? Or, is there any […]
Original post on hackers.pub
hackers.pub
December 19, 2025 at 10:29 AM
2025年が終わる前にHolloの新バージョンをリリースしないと…‼️
December 19, 2025 at 4:20 AM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
Just had the realization that my lost post could be summarized as:

"I’m a TCP person in a UDP world"

https://ploum.net/2025-12-15-communication-entertainment.html
How We Lost Communication to Entertainment
How We Lost Communication to Entertainment par Ploum - Lionel Dricot.
ploum.net
December 15, 2025 at 7:12 PM
Still stick with Pino? Give LogTape a try!
LogTape
Simple logging library with zero dependencies for Deno, Node.js, Bun, browsers, and edge functions
logtape.org
December 18, 2025 at 4:23 AM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
Mozilla has a new CEO which:

- Has been at Mozilla for less than a year
- Has no prior open source experience (but well in "fintech" and "real estate")
- Has a MBA (aka "brainworm diploma")
- Is all-in on AI

That’s exactly the kind of profile the whole community has been waiting for.
December 16, 2025 at 7:39 PM
As someone who's been mass-mass-publishing to JSR since its early days, this has been really frustrating. I even set up a local JSR server to debug it, only to find that the problem simply doesn't exist locally. At this point I'm out of ideas—hoping the JSR team can take a look at the production […]
December 16, 2025 at 4:45 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
We've been struggling with a JSR publishing issue for nearly two months now—`@fedify/cli` and `@fedify/testing` packages hang indefinitely during the server-side processing stage, blocking our releases. Strangely, the problem doesn't reproduce on a local JSR server at all.

We've opened a GitHub […]
Original post on hollo.social
hollo.social
December 16, 2025 at 4:39 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
Ok, hotels, flights, trains, and one extra family fun day were booked. See you next year at #fosdem2026 !

https://fosdem.org/2026/schedule/event/decentralised-badges-activitypub-badgefed/

#fosdem
FOSDEM 2026 - Decentralised Badges with BadgeFed: Implementing ActivityPub-based Credentials for Non Profits
fosdem.org
December 15, 2025 at 10:24 PM
LogTape 1.3.0 is out!

This release brings official middleware for Express, Fastify, Hono, and Koa with Morgan-compatible formats, plus Drizzle ORM integration for database query logging.

For SDK authors: the new `withCategoryPrefix()` lets you wrap internal library logs under your own […]
Original post on hollo.social
hollo.social
December 15, 2025 at 7:48 AM
明日、弟と2泊3日で東京に旅行に行くんだ。
December 14, 2025 at 12:01 PM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
Still validating CLI option relationships with `if` statements? Your type system can do it for you.
Stop writing if statements for your CLI flags
If you've built CLI tools, you've written code like this: if (opts.reporter === "junit" && !opts.outputFile) { throw new Error("--output-file is required for junit reporter"); } if (opts.reporter === "html" && !opts.outputFile) { throw new Error("--output-file is required for html reporter"); } if (opts.reporter === "console" && opts.outputFile) { console.warn("--output-file is ignored for console reporter"); } A few months ago, I wrote _Stop writing CLI validation. Parse it right the first time._ about parsing individual option values correctly. But it didn't cover the _relationships_ between options. In the code above, `--output-file` only makes sense when `--reporter` is `junit` or `html`. When it's `console`, the option shouldn't exist at all. We're using TypeScript. We have a powerful type system. And yet, here we are, writing runtime checks that the compiler can't help with. Every time we add a new reporter type, we need to remember to update these checks. Every time we refactor, we hope we didn't miss one. ## The state of TypeScript CLI parsers The old guard—Commander, yargs, minimist—were built before TypeScript became mainstream. They give you bags of strings and leave type safety as an exercise for the reader. But we've made progress. Modern TypeScript-first libraries like cmd-ts and Clipanion (the library powering Yarn Berry) take types seriously: // cmd-ts const app = command({ args: { reporter: option({ type: string, long: 'reporter' }), outputFile: option({ type: string, long: 'output-file' }), }, handler: (args) => { // args.reporter: string // args.outputFile: string }, }); // Clipanion class TestCommand extends Command { reporter = Option.String('--reporter'); outputFile = Option.String('--output-file'); } These libraries infer types for individual options. `--port` is a `number`. `--verbose` is a `boolean`. That's real progress. But here's what they can't do: express that `--output-file` is required _when_ `--reporter` is `junit`, and forbidden _when_ `--reporter` is `console`. The relationship between options isn't captured in the type system. So you end up writing validation code anyway: handler: (args) => { // Both cmd-ts and Clipanion need this if (args.reporter === "junit" && !args.outputFile) { throw new Error("--output-file required for junit"); } // args.outputFile is still string | undefined // TypeScript doesn't know it's definitely string when reporter is "junit" } Rust's clap and Python's Click have `requires` and `conflicts_with` attributes, but those are runtime checks too. They don't change the result type. If the parser configuration knows about option relationships, why doesn't that knowledge show up in the result type? ## Modeling relationships with `conditional()` Optique treats option relationships as a first-class concept. Here's the test reporter scenario: import { conditional, object } from "@optique/core/constructs"; import { option } from "@optique/core/primitives"; import { choice, string } from "@optique/core/valueparser"; import { run } from "@optique/run"; const parser = conditional( option("--reporter", choice(["console", "junit", "html"])), { console: object({}), junit: object({ outputFile: option("--output-file", string()), }), html: object({ outputFile: option("--output-file", string()), openBrowser: option("--open-browser"), }), } ); const [reporter, config] = run(parser); The `conditional()` combinator takes a discriminator option (`--reporter`) and a map of branches. Each branch defines what other options are valid for that discriminator value. TypeScript infers the result type automatically: type Result = | ["console", {}] | ["junit", { outputFile: string }] | ["html", { outputFile: string; openBrowser: boolean }]; When `reporter` is `"junit"`, `outputFile` is `string`—not `string | undefined`. The relationship is encoded in the type. Now your business logic gets real type safety: const [reporter, config] = run(parser); switch (reporter) { case "console": runWithConsoleOutput(); break; case "junit": // TypeScript knows config.outputFile is string writeJUnitReport(config.outputFile); break; case "html": // TypeScript knows config.outputFile and config.openBrowser exist writeHtmlReport(config.outputFile); if (config.openBrowser) openInBrowser(config.outputFile); break; } No validation code. No runtime checks. If you add a new reporter type and forget to handle it in the switch, the compiler tells you. ## A more complex example: database connections Test reporters are a nice example, but let's try something with more variation. Database connection strings: myapp --db=sqlite --file=./data.db myapp --db=postgres --host=localhost --port=5432 --user=admin myapp --db=mysql --host=localhost --port=3306 --user=root --ssl Each database type needs completely different options: * SQLite just needs a file path * PostgreSQL needs host, port, user, and optionally password * MySQL needs host, port, user, and has an SSL flag Here's how you model this: import { conditional, object } from "@optique/core/constructs"; import { withDefault, optional } from "@optique/core/modifiers"; import { option } from "@optique/core/primitives"; import { choice, string, integer } from "@optique/core/valueparser"; const dbParser = conditional( option("--db", choice(["sqlite", "postgres", "mysql"])), { sqlite: object({ file: option("--file", string()), }), postgres: object({ host: option("--host", string()), port: withDefault(option("--port", integer()), 5432), user: option("--user", string()), password: optional(option("--password", string())), }), mysql: object({ host: option("--host", string()), port: withDefault(option("--port", integer()), 3306), user: option("--user", string()), ssl: option("--ssl"), }), } ); The inferred type: type DbConfig = | ["sqlite", { file: string }] | ["postgres", { host: string; port: number; user: string; password?: string }] | ["mysql", { host: string; port: number; user: string; ssl: boolean }]; Notice the details: PostgreSQL defaults to port 5432, MySQL to 3306. PostgreSQL has an optional password, MySQL has an SSL flag. Each database type has exactly the options it needs—no more, no less. With this structure, writing `dbConfig.ssl` when the mode is `sqlite` isn't a runtime error—it's a compile-time impossibility. Try expressing this with `requires_if` attributes. You can't. The relationships are too rich. ## The pattern is everywhere Once you see it, you find this pattern in many CLI tools: **Authentication modes:** const authParser = conditional( option("--auth", choice(["none", "basic", "token", "oauth"])), { none: object({}), basic: object({ username: option("--username", string()), password: option("--password", string()), }), token: object({ token: option("--token", string()), }), oauth: object({ clientId: option("--client-id", string()), clientSecret: option("--client-secret", string()), tokenUrl: option("--token-url", url()), }), } ); **Deployment targets** , **output formats** , **connection protocols** —anywhere you have a mode selector that determines what other options are valid. ## Why `conditional()` exists Optique already has an `or()` combinator for mutually exclusive alternatives. Why do we need `conditional()`? The `or()` combinator distinguishes branches based on **structure** —which options are present. It works well for subcommands like `git commit` vs `git push`, where the arguments differ completely. But in the reporter example, the structure is identical: every branch has a `--reporter` flag. The difference lies in the flag's _value_ , not its presence. // This won't work as intended const parser = or( object({ reporter: option("--reporter", choice(["console"])) }), object({ reporter: option("--reporter", choice(["junit", "html"])), outputFile: option("--output-file", string()) }), ); When you pass `--reporter junit`, `or()` tries to pick a branch based on what options are present. Both branches have `--reporter`, so it can't distinguish them structurally. `conditional()` solves this by reading the discriminator's value first, then selecting the appropriate branch. It bridges the gap between structural parsing and value-based decisions. ## _The structure is the constraint_ Instead of parsing options into a loose type and then validating relationships, define a parser whose structure _is_ the constraint. Traditional approach | Optique approach ---|--- Parse → Validate → Use | Parse (with constraints) → Use Types and validation logic maintained separately | Types reflect the constraints Mismatches found at runtime | Mismatches found at compile time The parser definition becomes the single source of truth. Add a new reporter type? The parser definition changes, the inferred type changes, and the compiler shows you everywhere that needs updating. ## Try it If this resonates with a CLI you're building: * Documentation * Tutorial * `conditional()` reference * GitHub Next time you're about to write an `if` statement checking option relationships, ask: could the parser express this constraint instead? _The structure of your parser is the constraint._ You might not need that validation code at all.
hackers.pub
December 10, 2025 at 3:45 AM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
I'm probably not going to buy Steam Machine given I highly doubt it will be as upgradable as normal desktops, but I still hope it serve as a chance for a lot of people to try Linux and see how it's feasible for everyday use.

(If you want to see that today you can just install Bazzite)
December 13, 2025 at 1:19 PM
I'll be presenting @fedify at @fosdem 2026! My talk _Fedify: Building ActivityPub servers without the pain_ was accepted for the Social Web Devroom. See you in Brussels on January 31–February 1!
FOSDEM 2026 - Fedify: Building ActivityPub servers without the pain
fosdem.org
December 14, 2025 at 1:27 AM
Reposted by 洪 民憙 (Hong Minhee) :nonbinary:
ライブラリ作者のみなさん、ロギングどうしてますか? winston? Pino? debug? どれもしっくりこなくて、結局自分で作りました。

https://zenn.dev/hongminhee/articles/e7cdd584dc467a
ユーザーに迷惑をかけないロギングの設計
zenn.dev
December 13, 2025 at 1:53 AM