Skip to content

feat: enrich I Ching data layers and Legge coverage#4

Draft
pro-vi wants to merge 22 commits into
mainfrom
feat/data-enrichment
Draft

feat: enrich I Ching data layers and Legge coverage#4
pro-vi wants to merge 22 commits into
mainfrom
feat/data-enrichment

Conversation

@pro-vi

@pro-vi pro-vi commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

Adds the committed data-enrichment layer for Wings, trigram associations, Legge translations, and renderer/CLI surfaces that consume those fields. Local JSON acquisition inputs and the JSON -> TS acquisition tooling are ignored; the PR ships the generated TypeScript data as the reviewable source.

This branch now also adds the first language-preference scaffold: a persisted display language setting for simplified Chinese, traditional Chinese, and English. It exposes the control in CLI config and terminal settings, while deliberately leaving text rendering unchanged until we decide which surfaces should spend real estate on translated variants.

Key Changes

Data and Types

  • Adds typed data modules for 序卦, 雜卦, 說卦, trigram associations, and Legge English source text.
  • Backfills GUA with 卦辭 English, 小象 text, and Legge judgment/image/line data.
  • Extends public type surfaces for enriched hexagram, Wings, Legge, connection, and 說卦 derivation-context fields.
  • Adds a public DisplayLanguage type with canonical values zh-Hans, zh-Hant, and en.

Connections and Rendering

  • Adds buildConnections coverage for sequence, contrast-pair, and 說卦 citation overlays.
  • Surfaces enriched detail data in terminal dictionary/detail rendering and CLI/plain/JSON output.
  • Adds the gc commentary style across CLI/style parity paths.
  • Adds direct terminal navigation from a derived hexagram citation to the cited 說卦傳 ch.N reader: focus derived rows with tab, then press s for the chapter or enter for the related hexagram.
  • Enriches the 說卦 chapter reader with canonical text, project-authored working translation, derivation relevance, and trigram association tables for catalogue chapters.

Configuration

  • Persists a display language preference in user config, defaulting to zh-Hant to preserve current behavior.
  • Exposes the setting through iching config get/set/list language with canonical values zh-Hans, zh-Hant, and en.
  • Adds a terminal Settings row rendered as compact choices , , and EN.
  • Normalizes hand-edited config through typed parsing instead of spreading unchecked JSON into UserConfig.

Documentation and Repo Hygiene

  • Documents the data layers, provenance, and integrity expectations.
  • Keeps data-acquisition/ and scripts/data-acquisition/ ignored because they depend on local verified JSON pull artifacts.

Review Fixes

  • Reroutes documented Legge 雜卦 anomalies so hex 39 receives Kien text, hexes 49/50 receive the Ko/Ting couplet, and legitimate hex 41/51 mappings are preserved.
  • Removes the documented 62/64 gap now that Legge 雜卦 coverage is 64/64.
  • Tightens adversarial coverage so the hex 51 overwrite regression would fail tests.

QA Checklist

  • Full test suite passes: bun test --path-ignore-patterns='**/dist/**' -> 623 pass, 0 fail, 225630 expect() calls.
  • Typecheck passes: bun run typecheck exits 0.
  • Focused config/settings coverage passes: bun test packages/storage/src/__tests__/config-store.test.ts packages/storage/src/__tests__/schema-shape.test.ts apps/cli/src/__tests__/config-command.test.ts packages/terminal/src/__tests__/settings-scene.test.ts -> 30 pass, 0 fail, 107 expect() calls.
  • Focused 說卦/detail reader coverage passes: bun test packages/core/src/__tests__/shuogua.test.ts packages/terminal/src/__tests__/detail-renderer.test.ts packages/terminal/src/__tests__/detail-scene.test.ts packages/terminal/src/__tests__/shuogua-chapter-scene.test.ts apps/cli/src/__tests__/scene-factories.test.ts -> 57 pass, 0 fail.
  • Legge 雜卦 adversarial spot-check verified: hex 39 has Kien text, hexes 49/50 share Ko/Ting text, hex 41 keeps Sun/Yi, and hex 51 keeps Kan starts; Kan stops.
  • Bundle scope verified: bun run bundle emits publish/iching.js at ~1.40 MB, and rg inspection finds no acquisition-script markers.

Acceptance Evidence

Acceptance was rerun against this branch after reverting b25f882; the branch has since added the language-setting scaffold. The latest head commit is b2cdadb; the active rich-reader implementation commit is f751b90.

Claim Class Evidence
Data coverage and consumer completeness remain intact Automated bun test packages/core/src/__tests__/data-coverage.test.ts -> coverage includes Legge anomaly reroute and consumer-completeness assertions
Branch-wide regression suite remains green Automated bun test --path-ignore-patterns='**/dist/**' -> 623 pass, 0 fail, 225630 expect() calls
Type surface compiles Automated bun run typecheck exited 0
Language preference is persisted and exposed without changing text rendering yet Behavioral Focused config/settings tests verify default zh-Hant, CLI config get/set language, invalid-value rejection, alias normalization, and Settings row choices , , EN
Citation chapters are reachable from the detail view Behavioral Focused tests verify derived-row s emits openShuoguaChapter, routers construct ShuoGuaChapterScene, and the selected derivation op is preserved
說卦 chapter pages are no longer single-line stubs Behavioral Focused tests verify canonical text, working translation, derivation relevance, trigram table rows, clamping, scrolling render, and back navigation
Legge anomaly routing preserves the intended mappings Structural Spot-check output: 39 -> Kien, 41 -> Sun and Yi, 49/50 -> Ting ... Ko, 51 -> Kan starts; Kan stops.
Local acquisition scripts are excluded from runtime bundle Structural bun run bundle succeeded at ~1.40 MB; `rg -n "data-acquisition

No browser DOM visual ACs were present for this terminal/data/core change, so no screenshot evidence was required.

Notes

No Linear ticket detected for this branch.

pro-vi added 22 commits May 30, 2026 22:04
Plan covers 11 units across two tracks (zh canon + types + renderer +
CLI on Track A; Legge cleanup + integration on Track B).

data-acquisition/ holds verified pulls and intermediate scaffolding;
the structured JSON outputs feed U2/U3/U8/U10 backfills but only the
resulting data/ modules ship. Source pulls do not travel with the repo.
Extends Hexagram with optional gc, gcEn, yaoXiao, yaoXiaoEn, legge.
Extends TrigramInfo with optional assoc. Adds new interfaces:
LeggeHexagram, TrigramAssoc, XuGuaEntry, ZaGuaEntry, ShuoguaChapter,
ShuoguaCitation, CastConnections.

Style/QuoteStyle union extension deferred to U7 (lockstep with the CLI
consumer sites at format/reading.ts:31 and output/plain.ts:96 that do
g[style] indexed access). New optional fields are fully usable via
direct access (gua.gc, gua.legge, etc.) in U2-U6.
xugua.ts: XU_GUA — 64 序卦傳 entries in King Wen order, sourced from
ctext.org with zh.wikisource cross-check. Editorial notes on hex 1, 2,
30, 31 preserved from the verified pull's _meta. Provenance in
XU_GUA_META.

zagua.ts: ZA_GUA — 53 雜卦傳 entries preserving the canonical disordered
tail. ZA_GUA_BY_HEX is the 64-key reverse index (many-to-one for the
self-mirror pairs where two hexagrams share an entry — 乾/坤 at index 0).
Provenance in ZA_GUA_META.

index.ts: re-exports the new modules plus the U1 type additions
(XuGuaEntry, ZaGuaEntry, ShuoguaChapter, ShuoguaCitation,
CastConnections, LeggeHexagram, TrigramAssoc) that were missed in U1.

Tests: 19 integrity assertions — exact lengths, sequential indices,
verification anchors (opening cosmological premise on hex 1+2; 雜卦
opens with 乾/坤 pair), every hex 1..64 covered exactly once, reverse
index correctness for the 8 self-mirror pairs.

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
shuogua.ts: SHUOGUA — 11 canonical chapters per the standard
王弼/孔穎達 division (周易正義). TRIGRAM_ASSOC — 8-trigram structured
catalogue keyed by zh char. Sourced from ctext.org with 4 cross-checks.
Provenance in SHUOGUA_META.

Canonical fields (image, family, body, animal, direction, attribute,
extendedImages) normalized to pure canonical chars per 說卦 ch.7-10 —
phonological variants ((悅) on 兌) and alternate images (木 on 巽)
stripped from canonical columns; the alternates already live in
extendedImages where the canon places them. Optional editorial fields
(season, cosmologicalRole, other) carry English glosses; UI must label
non-canonical per the U4 plan.

Tests: 16 integrity assertions — chapter count + sequential numbers,
verification anchors (ch5 directional cycle, ch8 animals, ch11 extended
catalogue), every canonical column matches 說卦 verbatim (馬牛龍雞豕雉狗羊
animal column, 健順動入陷麗止說 attribute column, 父母三男三女 family
column via 索 scheme), 乾 extendedImages has the 14-image catalogue,
editorial fields present on 乾 spot-check.

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
trigrams.ts: wires TRIGRAM_ASSOC into each of the 8 TRIGRAMS entries via
the new .assoc field (by reference, not copy — same identity as
TRIGRAM_ASSOC[zhName]).

Adds TRIGRAM_ASSOC_EN — project-authored short English glosses for
family/body/animal/direction/attribute, keyed by trigram zh char. NOT
from Legge or Wilhelm/Baynes; these are project-supplied UI convenience
labels. UI surfaces MUST label them as "project gloss" per the U6/Q5
decision. Excludes extendedImages (no terse en for 14-item polysemous
catalogue — render in zh).

Tests (9): TRIGRAMS shape preserved (sym/n/img unchanged from baseline),
every .assoc points into TRIGRAM_ASSOC by reference, all canonical
fields populated, TRIGRAM_ASSOC_EN has 8 keys + 5 fields each, no
extendedImages leak, ASCII-only check for the en glosses (defense
against accidental zh paste), family-scheme spot check
(father/mother/eldest/middle/youngest).

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
derivation/connections.ts: pure function
  connections(cast: Pick<Cast, "primary">): CastConnections
that joins the numeric derivations with canonical text data — XU_GUA
(序卦 sequence narrative), ZA_GUA_BY_HEX (雜卦 contrast pair), and a
static DERIVATION_CITATIONS map (5 ops → 說卦 chapter). No I/O.

detail.ts: HexagramDetail extends with .connections; buildHexagramDetail
synthesizes a minimal {primary: kw} for the lookup. Existing fields
unchanged.

index.ts: re-exports connections.

Tests (14): overlay correctness on hex 1/3/29/30, all-hex coverage,
shuoguaCitation chapter validity, fresh-array per call (no shared
mutable state), HexagramDetail integration. The satisfying adversarial
check is the orbit-integrity invariant — for every non-tail hex with
a 2-element 雜卦 pair, the pair contains the geometric partner
(mirror, with polarity fallback for the 8 self-mirror hexagrams). All
56 non-tail hexagrams pass; the famously disordered final 8 (27, 28,
43, 44, 53, 54, 63, 64) are whitelisted by Set membership.

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
detail-model.ts: DerivedLink gains an `op: DerivedType` field so the
renderer can look up each row's 說卦 chapter citation. Populated in the
constructor for all 4 numeric derivations (nuclear/polarity/mirror/
diagonal).

detail-renderer.ts adds:
- VoiceNode interface + renderStackedVoices helper. Voice-driven,
  content-gated, fixed order (zh → modern → wilhelm → legge). Empty
  voices skip. Scaffolded for U10's full activation; used in U6 for the
  卦辭 path.
- Compact 說卦 trigram catalogue (family / body / animal / direction)
  inline under the trigram line. Always-on (U4 populates assoc for all
  8 trigrams).
- 卦辭 section at the top of the commentary block — gated on gua.gc
  (absent for legacy data until U8 backfills, so legacy renders
  unchanged).
- 小象傳 lines under each 爻辭 in the yao block — gated on
  gua.yaoXiao[i] (absent until U8).
- Relations block (renamed from "Derived"): each numeric derivation row
  carries a [說卦 ch.N] annotation pulled from U5's
  connections.shuoguaCitations; followed by 序卦 (sequence narrative)
  and 雜卦 (contrast pair) text-bearing rows from connections.xuGua /
  zaGuaPair. Both always present (U5 populates for every valid hex).

Tests (10): always-on additions visible (trigram catalogue, Relations
header, 說卦 chapter annotations, 序卦 with 故受之以 transition, 雜卦
with 剛/柔 contrast); gated sections correctly absent for legacy data
(no 卦辭 header, no 小象 lines); legacy commentary sections preserved
(大象傳, 彖傳, Image, Judgment, Wilhelm, 爻辭).

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
Extends Style to include "gc" (卦辭 root oracle). Hardcoded lists move
in lockstep, with one deliberate asymmetry:

- types.ts            Style += "gc"
- data/trigrams.ts    STYLES += "gc"; QUOTE_STYLES intentionally unchanged
- commands/hexagram.ts VALID_STYLES += "gc"
- output/plain.ts     formatCastPlain + formatHexagramPlain print 卦辭 row
                      when hex.gc present (gated on field, legacy unchanged)
- output/json.ts      castToJson + hexagramToJson conditionally include gc
                      via spread-when-defined

QUOTE_STYLES stays at 5 entries (dx/tu/en/te/w) — 卦辭 is the root
oracle, not a random-quotable commentary lineage, so the random-quote
selector never picks it. CLI's VALID_STYLES does include "gc" for
explicit --style gc lookup.

format/reading.ts:31 (g[style] dynamic access) and output/plain.ts:96
gain ?? "" fallback so optional Style keys can't push undefined.
commands/hexagram.ts errors cleanly when --style gc requested before U8
backfills (rather than emit an empty line).

Tests (5): STYLES parity (7 keys), QUOTE_STYLES asymmetry (5 keys, no
gc/no st), VALID_STYLES parity (6 keys = STYLES minus st), conditional
gc emission in hexagramToJson (absent when undefined, present when
populated).

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
Workflow pulled all 64 hexagrams' 卦辭 (root oracle) + 6 小象傳
entries (per-line Small Image commentary) from ctext.org with
zh.wikisource cross-check at hex 1/30/32/64. Adversarial verification
passed completeness + source-fidelity; structure lens caught one real
blocker — JSON used 无 (77 occurrences) while gua.ts uses 無 — fixed
by targeted normalization (无 → 無) with the canonical hex-25 name 无妄
explicitly protected. 74 swaps applied; remaining 无 occurrences are
all in 无妄 (verified at script time).

Backfill into packages/core/src/data/gua.ts: every entry gains
optional `gc` (root judgment text) and `yaoXiao[6]` (per-line
commentary) fields, inserted after `yaoEn` and before the closing `}`.
1797 → 2373 lines (+576). All existing fields verbatim — typecheck
green + 570/570 tests pass.

Tests (10): every hex has non-empty gc; every hex has yaoXiao with 6
non-empty entries; hex 1 anchors (元亨/利貞, 潛龍勿用陽在下也); hex 3
anchors (雖磐桓/志行正也); hex 25 name preserved as 无妄;
encoding-correctness scan asserts no stray 无 outside 无妄 across all
64 × (gc + 6 yaoXiao) text fields; hex 1/64 legacy fields unchanged.

U6's two "gated-section absent" tests flipped to assert the now-
shipped state (卦辭 header + 雖磐桓 line render for hex 1/3
respectively). U6's gating LOGIC (`if (gua.gc)` branches in the
renderer) is unchanged.

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
data/legge.ts: standalone Wings translations from Legge SBE XVI (1882,
PD). Three exports:
- LEGGE_XUGUA_EN: 64 keyed entries (parallels XU_GUA by hex number)
- LEGGE_ZAGUA_EN: Legge's 46 rhymed couplets (kept separate from
  ZA_GUA's 53-entry ctext source order — couplet structure differs)
- LEGGE_SHUOGUA_EN: Legge's 22-paragraph Appendix V (Legge's own
  numbering; SHUOGUA.chapters uses the 11-chapter 王弼/孔穎達 division)
- LEGGE_META: source + license

Backfill into the zh data modules:
- GUA[i].legge: { leggeName, judgment, image, lines[6 or 7] } per hex
- GUA[i].gcEn: English translation of 卦辭 (Legge's judgment line)
- XU_GUA[i].textEn: per-entry English (sourced from LEGGE_XUGUA_EN)
- ZA_GUA / SHUOGUA backfill skipped — the 46↔53 and 22↔11 mismatches
  need editorial mapping work outside this unit's scope

gua.ts: 2373 → 3271 lines (+898 from the 64 Legge embeds).

detail-renderer.ts: Legge voice activated as a dim sibling row under
the existing Image and Judgment sections, and under each yao English
translation. The U6 stacked-voice scaffolding for 卦辭 picks up the
newly-populated gcEn automatically.

Tests (17): all 64 hexagrams have populated .legge + .gcEn; hex 1 + 2
have 7-entry lines arrays (用九 / 用六 included); hex 3..64 have
exactly 6 entries; no Legge line starts with the rejected baharna
"In the Nth NINE/SIX" form; XU_GUA's 64 entries all have non-empty
textEn with zero residue of the U9-fixed artifacts (letter-fused,
paren-suffix, space-separated digits, "axe subjected"); LEGGE_ZAGUA_EN
has 46 entries opening with Khien/Khwan and ending free of nav chrome;
LEGGE_SHUOGUA_EN has 22 paragraphs.

Plan: docs/plans/2026-05-30-001-feat-data-enrichment-plan.md
docs/data-layers.md documents the canonical/editorial split, the
file-per-corpus convention, the optional-field discipline on Hexagram
and TrigramInfo, the stacked-voice rendering composition, and the
orbit-integrity invariant as the canonical sanity check for the
雜卦 data.

The orbit-integrity TEST (the geometric probe that mirror(h)/polarity
fallback matches the canonical pair for every non-tail hexagram) landed
in U5 inside connections.test.ts and is referenced from the doc rather
than duplicated as its own test file. The disordered tail (27, 28, 43,
44, 53, 54, 63, 64) is whitelisted per recognized textual feature; the
test fails on any non-tail mismatch.

This closes the data-enrichment plan
(docs/plans/2026-05-30-001-feat-data-enrichment-plan.md). All 11 units
shipped: U1 types, U2 序卦/雜卦, U3 說卦, U4 trigram enrichment, U5
connections overlay, U6 detail-renderer + scaffolding, U7 Style union
lockstep, U8 卦辭/小象 backfill, U9 Legge cleanup, U10 Legge module +
voice activation, U11 docs.
Three renames from the /gate /naming lens:

1. SHUOGUA → SHUO_GUA (and SHUOGUA_META → SHUO_GUA_META). Sibling
   consistency with XU_GUA / ZA_GUA — all three Ten Wings now split
   on the pinyin word boundary uniformly.

2. TRIGRAM_ASSOC_EN → TRIGRAM_ASSOC_GLOSS_EN. The "_GLOSS_EN" token
   telegraphs the project-authored editorial role at the identifier
   level rather than relying solely on the file-head comment.
   TrigramAssocEn interface renamed to TrigramAssocGloss to match.

3. connections (the function in derivation/connections.ts) →
   buildConnections. The bare noun-form read as "fetch existing
   connections" at call sites; the verb-form makes the construction
   intent visible. The import alias in detail.ts is dropped.

Touches: types.ts (1 doc comment), index.ts (3 re-exports),
data/shuogua.ts + trigrams.ts + legge.ts (declarations +
1 doc comment), derivation/connections.ts (function decl),
detail.ts (alias dropped), 3 test files (call sites + describe
labels), detail-renderer.ts (1 comment), docs/data-layers.md
(3 references). docs/plans/2026-05-30-001-... left as-is per
build-skill discipline (do not edit the plan body during execution).

Typecheck green, 587/587 tests pass. Pure rename — no behavior
change.
Addresses the "enrichment outran wiring" cluster surfaced by the
multi-persona review. Five of seven findings shared the same shape —
data declared/populated but never reached its consumer — so the fix is
one coherent wiring pass plus a coverage-invariant test.

P1 — Dead 雜卦 English path
  data/legge.ts adds LEGGE_ZAGUA_BY_HEX, a hex→Legge-couplet reverse
  index built from LEGGE_ZAGUA_EN. derivation/connections.ts joins it
  onto zaGuaPair.textEn via shallow-spread so the existing renderer
  branch (detail-renderer.ts:258) finally has English to surface.
  Coverage: 62 of 64 hexes get textEn; hexes 39 and 49 stay zh-only
  due to documented Legge typography anomalies, pinned by the
  coverage test.

P2 — Legge 7th line silently dropped on hex 1/2
  detail-renderer.ts adds a 用九/用六 section after the 6-line yao
  loop. Only fires when legge.lines.length > 6 (hex 1 & 2 only).

P2 — Asymmetric word-wrapping in the Relations block
  Both 序卦 and 雜卦 zh rows now go through wordWrap matching the
  English siblings; long entries (hex 30/31 cosmological preamble)
  no longer clip at terminal width.

P3 — yaoXiaoEn dead branch removed
  Legge does not translate per-line 小象 (his Appendix II isn't in
  our pull). The dead `if (gua.yaoXiaoEn ...)` branch is gone; the
  coverage test pins "yaoXiaoEn = 0/64 populated" as a deliberate
  gap so a future partial backfill is caught.

P3 — QuoteStyle precision drift
  types.ts: QuoteStyle = Exclude<Style, "st" | "gc"> (was just "st",
  letting gc leak in as a quotable style). trigrams.ts: QUOTE_STYLES
  retyped Style[] → QuoteStyle[]. format/reading.ts: drop the now-
  unnecessary `as QuoteStyle` cast.

P3 — Hex 1 xuGua doc misleading
  types.ts CastConnections.xuGua comment was "Absent for hex 1";
  in fact hex 1 and hex 2 share the canonical opening cosmological
  preamble. Comment corrected.

Design Q — gcEn duplicates legge.judgment
  Both fields carry the same text. Rather than derive one from the
  other (which would change the data layer), the coverage test pins
  their equality across all 64 hexagrams — silent divergence is
  caught.

Design Q — Legge Judgment rendered twice
  The Judgment section (gua.te = English of 彖傳, commentary on 卦辭)
  was emitting gua.legge.judgment as a Legge row, but legge.judgment
  IS the 卦辭 translation, already shown in the 卦辭 section. Removed
  from the Judgment section so each Legge sentence renders exactly
  once.

Deferred for data-owner adjudication
  The xugua textEn segmentation-alignment finding (zh hex-3 introduces
  屯; en hex-3 describes the post-屯 elaboration). The reviewer flagged
  this needs domain judgment — surfaced in the response, not auto-fixed.

NEW: __tests__/data-coverage.test.ts
  14 tests that catch the entire "enrichment outran wiring" pattern in
  one place. For every optional Hexagram/Wings field, asserts EITHER
  "populated for all 64" OR "documented gap, this is the exception."
  Plus consumer-completeness checks against buildConnections() so any
  future field added to the type but not wired to a consumer fails
  here.

Typecheck green, 601/601 tests pass (+14 from data-coverage).
Applies F1 + F2 from the /refactor lens findings.

F1 — detail-renderer.ts: extract pushWrapped + 8 per-section helpers

  buildContentLines went from a 270-line monolith coordinating 8
  concerns to a 25-line orchestrator that reads as a table of contents:

      renderGlyphPlaceholder
      renderHeader
      renderLineDiagram
      renderTrigramCatalogue
      [separator]
      renderCanonicalOracle      ← 卦辭
      renderCommentarySections   ← 大象/彖/Image/Judgment/Wilhelm
      renderYaoBlock             ← 爻辭 + en + legge + 小象 + 用九/用六
      renderRelationsBlock       ← derivations + 序卦 + 雜卦
      renderFooterTrails         ← locked pair + history

  pushWrapped(lines, textWidth, text, style) collapses the
  wordWrap+for+push pattern that appeared at ~14 sites — 3 lines → 1
  each. Pure mechanical extraction; behavior unchanged. The U6
  renderStackedVoices helper stays as-is (right shape for 卦辭's zh +
  multi-en stack); per-section Legge sibling rows stay on the Section
  type (different problem from voice stacking — single optional sibling
  vs full voice stack).

  Dropped two unused imports along the way (stringWidth, DerivedLink).

F2 — scripts/data-acquisition/: commit the data-import tooling

  The 5 Python generator/backfill scripts that produced the data layer
  (originally lived in /tmp, discarded after each run) are now
  permanent in scripts/data-acquisition/. The recipe IS the tooling
  — read one to understand the pattern, copy + adapt for the next
  data layer (文言, 繫辭, oracle bones, etc.).

      README.md                        — the recipe + how to use these
      lib.py                           — ts_str, ts_str_arr helpers
      gen-xugua-zagua.py               — U2 (zh canonical)
      gen-shuogua.py                   — U3 (zh canonical)
      backfill-guaci-xiaoxiang.py      — U8 (with 无→無 normalization,
                                        preserving 无妄 hex-25 name)
      cleanup-legge.py                 — U9 (post-pull structural fixes,
                                        idempotent in-place)
      backfill-legge.py                — U10 (generate legge.ts +
                                        backfill gua.ts + xugua.ts)

  Scripts read from data-acquisition/ (gitignored — produced by the
  pull workflows documented in docs/plans/) and write to
  packages/core/src/data/ (committed). Each script is self-contained,
  documented, idempotent where possible, and uses repo-relative paths
  via lib.py.

  Also anchors the data-acquisition/ gitignore rule to root (/data-
  acquisition/) so it no longer accidentally swallows
  scripts/data-acquisition/.

Typecheck green, 601/601 tests pass — pure structure refactor + new
tooling, no behavior change.
Two review findings:

P2 — LEGGE_ZAGUA_BY_HEX overwrites valid mappings (legge.ts:157)

  The naive `for (const hex of entry.pair) idx[hex] = entry.text` let the
  two documented Legge typography anomalies clobber legitimate
  neighbouring couplets. After this fix all 64 hexes get a couplet, and
  hex 41 / hex 51 retain their legitimate text.

  Inspection of the actual entry text settled the routing:

    pair=[41]    "Hard toil and danger have in Kien their home."
                 → single-hex content; route to hex 39 only.
    pair=[50,51] "Ting takes what's new; the old is left by Ko."
                 → couplet content for Ko + Ting; route to BOTH hex
                 49 AND hex 50. (Hex 50 had no legitimate Legge couplet
                 of its own; the anomaly is the only source.)

  The reroute is expressed as `Record<string, number[]>` so the
  single-hex and couplet cases share one shape: `targets = reroute ??
  entry.pair`. Iteration order no longer matters because the anomalous
  entries' writes never overlap with the legitimate entries' targets.

  Backfill script (backfill-legge.py) mirrors the fix so future
  regenerations produce the same legge.ts.

  data-coverage.test.ts updated: closes the documented gap (was 62/64
  with hex 39+49 as exceptions; now 64/64). Adds two adversarial
  assertions — hex 39/49 carry their rerouted Kien/Ko text, AND hex
  41/51 do NOT (proving the legitimate couplets are preserved).

P3 — cleanup-legge.py metadata not idempotent (cleanup-legge.py:72-77)

  The unconditional `extend(...)` of `postPullFixes` appended duplicate
  entries on every re-run, even when zero fixes were applied. Now gated
  on `fixes_applied` (the in-script flag list that ALREADY tracks
  whether each fix ran). Also dedupes against any pre-existing entries
  in `_meta.postPullFixes` so two runs that both applied the same fix
  don't create two identical records.

Typecheck green, 602/602 tests pass.
Add project-authored modern translations and derivation context for every 說卦 chapter, preserve the selected derivation op through navigation, and render chapter pages with canonical text, working translation, relevance notes, and trigram association tables.

Covers the reader path with core data, terminal scene, detail navigation, and CLI factory tests.
Show the selected derived hexagram's own 卦辭 and Legge judgment before the 說卦 source link, keeping source citations secondary.

Remove the project-authored derivation relevance layer from the 說卦 reader so chapter pages remain source-and-translation oriented.
Persist a display language preference with canonical values for simplified Chinese, traditional Chinese, and English. Expose the setting through the CLI config command and the terminal settings scene while keeping current text rendering unchanged.
@pro-vi pro-vi marked this pull request as draft June 2, 2026 06:17
@pro-vi pro-vi mentioned this pull request Jun 2, 2026
5 tasks
pro-vi added a commit that referenced this pull request Jun 20, 2026
…k gap (review #4)

Review finding #4 (P2). The dangling-paste flush re-armed a 500ms timer on every
feed() (and feed() cleared it on entry), so PASTE_TIMEOUT_MS measured the gap
BETWEEN chunks, not the paste's age. A real paste split by a >500ms stall (a
backgrounded process, a slow pipe) flushed a PARTIAL paste, left paste mode, and
the unterminated tail re-entered normal parsing — a trailing \r became a real
Enter and submitted the form mid-paste.

Make the flush an absolute age backstop: a separate pasteTimer armed ONCE at the
ESC[200~ start, never re-armed, and never cleared by feed() — so it bounds the
paste's total age (PASTE_FLUSH_MS, 10s), not the inter-chunk gap. A genuine paste
terminates via ESC[201~ or the 64KB byte cap long before it fires (terminals
deliver a paste to the pty in ms), so it never truncates a real paste; it only
recovers a never-terminated paste-start so the parser can't wedge (the byte cap
alone could otherwise swallow Ctrl-C). The timeout is injectable via a constructor
option (default 10s) purely so tests exercise the backstop without a 10s wait.

Tests: a >500ms mid-paste stall stays one paste with the CR as content, never a
stray Enter (mutation-verified); the backstop fires for a truly abandoned paste;
a no-content dangling start emits nothing. The two prior tests that pinned the
old 500ms per-chunk timing are rewritten for the absolute-age contract.

Gates: 1338 tests pass, typecheck clean, language-surface PASS, smoke 5/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant