Release 0.1.10 — keyboard-complete board + tested/documented CJK

- The full create → sort → edit → archive loop now works without the mouse:
  Space / A / D check, archive, or delete a focused card; arrow keys tug a
  tile within or across lanes; Tab to a lane header and arrow-reorder the
  whole lane; new / rename lane are rebindable commands.
- IME composition is guarded on every keyboard surface (the editor, card
  submit, and the board's move keys), so a Chinese / Japanese / Korean
  candidate-confirm is never mistaken for a shortcut.
- Long-card collapse is width-aware for CJK (fullwidth chars count as two).
- New CJK.md documents the six CJK guarantees and how each is verified.
- README: keyboard + CJK highlights refreshed across all four languages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
chodaict 2026-06-27 12:00:46 +09:00
parent 5a1483968a
commit 46c8c89376
5 changed files with 416 additions and 17 deletions

111
CJK.md Normal file
View file

@ -0,0 +1,111 @@
# CJK support in tugtile
tugtile is built to be a first-class kanban for Chinese, Japanese, and Korean boards — not
latin-first with CJK bolted on. This document turns that claim into something you can check:
every guarantee below states **what it promises** and **how it is verified** — an automated test
where the logic is pure, a short manual repro where a live browser / IME is required.
The pure logic lives in the engine block (`plugin.src.js`, the `CORE` region: `homeKey`,
`displayWidth`, `parseFile`, `serializeFile`, …). It has no Obsidian dependency, so it runs in
plain Node and is unit-tested in `test/cjk.test.cjs`.
## The 6 guarantees
### 1. Round-trip fidelity for CJK content
**Promise:** A card whose body is Chinese / Japanese / Korean text survives `parse → serialize`
byte-for-byte. We never re-encode, normalise, or "clean up" your characters — `tile.raw` is the
exact markdown from your file and is what we write back.
**Verified by:** automated — `test/cjk.test.cjs` (`raw line preserved verbatim`,
`parse→serialize keeps the fullwidth checkbox line`, `round-trip idempotent`) and the broader
round-trip suites in `test/core.test.cjs` / `test/archive-home.test.cjs`, which exercise CJK
boards (`## 進行中`, `卡片內容`, `%%tg-home:進行中%%`).
### 2. Fullwidth / non-ASCII checkbox tolerance
**Promise:** The board parser does not assume the checkbox mark is ASCII. A card written
`- [] …` (fullwidth ) — or any single non-ASCII mark inside the `- [ ]` brackets — parses
without crashing, and the mark is captured and preserved verbatim.
**Verified by:** automated — `test/cjk.test.cjs`
(`parseFile tolerates fullwidth checkbox mark (no crash)`,
`fullwidth checkbox mark captured verbatim`). The parser's matcher is `/^- \[(.)\]/`, capturing
any single code point rather than a fixed `[ xX]` set.
### 3. Display-width awareness (long-card collapse)
**Promise:** The "this card is long, collapse it to its title" decision reasons in **visual
columns**, not UTF-16 code units. A CJK character occupies ~2 columns, so a 30-character Chinese
title (~60 columns wide) collapses at the same *visual* length as a 60-character latin title —
neither language is treated as "shorter" than it looks.
**How it works:** `displayWidth(str)` counts East-Asian wide / fullwidth code points as 2 and
everything else as 1, iterating by Unicode code point (so surrogate-pair / astral CJK characters
are measured once). The host collapse check is `displayWidth(meta.clean) > 60` — the threshold is
**60 columns**, deliberately *not* doubled: 60 latin chars = width 60 (identical to the previous
`.length > 60` behaviour), while ~30 CJK chars = width 60.
**Verified by:** automated — `test/cjk.test.cjs`. `displayWidth` cases: latin-only (`"hello"→5`),
CJK (`"理牌"→4`), mixed (`"a理b"→4`), fullwidth punctuation (`""→4`), empty (`→0`), kana /
hangul / ideographic-space, and a surrogate-pair char (`"𠀀"→2`, not 4). Boundary cases assert
latin behaviour is unchanged (60→not long, 61→long) and the CJK collapse point
(29 CJK = width 58 → not long; 31 CJK = width 62 → collapses).
### 4. Fullwidth-space tolerant lane matching
**Promise:** A lane named with a fullwidth ideographic space (U+3000, common when typing with an
IME) still matches its own archive-restore token. `homeKey()` collapses all whitespace — including
U+3000 — to a single ASCII space, on both the writing and the matching side, so an archived card
restores to the right lane.
**Verified by:** automated — `test/cjk.test.cjs` (`homeKey collapses fullwidth space U+3000`,
`homeKey fullwidth-space == ascii-space twin`) and the end-to-end archive restore in
`test/archive-restore-e2e.test.cjs`.
### 5. IME composition guard (keyboard surfaces)
**Promise:** While you are mid-composition with an IME — picking among Chinese/Japanese candidate
characters — pressing Enter or an arrow key confirms / navigates candidates and **never** fires a
tugtile shortcut. This is guarded consistently in **all three** keyboard surfaces:
- the full-screen **editor** (`editor-core.js` — `compositionstart`/`compositionend` gate the
re-highlight, and `Enter`/`Tab` check `!e.isComposing` / `keyCode !== 229`),
- **card submit** (`plugin.src.js` — the inline-editor Enter handler checks
`!(e.isComposing || e.keyCode === 229)`),
- the **board card keydown / arrow handler** (`plugin.src.js` — bails on
`e.isComposing || e.keyCode === 229` before any bare-key card action).
Because composition state is browser/IME-driven, this guarantee is verified by hand, not in Node.
**Manual repro:**
1. Open a board, focus a card's inline editor (or the full-screen editor), and switch your OS to a
Chinese or Japanese IME.
2. Type a syllable so candidate characters appear (composition is active), then press **Enter** to
confirm a candidate — the candidate is inserted; the card must **not** submit/close.
3. With a card focused on the board (not its input), start composing and press the **arrow keys**
candidate navigation should work and the card must **not** move between lanes. Releasing the IME
and pressing the same keys then behaves normally.
### 6. CJK line-wrap inside cards
**Promise:** A long Chinese / Japanese / Korean title wraps onto multiple lines *inside* the card
rather than overflowing or being clipped, so the full title stays readable on the board.
Wrapping is layout behaviour in a live browser, so it is verified by hand.
**Manual repro:**
1. Create a card with a long single-line CJK title (e.g. 40+ Han characters, no spaces).
2. On the board, confirm the title wraps within the card's width and every character is visible
(the card grows in height; text is not cut off horizontally).
3. Collapse/expand the card (guarantee 3) and confirm the collapsed title line behaves the same.
## Known limitation (honest disclosure)
**Table / row view truncates CJK at ~30 visible characters.** The compact row view caps each row's
text at `max-width: 60ch` (`styles.css`, the `.tugtile__row-text` rule) with an ellipsis. Because
`ch` is the width of the `0` glyph, a CJK character (~2× that width) consumes roughly two `ch`, so
CJK row text is visually truncated at about **30 characters** where latin truncates at ~60. This is
a **cosmetic** limit of the dense table view only — the full title is always intact in the card
view and on disk (round-trip fidelity, guarantee 1, is unaffected). It is documented here rather
than hidden; a future fix would switch that cap to a display-width-aware measure.
## Running the tests
```
node test/cjk.test.cjs # the CJK-specific engine tests
bash scripts/test.sh # full suite: syntax + i18n + builds + every test/*.cjs
```

View file

@ -32,12 +32,19 @@ whole table was easier than patching someone else's. Written from scratch (clean
- **Never lose a card** — built-in undo / redo, a backup before the first write, and
overwrite protection: if the file changes elsewhere, tugtile reloads instead of
clobbering it. (Backed by a data-safety test suite.)
- **Hands never leave the keyboard** — arrow keys tug a tile within or across lanes;
Space / A / D check, archive, or delete it; Tab to a lane header and arrow-reorder the
whole lane; new / rename lane are rebindable commands. Sort a whole board without
reaching for the mouse.
- **An editor that survives phones** — two-row toolbar, virtual-keyboard-aware layout on
iOS/iPad; tug lanes & tiles to reorder, dates/times, tags, stash (archive).
- **Your data stays Markdown** — human-readable, diff-friendly, portable. Board and
Table views over the same note, plus a raw-editor button when you want the text itself.
- **CJK-first, concretely** — re-highlighting waits while your IME is composing, so CJK
input is never interrupted; `#tags` accept CJK; sorting is locale-aware.
- **Your language isn't an edge case** — every keyboard surface (the editor, card submit,
*and* the board's move keys) pauses while your IME is composing, so a Chinese / Japanese
/ Korean candidate-confirm is never mistaken for a shortcut; card collapse is
width-aware; `#tags` accept CJK. The six CJK guarantees — and how each is verified —
live in [CJK.md](./CJK.md).
- **No network, no telemetry** — your boards never leave your vault.
### Install
@ -83,12 +90,17 @@ tugtile の前身は、obsidian-kanban のために書いた plugin の山plu
- **カードを失わない**:取り消し/やり直しを内蔵、最初の書き込み前にバックアップ、
ファイルが外部で変わったら上書きせず再読み込み。(データ安全テストで保証)
- **手はキーボードから離れない**矢印キーでタイルを同じ列隣の列へ動かし、SpaceAD
で完了・アーカイブ・削除、Tab で列見出しへ移って矢印キーで列ごと並べ替え。列の新規作成
/名前変更は割り当て可能なコマンド。ボード一枚、マウスに触れずに片づきます。
- **スマホで生き残るエディタ**2 段ツールバー、iOSiPad の仮想キーボードに追従する
レイアウト。列もタイルもつまんで並べ替え、日付/時刻、タグ、アーカイブ。
- **データはずっと Markdown**人間が読めて、diff しやすく、持ち運べます。同じノートを
ボード/表の 2 ビューで。テキストそのものを触りたいときは raw エディタボタンも。
- **CJK ファースト、具体的に**IME の変換中は再ハイライトを待機し、日本語入力を決して
中断しません。`#タグ` も CJK 対応、並べ替えはロケールを認識します。
- **あなたの言語は、例外じゃない**:すべてのキーボード操作(エディタ、カード送信、そして
ボードの移動キー)は IME の変換中は待機するので、日本語・中国語・韓国語の変換確定が
ショートカットと取り違えられることはありません。長いカードの折りたたみは文字幅を考慮し、
`#タグ` も CJK 対応。6 つの CJK 保証と、その検証方法は [CJK.md](./CJK.md) に。
- **ネットワークなし、テレメトリなし**:ボードが vault の外に出ることはありません。
### インストール
@ -134,13 +146,18 @@ tugtile은 원래 obsidian-kanban을 위해 쓴 플러그인들(플러그인의
- **카드를 잃지 않습니다**: 실행 취소/다시 실행 내장, 첫 쓰기 전 백업, 파일이 외부에서
바뀌면 덮어쓰지 않고 다시 읽기.(데이터 안전 테스트로 보장)
- **손이 키보드를 떠나지 않습니다**: 화살표 키로 타일을 같은 열이나 옆 열로 옮기고,
SpaceAD로 완료・보관・삭제, Tab으로 열 머리글로 이동해 화살표 키로 열 전체를 재정렬.
열 추가/이름 변경은 단축키를 지정할 수 있는 명령입니다. 보드 하나를 마우스 없이 정리합니다.
- **휴대폰에서 살아남는 에디터**: 2단 툴바, iOS/iPad 가상 키보드를 따라가는 레이아웃,
열과 타일을 집어서 정렬, 날짜/시간, 태그, 보관함.
- **데이터는 언제나 Markdown**: 사람이 읽을 수 있고, diff 친화적이며, 어디로든 가져갈 수
있습니다. 같은 노트를 보드/표 두 가지 뷰로, 텍스트를 직접 만지고 싶을 때는 raw 에디터
버튼도 있습니다.
- **CJK 우선, 구체적으로**: IME 조합 중에는 재하이라이트를 멈춰 한국어 입력이 끊기지
않습니다. `#태그`도 CJK 지원, 정렬은 로케일 인식 비교를 사용합니다.
- **여러분의 언어는 예외가 아닙니다**: 모든 키보드 입력(에디터, 카드 전송, 그리고 보드의
이동 키)이 IME 조합 중에는 대기하므로, 한국어・중국어・일본어의 변환 확정이 단축키로
잘못 인식되지 않습니다. 긴 카드 접기는 글자 너비를 고려하고, `#태그`도 CJK 지원. 여섯
가지 CJK 보증과 각각의 검증 방법은 [CJK.md](./CJK.md)에 있습니다.
- **네트워크 없음, 텔레메트리 없음**: 보드는 vault 밖으로 나가지 않습니다.
### 설치
@ -182,12 +199,16 @@ tugtile 的前身正是幫 obsidian-kanban 寫的一堆 pluginplugin 的 plug
- **絕不掉牌**:內建復原/重做、首次寫入前先備份、外部改檔時自動重載而非覆寫
(由資料安全測試套件守護)。
- **雙手永遠在鍵盤上**:方向鍵把一張牌拖到同列或隔壁列,`Space``A``D` 勾選/封存/
刪牌,`Tab` 到牌列標題再用方向鍵搬整條牌列;新增/改名牌列是可自綁熱鍵的指令。整桌牌
理完,不用碰滑鼠。
- **在手機上活得下來的編輯器**雙排工具列、iOSiPad 虛擬鍵盤自動避讓;牌列與牌都能
拖曳排序,加上日期/時間、標籤、收牌(牌庫)。
- **資料永遠是 Markdown**:人類可讀、好 diff、帶得走。同一份筆記有牌桌牌表兩種檢視
想直接碰文字時還有 raw 編輯器按鈕。
- **CJK 優先,而且講得出細節**IME 組字期間暫停重新上色,中文輸入絕不被打斷;`#標籤`
支援 CJK排序使用 locale 感知比較。
- **你的語言,不是 edge case**:每一個鍵盤入口(編輯器、送出、連同牌桌的搬牌鍵)都在
IME 組字期間讓位,中日韓的選字確認絕不被誤判成快捷鍵;長牌收合是寬度感知的;`#標籤`
支援 CJK。六項 CJK 保證與各自的驗證方式都寫在 [CJK.md](./CJK.md)。
- **不連網、零遙測**:你的牌桌永遠不會離開 vault。
### 安裝

281
main.js

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
{
"id": "tugtile",
"name": "tugtile",
"version": "0.1.8",
"version": "0.1.10",
"minAppVersion": "1.4.0",
"description": "A card table for your Markdown notes — tug tiles to reorder. Reads your existing kanban boards. CJK-friendly.",
"author": "CVER Inc.",

View file

@ -1,3 +1,3 @@
{
"0.1.8": "1.4.0"
"0.1.10": "1.4.0"
}