docs: 同步現行契約並保留逐字半形後備

This commit is contained in:
2026-10-02 19:29:33 +08:00
parent 0bef51973a
commit 024ad5da93
3 changed files with 113 additions and 71 deletions
+84 -53
View File
@@ -51,8 +51,9 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro
屬性);**所有視覺尺寸(margin/letter-spacing/寬度/懸掛位移)一律寫在
`juzhen.css`**。同一段 DOM 在 justify 與否、主題與否下都成立。此分離是擴充性
與跨瀏覽器調校的關鍵。
- **I3 依 `lang` 行為**:每個 pass 解析節點的有效 `lang`(最近祖先),行為與
CSS 皆以 `:lang()` 與 locale 政策驅動。全角式/開明式之別即由此決定。
- **I3 依 `lang` 行為**:JS 解析節點的有效 `lang` 與逐元素樣式政策,決定標點是否半形,
再以 `jz-half`/`jz-half-le` class 交由 CSS 渲染;CSS 不以 `:lang()` 另判寬度政策。
字型地域字形由呼叫端的 `lang` 與字型決定,`options.lang.default` 不會改寫 DOM 的 `lang`。
- **I4 跨瀏覽器一致**:間隙、擠壓等一致性關鍵層只用 `margin`/`letter-spacing`/
`inline-block`/`user-select` 等普遍原語。`text-spacing-trim`/
`hanging-punctuation`/`text-emphasis` 等僅作漸進增強,絕不作為唯一機制。**且當
@@ -61,12 +62,12 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro
`text-spacing-trim` 不設定),使三引擎行為收斂於同一路徑(見 §9.1)。
- **I5 justify 單份間距**:間隙與擠壓的結構設計,須保證每個標點在
`text-align: justify` 下只貢獻**一份**對齊伸縮量(見 §8)。
- **I6 冪等與可還原**:每個 pass 對其產物打標記,重跑跳過;提供 `revert` **乾淨移除聚珍注入之
全部結構**(`jz-*` 元素、class、`data-jz-*` 屬性;wrap→解包還原原內容、marker→移除、
splitoff→併回原元素),DOM 回到 pass 前狀態之**邏輯等價**態。「邏輯等價」而非逐字節相同之**唯一
差異**:`spacing` 規範化之贅餘作者空格不還原(見 I1)——故 `render→revert` 對含 CJK↔西文作者
空格之源,還原後語義一致、結構全清,唯該空格已正規化(此差異穩定、不隨 `render`/`revert` 反覆
累積)。`revert` 後 DOM 內無任何 `[data-jz]` 殘留(實測驗收成立)。
- **I6 冪等與可還原**:每個 pass 對產物打標記供定址,私有所有權憑證才決定還原權限。
`revert` 撤回本實例仍有效的產物(wrap→解包、splitoff→併回)及受控屬性,保留作者與
其他實例內容。沒有外部修改時,DOM 回到處理前的**邏輯等價**態;`spacing` 正規化的
贅餘作者空格不還原(見 I1),此差異不隨反覆 render/revert 累積。
外部克隆、替換、刪除後,按產物及來源完整性檢查安全回退,不復活過時文字;
不保證恢復歷史快照或清空所有 `[data-jz]`。DOM 標記本身不授予所有權。
- **I7 邊界感知**:跨樣式行內元素的鄰接(如 `<strong>是</strong>A`)須能識別並
正確處理——由核心 finder 統一提供,各 pass 不自行重複造輪。
- **I8 向下相容 v1(已退役)**:相容層曾保障 v1 消費端(build.sh 單檔串接、Nvim
@@ -76,7 +77,8 @@ CSS box、observer、瀏覽器量測,以及 TeX node/glue/penalty/macro
(見 §3.4),以減少全樹走訪次數;聚合不得犧牲 I6(冪等可還原)與逐功能開關。
- **I10 作用域與分塊**:作用範圍可由 `scope.include` 選擇器限定(opt-in);功能集
可逐塊覆寫(屬性或 `blocks` 映射),由 pass 在 `collect` 內以 per-node 閘解析
(見 §3.6)。CSS 視覺規則須與 JS 作用域同域收斂,不外溢。
(見 §3.6)。`scope.include` 只限定 JS 處理;結構樣式直接作用於 `jz-*`,原生間距
禁用層固定於 `.juzhen`。呼叫端負責讓容器標記與 JS 作用域對應。
---
@@ -105,6 +107,10 @@ jz.disconnect(); // 停止觀察
同批移出根的節點與已離根的舊記錄則跳過,不以 `isConnected` 代替作用域判斷。
搬入既有塊時,以同批移除來源、私有產物或已知 render 根辨識;先 revert 再 render
撤回舊祖先的語言/級別/功能效果。純新增塊直接 render,但插入子樹的快取先失效。
手動 `render`/`revert`/`rerender` 在最外層暫停 observer 前,先同步結算 `takeRecords()`
中的待交付變化,可能更新其他區塊;隨後才執行手動操作,局部回退仍最後生效。
結算舊記錄的錯誤由宿主微任務非同步報告,不成為手動 API 的同步例外;手動操作自己的
錯誤仍照常同步拋出。
**增量粒度契約(下游說明八)**:增量更新須以**塊**為粒度——換塊內容(`textContent`/
`innerHTML`)、增刪塊、改分類屬性,皆與全量重繪一致(差分複驗逐位元相同)。**不支持**對聚珍
@@ -165,6 +171,7 @@ interface BasePass {
interface CharifyPass extends BasePass {
kind: "charify";
prepare?: (ctx: RenderContext) => void;
collect(node: Text, ctx: RenderContext): WrapRequest[];
}
@@ -176,7 +183,8 @@ interface StandalonePass extends BasePass {
type Pass = CharifyPass | StandalonePass;
```
`CharifyPass` 只收集文本包裝請求;`StandalonePass` 自行執行功能所需的掃描或 layout。
`CharifyPass` 可先以 `prepare` 處理跨節點完整原子,再收集普通 Text 的包裝請求;
`StandalonePass` 自行執行功能所需的掃描或 layout。
兩類皆由同一註冊表管理,且均按 `level` 與功能閘判定是否生效。
`createJuzhen` 內建一個**有序、可開關**的 pass 註冊表。`render` 依 `order` 執行
@@ -247,8 +255,9 @@ Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費
- `scope.include`:**啟用選擇器**(opt-in 模式)。設定後,**僅處理命中該選擇器之
子樹**(如 `".juzhen, [data-juzhen]"`),其餘一概不動——此即「限定作用範圍」。
- `scope.avoid`:排除選擇器,與內建 avoid 合併。
- **CSS 同域**:`juzhen.css` 之視覺規則一律收斂於同一啟用選擇器(如 `.juzhen` 下),
與 JS 作用域一致,杜絕樣式外溢(呼應 I2、§9.1 之禁用層亦限於此域)。
- **CSS 作用域**:`scope.include` 不改寫 CSS。`jz-*` 結構樣式直接按元素/class 生效;
§9.1 的原生間距禁用層固定於 `.juzhen`。若 JS 用 `.prose` 啟用,呼叫端可用
`class="prose juzhen"` 對齊兩者;作者自行建立的 `jz-*` 也會匹配結構樣式。
**分塊功能選擇**——不同塊啟用不同功能集:
@@ -281,12 +290,11 @@ Nvim peek/Claude 模板等 v1 消費端之過渡期。**最後一批 v1 消費
- **scope.include**:若設啟用選擇器,僅走訪命中之子樹(opt-in,§3.6)。
- **featureEnabledFor(node, name)**:解析該節點之有效功能集(§3.6),供各 pass 之
`collect` per-node 閘用;結果以元素為鍵快取。
- **charify**:把符合條件之字元/字串包進指定 `jz-*` 元素(兩階段:先收集目標、
後變更,避免遍歷中改 DOM 失效)。
- **邊界感知 replace(I7)**:對「跨相鄰文本節點、可跨樣式行內元素」的邏輯字串
做正則 find,回呼決定如何包裝/插入標記。樣式行內元素(`strong/em/a/span/…`)
透明穿越;block/pill/avoid 中斷邏輯串。此即 v1 Pass A「剝離樣式收集邏輯文本
再反映射」之一般化,提升為可重用引擎。
- **文字走訪**:`eachTextNode` 提供受過濾的 Text,charify pass 收集 `WrapRequest`,
由 `core/pass.ts` 批次包裝;Finder 本身不提供 charify mutation API。
- **邊界與來源映射(I7)**:`collectRuns` 收集跨透明 Text 的邏輯字串及完整來源跨度,
`adjacentLogicalChar` 查詢完整鄰字。block/pill/avoid/isolate 等按分類中斷脈絡;
正則匹配與間隙回放由功能模組實作,不是 Finder 的泛用 replace/回呼插入 API。
- **完整 Unicode 原子**:`collectRuns` 將跨透明 Text 的 surrogate pair 先合成完整碼點,
再將 base 與至多一個 variation selector 合為原子;`map.parts` 保留所有來源跨度。
間隙、禁則與避孤字共用此映射,不在原子中間建間隙/斷點、不把 selector 另算實義字。
@@ -385,8 +393,9 @@ avoid」用組合入口 `isAvoided`(=自身或祖先,階段二歸一,免
(`{ cjk?, western?, biaodian? }`)**擴充**,`resolveRuleset(override)` 以**追加**語義合併
為最終規則集,置於 `ResolvedOptions.ruleset`。各 pass 由此取分類:
- `spacing`:`makeRules(ruleset)` 由 `cjk`/`ansAfter`/`ansBefore` **per-instance 編譯**
全部間隙正則(createJuzhen 各一次,非每 block);`ALNUM`/`ALPHA`/運算符/括號等結構性
- `spacing`:每次 spacing render 開始時,`makeRules(ruleset)` 按該實例的
`cjk`/`ansAfter`/`ansBefore` 編譯全部間隙正則,復用於本次各處理塊;
`ALNUM`/`ALPHA`/運算符/括號等結構性
ASCII 集為固定常數、不隨規則集變。
- `jiya`:`classifyBiaodian(ch, ruleset.biaodian)` 決定 charify 與 bd-* 子類。
- `jinze`:`anyCjk = RegExp(ruleset.cjk)` 決定詞元邊界之 CJK 判定。
@@ -409,21 +418,28 @@ avoid」用組合入口 `isAvoided`(=自身或祖先,階段二歸一,免
祖先鏈掃 `data-jz-style` 屬性/`stylePolicy` 選擇器命中,近祖先勝、同層屬性優先於選擇器)
> 逐 lang `policy` > 全域 `style` > 內建逐語言預設(`zh-Hant`/`zh-HK` → 全角、`zh-Hans` →
開明)。逐元素覆寫供「非段落/UI 元素用半角、段落用全角/開明」等需求(`data-jz-style` 屬性
值非法則忽略、回退 lang)。此純為「按元素選樣式」之路由,`banjiao` 半形渲染沿用 `halt`/
值非法時只忽略該信號,仍查同層選擇器與祖先;逐元素信號皆缺失才回退語言政策)。
此純為「按元素選樣式」之路由,`banjiao` 半形渲染沿用 `halt`/
`margin`(§6.3),無新增壓縮邏輯。
CSS 端對應以 `:lang()` 選擇器落實 lang 層政策(I2/I3);逐元素覆寫屬 JS 端 baseline 判定。
語言與逐元素政策均由 JS 判定並轉為半形 class;CSS 按 class 使用 `halt`/`margin` 渲染。
`lang.policy` 的鍵是 `zh-Hans`/`zh-Hant`/`ja`/`other` 四個分類,非任意完整語言標籤。
### 4.4 `dom.ts`——`jz-*` 元素工廠與還原
`createEl('jz-char', {class, attrs})`、`wrap(node, el)`、`splitAt`、`insertMarker`、
冪等標記(`data-jz="<pass>"`)、`revertPass(name, root)`(移除該 pass 之產物、
解包 `jz-*`、合併回文本節點並 `normalize()`)。
`createJz(ownedElements, tag, pass, kind, opts)` 建立元素、登記私有所有權,並寫入
`data-jz`/`data-jz-kind` 供定址。`setOwnedAttribute` 與 `revertOwnedAttribute`/
`revertOwnedAttributes` 以共享寫者堆疊管理受控屬性;外部修改或其他實例寫入不被無條件覆蓋。
`data-jz-kind` 區分產物還原方式:`marker`(純注入,移除)、`wrap`(包裝原內容,解包)、
**`splitoff`**(行內元素克隆切分之邊界詞元,見 `core/split.ts`/§6.4)。`revertPass` 分
**兩階段**處理:主迴圈逆序解包 marker/wrap,第二階段再把 splitoff 併回前兄弟(同標籤
原元素)——順序必要,否則 splitoff 之前兄弟可能仍是 wrap 殼(如 `jz-jinze`)而非原元素。
`revertPass(pass, root, ownedElements)` 先驗證本實例所有權與合法 pass/kind/tag 組合。
目前接受 `wrap`(包裝原內容)與 **`splitoff`**(行內元素克隆切分之邊界詞元);不提供
泛用 `marker` 還原。第一階段逆序解包 wrap,第二階段把 splitoff 併回同標籤前兄弟,
最後 normalize 受影響父節點。順序必要,否則 splitoff 前兄弟可能仍是 wrap 殼。
`recordUnwrappedParent`/`resolveUnwrappedParent` 記錄及解析合法產物的受控解包鏈,
供跨節點來源回退使用;重用空殼會使舊位置失效,重定向環不作為文字恢復依據。
包裝請求由 `core/pass.ts` 執行,安全行內切分位於 `core/split.ts`;本模組沒有
`createEl`/`wrap`/`splitAt`/`insertMarker` 等泛用變更函數。
---
@@ -584,7 +600,8 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd
`halt` 收的是字身寬度、由字型按字形正確定位(簡左/繁中/括號偏側皆對),故
不重疊。`font-feature-settings:"halt"` 各引擎一致(當初疑慮之不一致者為**自動**
的 `text-spacing-trim`/`chws`,非顯式 `halt`);缺 `halt` 之字型優雅降級為全形。
鄰接/行端之「收哪一字」之**決策**仍由 `blankSides` 邏輯定,渲染統一為 `halt`。
鄰接/行端之「收哪一字」之**決策**仍由 `blankSides` 邏輯定;預設以 `halt` 渲染,
缺半形特性的字型可採下述逐字 `margin` 後備。
- **句末標點集含全角句點 `.`(下游回饋)**:`BD_STOP` 為 `。.!?`——除表意句號 `。`
(U+3002)外,納入**全角句點 `.`**(U+FF0E)。實測 Noto CJK SC/TC 之 `halt`(GPOS
SinglePos 調 advance)皆覆蓋 `.`、挤壓收 0.5em,與 `。` 一致;`inkBias` 同步補 `.`
@@ -617,7 +634,9 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd
依**字身墨色中心**給出 `left|center|right|full`(簡體點號偏左、繁體居中;彎引號
`“”`/角括號 `「」`/尖括號 `〈〉` 墨強偏一側→開 right、閉 left;圓方書名括號
`()【】《》…` 近中→center),charify 時標為 `jz-ink-{left,center,right}`。消費端可
經 `jiya.bias` 逐 lang 逐字覆寫;配字工作流與量測工具 `tools/measure-bias.py` 見 README。
經 `jiya.bias` 逐語言分類、逐字覆寫;`full` 不套 margin 的 0.5em 盒寬,保留全形。
**此逐字字型相容方案仍是當前支援路徑**;配字工作流、JSON 範例與量測工具
`tools/measure-bias.py` 見 README,不屬已移除的負 letter-spacing/標點懸掛機制。
- **激活與粒度(I3/I10)**:最近有效 `data-jz-halfwidth="halt|margin"` 屬性透過
可繼承 CSS 變數決定模式;`render()` 在全域 margin 時自動掛於 root。可在任意祖先
逐區塊/逐語言指定並交替巢狀,切回 halt 會撤銷 margin 盒寬、內盒及偏移。bias
@@ -704,20 +723,24 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd
直覺:文本級=「字符級排版」(間隙、標點形態);段落級=「行/段級排版」(換行、行端、段末)。
**標記 API(下游)**:
- 預設推斷:塊級元素(`blockTags`:p/div/li/td…)→ **段落級(全功能)**。
- 級別由自身向上查最近有效信號,不依標籤名推斷;沒有任何信號時預設 **paragraph**。
- 顯式降級:`data-jz-level="text"` 於元素/祖先 → 該子樹**僅文本級**(段落級功能自動關);
`data-jz-level="paragraph"`(或不標)→ 全功能。
子元素可用 `data-jz-level="paragraph"` 重新允許段落級功能,但仍受全域功能開關、
逐元素功能閘與作用域限制。不標記的子元素繼承祖先信號,不會自動重新啟用全功能。
- 選項 `level?: { text?: string }`:選擇器一鍵標記(如 `level:{ text: "h1,h2,h3,button,.ui" }`)。
- 典型:標題/按鈕/UI 文字標 **text-level**(要間隙與標點半形、不要避孤字/避頭尾);
正文 `<p>` 預設全功能。
**實現**:
- 每個 pass 加元數據 `level: "text" | "paragraph"`。
- `finder.levelFor(el)`:查 `data-jz-level` 祖先 + 選項 `level.text` 選擇器 + 預設(塊→段落)。
- `finder.levelFor(el)`:從自身向上查;每層先取合法 `data-jz-level`,再查 `level.text`
選擇器,第一個有效信號勝出;皆無才回退 paragraph。非法屬性值繼續查找。
- gate:段落級 pass 對 text-level 元素跳過;文本級 pass 不受限。與現有 `blocks`/
`featureEnabledFor` 疊加(level=粗粒度語義組先判,blocks=細粒度逐功能後調)。
- **文本級功能須支持非塊/單元素**:`spacing` 現用 `eachBlock`,須確認 scope root 為 inline/
單元素時亦能處理(或放寬走訪),使單行標題等之中西間隙、標點半形生效。
- **文本級功能支持非塊/單元素**:`spacing` 在逐塊處理後,補做未被塊覆蓋的行內 root
及 `scope.include` 命中的行內子樹,避免重複處理;標點 charify 也可走訪行內 root。
**與本批關係**:分級已落地(pass 標 level、`levelFor`、`data-jz-level`/選項);`orphan`
等段落級 pass 只作用於段落級元素、跳過 text-level 者。`hanging` 未註冊為 pass。
@@ -739,7 +762,7 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd
| 機制 | 作用層 | 唯一職責 | 手段 | 狀態 |
|---|---|---|---|---|
| 禁則(§6.4 jinze) | **斷行層** | 把標點趕到正確的邊(點號/閉類→行末、開類→行首;連續標點不拆) | nowrap `jz-jinze` | 已實作 |
| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt) | 已實作(將併入行端模式) |
| 行端半形(lineedge 現有) | **渲染層·紧排** | 行首開類/行末閉類**收半形** | `jz-half-le`(halt/margin) | 已實作 |
| 標點懸掛 | **渲染層·凸出** | 行末點號/閉類**右凸**、行首開類**左凸**出版心 | ~~負 `margin`~~ | ❌ 已移除(機制級死路,見 6.5.6) |
| 垂懸字避免 | **斷行層** | 段末行 **CJK 實義字數 ≥ N** | nowrap `jz-orphan` | 已實作 |
@@ -761,7 +784,7 @@ class 約定以現有 CSS/pass 為準:標點採 `bd-open`/`bd-close`/`bd
- **保證(成功建立尾部綁定時,免測 layout)**:該整體不可斷 → 末行恒含之 ⇒ 末行實義字 ≥ N,任意寬度/字型/
瀏覽器成立。平時無害(末行本多字時不改變),僅末行將塌為 <N 字時「拉字下來」。
- **選項**:`orphan?: boolean | { chars?: number }`(feature gate;**預設開 chars=2**;per-block
經 `blocks`/`data-jz`;段落級,`data-jz-level="text"` 子樹跳過)。lang 無關。
經 `blocks`/`data-juzhen`/`data-juzhen-off`;段落級,`data-jz-level="text"` 子樹跳過)。lang 無關。
- **邊界**:avoid/text/`orphan` 關閉/`isSkipped` 區域清空先前計數,只綁其後的合法
段末區域;不足 N 字則不綁。完整碼點及 base+VS 只算一個字。pill 不計內部字,仍可
作完整外部單元參與綁定;不可切且含排除內容的容器不退化整綁。極窄容器(<N 字寬)
@@ -920,8 +943,9 @@ order `90 jiyaLineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過
2. `jz-char` 設 `display: inline-block`:使其成為 justify 的**單一原子項**,內部
不開伸縮 gap,標點與其字面留白同屬一項,對齊伸縮只落在**項與項之間** → 標點
只貢獻一份。
3. **開明式**下,`jz-inner` 以負 letter-spacing 擠去半形留白,該項本身即半形,
justify 一份 gap 即足。
3. **開明式**下,JS 為可壓縮標點加半形 class;預設由 `jz-inner` 的 OpenType `halt`
收 advance。缺半形特性的字型可用 `margin` 後備:0.5em 外盒搭配逐字 `jiya.bias`
偏側定位(§6.3)。原子內部不另開 justify 伸縮量;舊負 letter-spacing 方案不再使用。
4. **全角式**下,留白保留於原子項內,仍只算一份;以 `jz-jinze` 綁定確保不在留白
側額外開機會點。
@@ -949,8 +973,8 @@ order `90 jiyaLineEdge`,`revert` 清 `jz-half-le`。純版面、jsdom 跳過
- `text-indent: 0`——段落首行縮排傳入後,每原子內部首行各縮排一次,shrink-to-fit 寬膨脹為
`縮排量+字形`,標點附近冒出巨大間距。實測:歸零後段落首行縮排仍保持 2em,原子寬由 ~3em
回到 0.5em/1em。
- `hanging-punctuation: none`——同屬區塊容器首/末行機制,每原子各自懸掛自身標點。Chrome
未實作(測不出)、Safari 會觸發,且與規劃中之「標點懸掛」(§任務 6)相衝,故按機制一併歸零。
- `hanging-punctuation: none`——避免區塊首/末行機制在每個標點原子內重複套用。
當前管線沒有標點懸掛 pass;段落保留此屬性不代表 inline-block 內的受管標點可以懸掛。
**不歸零者**(實測不洩漏或本應逐字穿透):`letter-spacing`/`word-spacing`(原子邊界處理
一致、無翻倍,實測 delta 與基線同);`text-align`/`text-align-last`/`text-justify`
@@ -1013,10 +1037,10 @@ src/
core/
pass.ts 兩類 pass 之分組、排序與管線執行器
cross-atom.ts 跨 Text 標點原子搬移/私有來源記錄/回退
finder.ts 遍歷/avoid/charify/邊界感知與跨節點 run
finder.ts 遍歷/avoid/功能閘/鄰字與跨節點原子映射
unicode.ts 字元類 + 標點子類 + 規則正則表
locale.ts 有效 lang / 分類 / 全角開明政策
dom.ts jz-* 工廠 / ownership / wrap / revert
dom.ts jz-* 工廠 / 所有權與屬性寫者 / 還原及受控解包鏈
split.ts 禁則/垂懸字之行內元素邊界切分
typeset/
spacing.ts 中西間隙
@@ -1029,9 +1053,10 @@ src/
css/
juzhen.css 入口(@import 各 partial)
_normalize.css _spacing.css _jiya.css _jinze.css _orphan.css
test/ jsdom DOM 正確性測試
demo/ 瀏覽器 layout/justify/簡繁 目視驗證頁
build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts
test/ jsdom DOM 測試 + 原生 WKWebView Swift 宿主/runner
demo/ layout/justify/簡繁 目視頁 + e2e.html 自動斷言頁
build.mjs esbuild:ESM + IIFE + CSS
package.json npm build 串聯 build.mjs 與 tsc 聲明生成
```
`biaodian.ts`/`emphasis.ts`/`ruby.ts`/`hanging.ts` 及同名 CSS partial 均不存在;
@@ -1042,12 +1067,13 @@ build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts
## 11. 構建與發佈
- **源碼**:TypeScript(strict),模組化。
- **構建**:`build.mjs` 以 esbuild 產出
- **JS/CSS 構建**:`node build.mjs` 以 esbuild 產出
- `dist/juzhen.mjs`(ESM,供打包器/TS)
- `dist/juzhen.iife.js`(IIFE,全域 `Juzhen`,原生 `<script>` 直跑——保留
build.sh/Nvim peek 之單檔串接消費)
- `dist/juzhen.css`(合併之樣式)
- `tsc --emitDeclarationOnly` → `dist/index.d.ts`
- **完整構建**:`npm run build` 先執行 `node build.mjs`,再執行
`tsc --emitDeclarationOnly` → `dist/index.d.ts` 及各模組型別宣告。
- **發佈物**:`dist/` 提交入庫(消費端可直接取單檔/CSS)。
---
@@ -1056,10 +1082,15 @@ build.mjs esbuild:ESM + IIFE + CSS;tsc:d.ts
- **jsdom(自動)**:驗證 DOM 變換——包裝結構、`bd-*` class、邊界處理、冪等、
`revert` 還原、copy-clean(`textContent` 不被注入污染)、lang 政策分支。
- **瀏覽器(目視,demo 頁)**:layout 相關**無法**於 jsdom 驗證——justify 單份
間距、擠壓視覺量、行端折疊、inline-block 基線。demo 頁載 Noto CJK,簡繁
並列、可切 justify、附選取/複製測試區,於 Chrome/Safari/FF 目視核對。
- 文件明確標注哪些結論來自自動測試、哪些待瀏覽器目視。
- **原生 WKWebView(自動)**:`npm run test:wkwebview` 需 macOS、Xcode 與 GUI 會話;
runner 編譯 Swift 宿主並載入 `demo/e2e.html`,驗證真實行盒、幾何、計算樣式、
Selection 及 observer/layout 生命週期。此命令使用現有 `dist`,不自動構建;
修改源碼後應先 `npm run build`。jsdom 本身不提供真實版面量測。
- **跨引擎目視(demo 頁)**:檢查擠壓墨色、基線、字型適配、justify 和寬度變化等。
demo 頁簡繁並列、可切 justify、附選取/複製測試區;Chrome/Safari/Firefox 是
待逐環境執行的驗證路線,WKWebView 通過不代表三引擎目視或真實下游頁面已通過。
- 驗證記錄須綁定實際快照與環境,分別標示 DOM、原生幾何與目視證據;已知交付記錄
見 `UPDATES.md`,不能由測試入口存在推定當前驗收完成。
---