Skip to content
Draft

V2 #151

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7287fb6
Implemnt v2
owjs3901 Mar 24, 2026
e3ee2be
Deploy version2
owjs3901 Mar 24, 2026
7e12516
Add korean testcase
owjs3901 Mar 24, 2026
eaab062
Impl math and Redesign rule
owjs3901 Mar 24, 2026
9d810b6
Fix testcase
owjs3901 Mar 26, 2026
3fcd26b
Fix testcase
owjs3901 Mar 26, 2026
628b0cd
Fix testcase
owjs3901 Mar 26, 2026
e68fb3e
Fix testcase
owjs3901 Mar 26, 2026
22fae16
Fix testcase
owjs3901 Mar 26, 2026
5aecf63
Fix testcase
owjs3901 Mar 26, 2026
03bf667
Impl pdf download
owjs3901 Mar 26, 2026
3eb6e78
Fix testcase
owjs3901 Mar 26, 2026
62fa234
Fix testcase
owjs3901 Mar 26, 2026
19597cb
Fix testcase
owjs3901 Mar 26, 2026
b71f933
Fix testcase
owjs3901 Mar 26, 2026
c2e5be9
Fix testcase
owjs3901 Mar 26, 2026
b866d2b
Fix testcase
owjs3901 Mar 26, 2026
faadddb
Fix testcase
owjs3901 Mar 26, 2026
30c31c9
Fix testcase
owjs3901 Mar 27, 2026
266d283
Fix testcase
owjs3901 Mar 27, 2026
f66e280
Fix testcase
owjs3901 Mar 27, 2026
8692102
Fix testcase
owjs3901 Mar 27, 2026
6e54b01
Fix testcase
owjs3901 Mar 27, 2026
d856b60
Fix testcase
owjs3901 Mar 27, 2026
dc38990
Fix testcase
owjs3901 Mar 27, 2026
cd55a3c
Fix testcase
owjs3901 Mar 27, 2026
17eeb97
Fix testcase
owjs3901 Mar 27, 2026
df624b5
Fix testcase
owjs3901 Mar 28, 2026
42ad966
Fix testcase
owjs3901 Mar 28, 2026
3995f6f
Fix test
owjs3901 Mar 28, 2026
d64640c
Fix test
owjs3901 Mar 28, 2026
b08302a
Add claude
owjs3901 Mar 28, 2026
36b3d5c
Add claude
owjs3901 Mar 28, 2026
41eea6e
Add claude
owjs3901 Mar 28, 2026
9135b72
Add claude
owjs3901 Mar 28, 2026
79f97ba
Add test
owjs3901 Mar 28, 2026
e8ce22d
Improve testcase
owjs3901 Mar 30, 2026
b0646fa
Add note
owjs3901 Mar 30, 2026
fbd90db
Add world testcase
owjs3901 Mar 30, 2026
3d94a5f
Add test
owjs3901 Mar 30, 2026
ffb64f2
Add test
owjs3901 Mar 31, 2026
f7cdf41
Add test
owjs3901 Mar 31, 2026
3c27eaf
Fix landing font
owjs3901 Mar 31, 2026
d7c3cd4
Support korean
owjs3901 Mar 31, 2026
ba09622
Support math
owjs3901 Mar 31, 2026
3eb91f0
Fix testcase
owjs3901 Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ codecov.*
**/.DS_Store
**/._.DS_Store
.claude
CLAUDE.md
.omc
152 changes: 79 additions & 73 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,106 @@
# PROJECT KNOWLEDGE BASE
# Braillify

**Generated:** 2026-01-13
**Commit:** b1d6b2b
**Branch:** main
한국어 텍스트를 한국 점자로 변환하는 라이브러리.

## OVERVIEW
## 프로젝트 구조

Korean Braille (Jeomja) translation library implementing 2024 Korean Braille Standard. Core in Rust with WASM (Node.js) and PyO3 (Python) bindings.
- `libs/braillify/` — Rust 핵심 변환 엔진
- `packages/node/` — Node.js WASM 바인딩
- `packages/python/` — Python 바인딩 (maturin)
- `apps/landing/` — Next.js 랜딩 페이지
- `test_cases/` — 점자 변환 테스트 케이스 (JSON)
- `docs/` — 2024 개정 한국 점자 규정 PDF
- `braillove-case-collector/` — 점자 내부표기 → 숫자/유니코드 변환기

## STRUCTURE
## 빌드 & 테스트

```
braillify/
├── libs/braillify/ # Core Rust library (see libs/braillify/AGENTS.md)
├── packages/
│ ├── node/ # WASM bindings via wasm-pack
│ └── python/ # PyO3 bindings via maturin
├── apps/landing/ # Next.js 16 docs site (@devup-ui)
├── test_cases/ # CSV rule test cases (61 files)
├── test_case_inputs/ # Input-only test CSVs
├── __tests__/ # Bun JS integration tests
├── py-test/ # Pytest Python tests
└── braillove-case-collector/ # Windows automation tool
```bash
bun install
cargo build --release -p braillify
bun test # 전체 테스트 (Rust + Bun + Python)
bun test test_cases/ # 테스트케이스 무결성 검증만
```

## WHERE TO LOOK
## 테스트 케이스 규칙

| Task | Location | Notes |
| ------------------------- | ------------------------------------------ | --------------------------------------- |
| Braille encoding logic | `libs/braillify/src/lib.rs` | Main `Encoder` struct, `encode()` |
| Korean character handling | `libs/braillify/src/korean_*.rs` | Choseong/Jungseong/Jongseong |
| Rule implementations | `libs/braillify/src/rule.rs`, `rule_en.rs` | Korean Braille Standard rules |
| Symbol/shortcut tables | `libs/braillify/src/*_shortcut.rs` | PHF static maps |
| CLI | `libs/braillify/src/cli.rs` | REPL mode, one-shot mode |
| Node.js API | `packages/node/src/lib.rs` | `encode`, `translateToUnicode` |
| Python API | `packages/python/src/lib.rs` | Same API + CLI entry |
| Landing page | `apps/landing/src/app/` | Next.js App Router |
| Test cases | `test_cases/*.csv` | Format: input,internal,expected,unicode |
### 파일 구조

## CONVENTIONS
- `test_cases/korean/rule_{N}.json` — 한글 점자 제N항
- `test_cases/korean/rule_{N}_b1.json` — 제N항 붙임 1
- `test_cases/math/math_{N}.json` — 수학 점자 제N항
- 근거: `docs/2024 개정 한국 점자 규정.pdf`

### Rust
### 엔트리 형식

- Edition 2024, resolver 3
- PHF macros for static lookup tables
- `Result<T, String>` for encoding errors (no custom error type)
- Feature flags: `cli` (default), `wasm`
- Tests inline with `#[cfg(test)]` modules
```json
{
"input": "입력 텍스트 (묵자 또는 LaTeX)",
"note": "설명 (선택, 동일 input이 여럿이거나 맥락 필요 시에만)",
"internal": "점자 내부표기",
"expected": "브라유셀 인덱스 연결 문자열",
"unicode": "점자 유니코드 문자열"
}
```

### TypeScript
### internal → expected/unicode 변환

- `strict: true`, `moduleResolution: bundler`
- `@/*` path alias to `./src/*`
- ESLint: `eslint-plugin-devup` recommended config
- Bun test with a preload plugin for WASM tests
`braillove-case-collector/converter.py`의 패턴을 따른다:

### Python
```
pattern: " a1b'k2l@cif/msp"e3h9o6r^djg>ntq,*5<-u8v.%[$+x!&;:4\0z7(_?w]#y)="
braille: ⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿
```

- Requires Python >= 3.13 (workspace), >= 3.8 (package)
- `uv` for workspace management
- `maturin` for building wheels
- CLI entry: `braillify = "braillify:cli"`
특수 매핑: `` ` ``→0, `{`→42, `}`→59, `~`→24, `|`→51

## ANTI-PATTERNS (THIS PROJECT)
`expected`는 각 문자의 인덱스를 문자열로 이어붙인 것, `unicode`는 대응하는 점자 유니코드 문자를 이어붙인 것이다.

- **Never suppress encoding errors** - propagate `Result<T, String>`
- **Never modify CSV test files without running full test suite** - `rule_map.json` must match
- **Avoid `as any` or `@ts-ignore`** in TypeScript
### 무결성 검증

## COMMANDS
`test_cases/testcase-integrity.test.ts`가 모든 엔트리의 internal → expected/unicode 일치를 검증한다. 대문자(수학 변수 A, B 등)를 포함한 internal은 기본 패턴 외이므로 skip된다.

```bash
# Install dependencies (runs uv sync, wasm-pack install, maturin install)
bun install
### 테스트 케이스 작성 원칙

# Build all packages
bun run build
1. **PDF가 유일한 근거** — `docs/2024 개정 한국 점자 규정.pdf`에 없는 예제를 만들지 않는다.
2. **PDF 순서 준수** — 기호 정의 → 해당 예제 순서로, PDF에 나온 순서 그대로 배치한다.
3. **기호 단독 엔트리 필수** — 각 기호는 단독 엔트리로 먼저 등록하고, 그 뒤에 해당 기호를 사용하는 예제가 온다.
4. **note는 필요할 때만** — 동일 input이 다른 의미로 쓰일 때, 또는 맥락이 필요할 때만 추가한다. input을 반복하는 note는 쓰지 않는다.
5. **소속 정확히** — 각 엔트리는 해당 항 파일에만 존재한다. 다른 항의 예제를 섞지 않는다.

# Run all tests (Rust coverage + Bun test + Pytest)
bun run test
### LaTeX 입력

# Build landing site (requires test_status.json from test run)
bun run build:landing
수학 수식은 LaTeX 형식의 input도 테스트한다. 기존 엔트리의 LaTeX 버전을 추가하는 방식이다:

# Dev server for landing
bun -F landing dev
- 형식: `$<LaTeX 수식>$` (앞에 `$`, 뒤에 `$`)
- 동일한 `internal`/`expected`/`unicode`를 공유
- `"note": "LaTeX"` 표기
- **기존 예제의 변환만** — 새로운 수식을 만들지 않는다

# Lint
bun run lint
bun run lint:fix
```json
{
"input": "$\\frac{3}{4}$",
"note": "LaTeX",
"internal": "#d/#c",
"expected": "6025129",
"unicode": "⠼⠙⠌⠉"
}
```

## NOTES
주요 LaTeX 변환:

| 수식 | LaTeX |
|------|-------|
| 분수 | `$\frac{분자}{분모}$` |
| 근호 | `$\sqrt{x}$`, `$\sqrt[n]{x}$` |
| 위첨자 | `$x^{2}$` |
| 아래첨자 | `$x_{n}$` |
| 부등호 | `$\neq$`, `$\geq$`, `$\leq$` |
| 절댓값 | `$\|x\|$` |
| 무한대 | `$\infty$` |
| 적분 | `$\int f(x)dx$` |
| 집합 | `$\cup$`, `$\cap$`, `$\subset$`, `$\emptyset$` |
| 논리 | `$\land$`, `$\lor$`, `$\forall$`, `$\exists$` |

### 대문자 수학 변수

- **Test output**: `cargo test test_by_testcase` generates `test_status.json` for landing page
- **WASM build**: `wasm-pack build --target bundler` in `packages/node`
- **Python wheel**: `maturin build --release` in `packages/python`
- **No CI workflows checked in** - build/test orchestrated via root `package.json`
- **Korean comments** in Rust code reference specific rule numbers (e.g., "제14항")
수학 점자에서 대문자 변수(A, B, N 등)를 사용하는 internal은 기본 64셀 패턴에 포함되지 않는다. 이런 엔트리는 `expected`/`unicode`가 빈 문자열이며, 무결성 테스트에서 자동으로 skip된다.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
Binary file not shown.
18 changes: 11 additions & 7 deletions apps/landing/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Box, globalCss, ThemeScript } from '@devup-ui/react'
import type { Metadata } from 'next'
import localFont from 'next/font/local'

import Footer from '@/components/Footer'
import Header from '@/components/Header'

const middleKoreanFont = localFont({
src: './fonts/NanumBarunGothic-YetHangul.woff2',
variable: '--font-middle-korean',
display: 'swap',
})

export const metadata: Metadata = {
title: 'Braillify',
description: '크로스플랫폼 한국어 점역 라이브러리',
Expand Down Expand Up @@ -82,25 +89,22 @@ export default function RootLayout({
return (
<html lang="ko" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `
<script>{`
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-KHQZ6Z4V')`,
}}
/>
})(window,document,'script','dataLayer','GTM-KHQZ6Z4V')`}</script>
<ThemeScript auto />
<link href="/favicon.svg" rel="shortcut icon" />
</head>
<body>
<body className={middleKoreanFont.variable}>
<noscript>
<iframe
height="0"
src="https://www.googletagmanager.com/ns.html?id=GTM-KHQZ6Z4V"
style={{ display: 'none', visibility: 'hidden' }}
title="Google Tag Manager"
width="0"
/>
</noscript>
Expand Down
20 changes: 18 additions & 2 deletions apps/landing/src/app/test-case/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ export default async function TestCasePage() {
const filterMap = createFilterMap(Object.keys(ruleMap))
let totalTest = 0
let totalFail = 0
let totalWorldTest = 0
let totalWorldFail = 0
let totalJeomsarangTest = 0
let totalJeomsarangFail = 0
const cases = Object.entries(ruleMap).map(([key, value]) => {
totalTest += testStatus[key][0]
totalFail += testStatus[key][1]
totalWorldTest += testStatus[key][2]
totalWorldFail += testStatus[key][3]
totalJeomsarangTest += testStatus[key][4]
totalJeomsarangFail += testStatus[key][5]

const isBut = value.title.includes('다만')

Expand All @@ -63,19 +71,23 @@ export default async function TestCasePage() {
</Text>
<TestCaseStat
fail={testStatus[key][1]}
jeomsarangFail={testStatus[key][5]}
jeomsarangTotal={testStatus[key][4]}
success={testStatus[key][0] - testStatus[key][1]}
total={testStatus[key][0]}
worldFail={testStatus[key][3]}
worldTotal={testStatus[key][2]}
/>
</Flex>
<Text color="$text" typography="body" wordBreak="keep-all">
{value.description}
</Text>
</VStack>
<TestCaseDisplayBoundary option="type" value="table">
<TestCaseTable results={testStatus[key][2]} />
<TestCaseTable results={testStatus[key][6]} />
</TestCaseDisplayBoundary>
<TestCaseDisplayBoundary option="type" value="list">
<TestCaseList results={testStatus[key][2]} />
<TestCaseList results={testStatus[key][6]} />
</TestCaseDisplayBoundary>
</TestCaseRuleContainer>
</TestCaseDisplayBoundary>
Expand All @@ -101,9 +113,13 @@ export default async function TestCasePage() {
</Text>
<TestCaseStat
fail={totalFail}
jeomsarangFail={totalJeomsarangFail}
jeomsarangTotal={totalJeomsarangTest}
showTotal
success={totalTest - totalFail}
total={totalTest}
worldFail={totalWorldFail}
worldTotal={totalWorldTest}
/>
</VStack>
<Text color="$text" typography="body" wordBreak="keep-all">
Expand Down
11 changes: 10 additions & 1 deletion apps/landing/src/components/test-case/TestCaseProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,16 @@ export function TestCaseProvider({
children: React.ReactNode
}) {
const [options, setOptions] = useState<TestCaseOptions>({
filters: ['korean'],
filters: [
'korean',
'math',
'science',
'music',
'western',
'foreign-language',
'ipa',
'corpus',
],
failedOnly: false,
type: 'list',
})
Expand Down
Loading
Loading