TIL: <form>에 .submit()을 하면 이벤트를 거치지 않고 검증도 안하고 냅다 제출이 된다. 이벤트를 거치게 만들려면 .requestSubmit()을 해야 된다
잇창명 EatChangmyeong💕🐱
@eatch@hackers.pub · 21 following · 22 followers
*Encounter the Wider World*
🏠
- eatch.dev
🦋
- @eatch.dev
🐘
- @EatChangmyeong@planet.moe
🐙
- @EatChangmyeong
📝₂
- blog.eatch.dev
📝₁
- eatchangmyeong.github.io
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 non-white maintainer, you deal with these assumptions constantly—people who feel entitled to your labor while casually othering you based on your name.
It chips away at your motivation. It makes you wonder why you bother.
https://github.com/dahlia/optique/issues/59#issuecomment-3678606022
제 지인 분이 GitHub 에서 인종차별적 코멘트를 받으셨습니다. GitHub 계정이 있으시면 신고 부탁드립니다. 영어가 어려우시더라도 LLM으로 신고글 써달라고 하면 잘 써줍니다. 신고는 단 시간 내에 많이 찍혀야 실제 보고로 올라가기 때문에 가능하신 분들은 꼭 신고 부탁드립니다.
여러분!!! 모바일에서 텍스트 편집을 할 때 나오는 저 '파란색 그거'를 뭐라고 하나요???
여러분!!! 모바일에서 텍스트 편집을 할 때 나오는 저 '파란색 그거'를 뭐라고 하나요???
해결했어요!
「Text Selection Handle」
루비(Ruby)의 홈페이지가 새 단장했다고 해서 들어가봤는데 꽤 예쁜데? https://www.ruby-lang.org/ko/
@gaeulbyul가을별 오 우연찮게 오늘 들어갈 일이 있었는데 일주일도 안된 따끈한 리뉴얼이었군요...
루비(Ruby)의 홈페이지가 새 단장했다고 해서 들어가봤는데 꽤 예쁜데? https://www.ruby-lang.org/ko/
ㅠㅠㅠㅠㅠㅠㅠㅠ
@eatch잇창명 EatChangmyeong💕🐱 텍스트 커서(text cursor) 내지는 캐럿(caret)이라고 부르는 걸로 아는데… 이걸 궁금해 하신 게 아니려나요?
@hongminhee洪 民憙 (Hong Minhee) 앗 저는 깜박이는 | 말고 그 밑에 나오는 💧 이게 궁금했던 거라서요.......🥺
여러분!!! 모바일에서 텍스트 편집을 할 때 나오는 저 '파란색 그거'를 뭐라고 하나요???
파이어폭스 버그인지는 모르겠는데 <noscript> 안에 깊숙이 들어가있는 <span>이 밖으로 빠져나온다
왜냐면 이 글을 재작성하고 있고 인터랙티브 부분이 복잡해서 타입 안정성은 웬만하면 챙기고 싶은데 타입스크립트도 안되고 ReScript도 안되고 굳이 Idris를 가져다 쓰는 건 오버킬같고...
많은 우여곡절이 있었지만 어쨌든 해냈습니다. 내일 배포해야지
When code is more complex than it needs to be, its under-engineered, not over
I was wondering when browsers started calling the UI "chrome" (it's not a Google thing!)
Amazingly, the Firefox (then Mozilla) commit that introduced the "chrome" tree into the source code dates back to Sep 4, 1998... which is also the same day Google was founded!
Edit: Netscape used the term much earlier though! Not as much in filenames, but in the actual source code it's all over the place.
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 well-documented material that explains how the major implementations handle it?
리디에서 마크다운 이벤트를 할 때마다 **이 markdown**인 줄 알고 당황하는 사람
왜요??????
아 DM으로 보냈어야 되는데 공개설정 잘못했다
🏃➡️
혹시 해커스펍에는 신고 기능이 없나요....?? 행동강령에서는 분명 있다고 하는데
문맥적으로 알아서 어떤 타입이어야 하는지 추론해주는 정적 분석기가 달린 언어는 없나
언제까지 (a:number, b:number) => a + b, (a:string, b:string) => a + b, <T>(a: T, b: T) => a + b 를 해줘야 하나고
그냥 대충 눈치껏 (a, b) => a + b 하면 'b 는 a 와 더할 수 있어야 하는 타입이고 a 는 무언가와 더할 수 있는 타입이구나' 하고 추론할 수 있는 분석기가 달린 언어가 필요함
갑자기 삘이 와서 블로그 리팩토링을 한참 했고 이제 각주에도 수식을 쓸 수 있다!!
어떻게 구현했길래 수식을 못 썼냐고요??
- (before)
.mdx파일에 별도 익스포트로 각주 내용을 썼었는데 왠지모르게 JSX 문법은 되지만 마크다운 문법은 안 되는 상황에 봉착... Astro가 별도 익스포트를 못 봐서import.global.meta로 불러오고 원래는 볼 일 없는AstroVNode타입을 써가면서 온몸비틀기로 구현 - (after) 모든 각주가 별도 파일(🤣).
import.global.meta는 아직 있지만 고치기 전보다 훨씬 깔끔해진 느낌
갑자기 삘이 와서 블로그 리팩토링을 한참 했고 이제 각주에도 수식을 쓸 수 있다!!
JS 프레임워크 성격테스트도 있네 😂
I'm Solid! You're the performance-obsessed perfectionist! Take the quiz to find your JS framework match! js-framework-quiz.vercel.app/result/solid
I'm Solid!
겟앰프드가 아직도 서비스 중이라고 해서 웹사이트를 들어가봤는데 파비콘을 보고 깜짝놀랐다
잇창명 EatChangmyeong💕🐱 shared the below article:
Stop writing if statements for your CLI flags
洪 民憙 (Hong Minhee) @hongminhee@hackers.pub
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.
🏃➡️
software gore
12月 6日 서울에서 開催되는 liftIO 2025에서 〈Optique: TypeScript에서 CLI 파서 컴비네이터를 만들어 보았다〉(假題)라는 主題로 發表를 하게 되었습니다. 아직 liftIO 2025 티켓은 팔고 있으니, 函數型 프로그래밍에 關心 있으신 분들의 많은 參與 바랍니다!
오늘 liftIO 2025에서 發表한 〈Optique: TypeScript의 타입 推論으로 CLI 有效性 檢査를 代替하기〉의 發表 資料를 共有합니다! 들어주신 모든 분들께 感謝 드립니다.
사실은 eatch.dev도 next.js인데 SSG로 올려서 그런지 React2Shell 뚫리는지 보니까 안되더라 (그래도 업데이트는 했습니다.)
지금 ‘React2Shell(리액트투쉘)’이라는 이름 하나로 술렁이고 있다. CVSS 10.0 등급이 부여된 신규 취약점 CVE-2025-55182가 공개되면서 전 세계 개발자와 보안전문가들은 “2025년판 Log4Shell”이라는 표현까지 사용하며 심각성을 경고
React/Next.js 쓰신다면 지금 당장 패치하세요. 19.0.1 / 19.1.2 / 19.2.1
2025년판 Log4Shell 이라고 합니다
https://www.dailysecu.com/news/articleView.html?idxno=203111
C++ rant만으로 2시간 분량을 채울 수 있다는 게 놀랍다 (CW: AI 삽화) https://youtu.be/7fGB-hjc2Gc
ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ pd6156.tistory.com/63
https://eatchangmyeong.github.io/2022/04/22/interest-driven-mu-recursive-functions.html 이 글을 재작성하고 싶은데 예시로 뭘 들어야 될지 모르겠다..... μ를 반드시 써야 되고 구현 난이도가 적당한 거였으면 좋겠는데 생각나는 게 너무 어려운 것밖에 없다
구글에서 이분 매칭 알고리즘을 검색하면 예를 들어서 https://blog.naver.com/ndb796/221240613074 이런 게 검색되고 코드도 나와서 참고할 수는 있는데... 아무리 찾아봐도 정확성 증명도 없고 이게 왜
LaTeX을 라텏으로 쓰는 사람
out of context svelte
out of context svelte
하스켈에서 다음과 같은 에러를 만날 경우에
withFile: resource busy (file is locked)
readFile 대신 readFile'을 써보셔요!
readFile은 lazy 버전이고readFile'은 strict 버전입니다!
System.IO 모듈 문서에 다음과 같은 설명이 있습니다.
경고:
readFile연산은 파일의 전체 내용을 모두 소비할 때까지 그 파일에 대해 부분적으로 닫힌(semi-closed) 핸들을 유지한다. 따라서 이전에readFile로 연 파일에 대해(writeFile등을 사용하여) 쓰기를 시도하면, 일반적으로isAlreadyInUseError오류와 함께 실패하게 된다.
🏃➡️
마크다운은 토씨랑 상성이 진짜 안맞아서 불편하다... *이것을 찾으셨나요?*에라고 쓰면 "*이것을 찾으셨나요?*에"가 되고 <em> 태그를 써야 제대로 적용이 된다
러스트 문서가 시전하는 이것을 찾으셨나요?에 큰 호감을 느끼고 있다
오늘 백준에서 2문제를 풀었는데 둘다 롱롱죽겠지를 당했다 (long long을 써야 되는데 int를 썼다는 뜻)
금감원에서도 토스 옵션건에 대해 경고를 함
@eatch잇창명 EatChangmyeong💕🐱 다른 서버에서 흘러 들어온 CW 걸린 글에 블러 처리는 해주는데, Hackers' Pub에서 CW 걸린 글을 쓰지는 못해요! (필요 없다고 생각해서 안 만들었는데… 필요할까요?)
@hongminhee洪 民憙 (Hong Minhee) 사실 문제의 그 글을 여기 쓰고 블스로 알피하려는 생각을 했었는데 CW 설정이 없어서 블스에 쓰고 여기로 공유했어요 😅 있으면 좋을 것 같아요
RP) 오 여기도 블라인드 구현이 있나보네 여기서 써서 올릴 때도 블라인드 설정이 가능한가요?
Explicit or potentially disturbing media
CSS IS AWESOMEㅠ (CW: 단순 자살 언급)
오 예상보다 빨리 나왔다
그와중에 99_999_999번부터 100_000_002번까지 1억번을 노린 듯한 예능제출이라는 게 웃기다
























