Text: the zh-hans Han order test replaces the skipping render test (#2039)

The CJK render test skipped wherever no Japanese face was installed, which
is every CI runner, and on a Mac it drew glyphs with or without the fix, so
it caught nothing. Replace it with a fallback-order test: fixture faces for
`Hiragino Sans` and `Hiragino Sans GB` show that a zh-hans Han lookup tries
the Chinese family first, that ja tries the Japanese one, and that a layout
gets Hiragino Sans GB. Synthetic fonts in an isolated child process keep it
independent of the host's fonts; with the Simplified list reverted it fails
on `[7]` against `[8, 7]`.

Delete `TEST_CJK_LOCALE`: the locale-mutating tests moved to an isolated
child process, so nothing used the lock and the test-only static failed
clippy's `-D warnings` gate.

Trim the documentation edits to the single docs/gaps.md gap-11 row: revert
the header and changelog edits in ROADMAP.md, docs/roadmap.md,
docs/localization-parity.md and docs/target-app-parity.md, and the gaps.md
header and changelog row, which conflict with other PRs.

Tests: cargo clippy -p effectcraft-text -p effectcraft-render --all-targets
-- -D warnings, cargo clippy --workspace --all-targets -- -D warnings,
cargo test -p effectcraft-text (79 lib), cargo test -p effectcraft-render
(264 passed, 1 ignored perf test), cargo fmt --all -- --check.
This commit is contained in:
midasdf
2026-10-29 14:47:22 +09:00
parent 667ebf229a
commit cbd67baf36
8 changed files with 65 additions and 52 deletions
+1 -7
View File
@@ -2,7 +2,7 @@
**Stage: alpha** · next: beta, ~30 points of "ready for real work" and ~350–620 h away
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-22 · **Change:** minor (Simplified Chinese Han text in compositions reaches the installed macOS Chinese faces instead of the Japanese face, with regression tests, #453; previous: minor, open-issue count, crash list and localization share re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026 (26.5.0.89)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (open-issue count, crash list and localization share re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026 (26.5.0.89)
EffectCraft aims to do what After Effects does, with the same panels, menus and behaviour, written
from scratch in Rust. This page is the one-page summary; the evidence is in
@@ -117,11 +117,6 @@ Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
## Progress log
- 2026-10-22: Text: Chinese Han text in compositions now draws with the installed Chinese face
instead of the Japanese one. macOS does not install the on-demand PingFang SC, so the Simplified
Han list names Hiragino Sans GB, Heiti SC, Songti SC and STSong (Heiti TC and Songti TC for
Traditional) before the fallback search can reach a Japanese face (#453). Japanese kana and kanji
are unchanged; a text-layer render test and a macOS fallback test cover both languages.
- 2026-10-16: Convert To Bezier Path turns a shape layer's Rectangle, Ellipse or Polystar
path into an editable Path 1 with the same geometry, as After Effects does; it used to
apply to mask RotoBezier only, so a parametric shape path could not be converted at all
@@ -160,7 +155,6 @@ Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Chinese Han text in compositions uses the installed macOS Chinese faces instead of the Japanese face; CJK text-layer and macOS fallback regression tests (#453) |
| 2026-10-12 | minor | Open-issue count (134 to 216), crash list and localization share (12% to 18%) updated to match docs/gaps.md |
| 2026-10-12 | patch | Roto Brush span validation after trimming; command regressions for both layer edges |
| 2026-10-10 | minor | Hours to ≈ 95% for each audience |
-21
View File
@@ -237,27 +237,6 @@ fn text_layer_renders_glyphs() {
assert!(covered > 300, "{covered}");
}
/// #453: a composition's Japanese (and Chinese) text layer draws its glyphs, not missing-glyph
/// boxes. Skipped where no CJK face is installed.
#[test]
fn text_layer_renders_cjk_glyphs() {
let cjk = effectcraft_text::fonts::resolve("Inter", "Regular").face;
if !effectcraft_text::fonts::face(effectcraft_text::fonts::fallback_for('日', cjk)).has_char('日') {
eprintln!("no Japanese face installed; skipping");
return;
}
for text in ["日本語", "中文"] {
let (mut p, cid, comp) = setup();
let mut l = build::layer(&mut p, &comp, "Text", LayerSource::Text, (200, 100), None);
l.props.prop_mut("text/sourceText").unwrap().value = Value::Text(Box::new(TextDoc { text: text.into(), size: 46.0, ..Default::default() }));
l.props.prop_mut("transform/position").unwrap().value = Value::Vec3([100.0, 70.0, 0.0]);
p.comp_mut(cid).unwrap().layers.push(l);
let img = render_frame(&p, cid, Tick::ZERO, 1.0);
let covered = img.data.iter().filter(|px| px[3] > 0.5).count();
assert!(covered > 200, "{text}: {covered} covered pixels");
}
}
#[test]
fn precomp_and_3d_render() {
let (mut p, cid, comp) = setup();
-6
View File
@@ -767,12 +767,6 @@ const GEORGIAN_FAMILIES: &[&str] = &["Sylfaen", ".SF Georgian", "SF Georgian", "
/// The CJK locale the host asked fallbacks to prefer, when it has one (see [`set_cjk_locale`]).
static REQUESTED_CJK_LOCALE: RwLock<Option<String>> = RwLock::new(None);
/// Serialises the tests that change the process-global CJK locale with the tests that read it
/// through [`fallback_for`] (the layout tests), which would otherwise cache the other order's
/// families.
#[cfg(test)]
pub(crate) static TEST_CJK_LOCALE: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// Ask CJK font fallbacks to prefer `tag`'s language for Han ideographs instead of the locale
/// environment, so a Chinese interface is drawn with a Chinese face on a machine whose locale is
/// Japanese (and the other way round). The interface sets this from its language setting; an empty
+56 -6
View File
@@ -1,5 +1,14 @@
use super::*;
/// Run `test` again in a child process with `variable` set. The fixture faces below go into the
/// process-global font database and its fallback caches, so only a process that runs one such test
/// on its own can use them.
#[cfg(not(target_arch = "wasm32"))]
fn run_in_child(variable: &str, test: &str) {
let output = std::process::Command::new(std::env::current_exe().unwrap()).args(["--exact", test, "--nocapture"]).env(variable, "1").output().unwrap();
assert!(output.status.success(), "{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
}
/// Concurrent language requests must neither choose another context's font nor reuse its
/// cached family order. Synthetic fonts in a child process keep this independent of the host
/// font installation and of other tests that exercise the process-global database/locale.
@@ -8,12 +17,7 @@ use super::*;
fn cjk_requests_keep_their_language_and_cache_entries() {
const CHILD: &str = "EFFECTCRAFT_TEST_ISOLATED_CJK";
if std::env::var_os(CHILD).is_none() {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args(["--exact", "fonts::tests::cjk_requests_keep_their_language_and_cache_entries", "--nocapture"])
.env(CHILD, "1")
.output()
.unwrap();
assert!(output.status.success(), "{}\n{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr));
run_in_child(CHILD, "fonts::tests::cjk_requests_keep_their_language_and_cache_entries");
return;
}
let families = [("ja", "Yu Gothic"), ("zh-hans", "Microsoft YaHei"), ("zh-hant", "Microsoft JhengHei"), ("ko", "Malgun Gothic")];
@@ -71,6 +75,52 @@ fn cjk_requests_keep_their_language_and_cache_entries() {
}
}
/// #453: with a Chinese interface, a Han ideograph that both a Chinese and a Japanese face cover
/// must reach the installed Simplified Chinese family first, or Chinese text in a composition is
/// drawn with Japanese glyph forms. Fixture faces named after the macOS families keep this
/// independent of the host's fonts and of rendering. Before the fix the Simplified list named none
/// of the macOS faces a stock install has, so the walk below reached the Japanese face - the
/// regression this guards.
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn zh_hans_han_fallback_puts_the_chinese_family_first() {
const CHILD: &str = "EFFECTCRAFT_TEST_ZH_HANS_ORDER";
if std::env::var_os(CHILD).is_none() {
run_in_child(CHILD, "fonts::tests::zh_hans_han_fallback_puts_the_chinese_family_first");
return;
}
let mut ids = Vec::new();
{
let mut d = db().write().unwrap();
d.scanned = true; // This test process uses only bundled and synthetic fonts.
// The Japanese face is added first, so scan order cannot be what picks the Chinese one.
for family in ["Hiragino Sans", "Hiragino Sans GB"] {
let mut data = test_font(family);
let table = data[12..12 + 8 * 16].as_chunks::<16>().0.iter().find(|t| &t[..4] == b"cmap").unwrap();
let cmap = u32::from_be_bytes(table[8..12].try_into().unwrap()) as usize + 12;
// Map 页 to the fixture's triangle glyph instead of its private-use range.
for (offset, value) in [(16, '页' as u16), (24, '页' as u16), (30, 2u16.wrapping_sub('页' as u16))] {
data[cmap + offset..cmap + offset + 2].copy_from_slice(&value.to_be_bytes());
}
let data = Arc::new(data);
let names = read_faces_bytes(&data).into_iter().next().unwrap();
ids.push(push(&mut d, info_from(names, FaceData::Shared(data), "test")));
}
}
let &[japanese, chinese] = ids.as_slice() else { panic!("two fixture faces expected: {ids:?}") };
let base = resolve("Inter", "Regular").face;
assert!(face(japanese).has_char('页') && face(chinese).has_char('页'));
// The fallback list itself, before any coverage check: the Chinese family comes first for
// zh-hans and second for ja, whether or not either face can draw the probe.
let zh_hans: Vec<FaceId> = family_faces(FallbackScript::Han, &face(base), CjkLocale::Simplified).to_vec();
assert_eq!(zh_hans, [chinese, japanese], "zh-hans tries the Chinese family first");
let ja: Vec<FaceId> = family_faces(FallbackScript::Han, &face(base), CjkLocale::Japanese).to_vec();
assert_eq!(ja, [japanese, chinese], "ja keeps the Japanese family first");
// ...and the face a layout actually gets for the shared ideograph.
assert_eq!(face(fallback_for_locale('页', base, "zh-hans")).info.family, "Hiragino Sans GB");
assert_eq!(face(fallback_for_locale('页', base, "ja")).info.family, "Hiragino Sans");
}
#[test]
fn extra_font_dirs_come_from_a_path_list() {
assert!(extra_dirs(None).is_empty());
+1 -2
View File
@@ -1,6 +1,6 @@
# Where EffectCraft falls short
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-22 · **Change:** minor (gap 11: the Chinese Han fallback reaches the installed macOS Chinese faces, #453; previous: minor, path-editing layer bounds, #1220; issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (path-editing layer bounds, #1220; issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
An honest assessment of how far EffectCraft is from being a real replacement for After Effects,
and the work that closes the gap. This document is meant for contributors and agents choosing
@@ -335,7 +335,6 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Gap 11: the Chinese Han fallback reaches the installed macOS Chinese faces, so Chinese text in compositions no longer draws Japanese glyph forms (#453) |
| 2026-10-16 | minor | Gap 8: #932 closed (Convert To Bezier Path converts a shape layer's parametric path, not only mask RotoBezier) |
| 2026-10-13 | patch | G2: layer bounds no longer compete with editable shape/mask path points (#1220) |
| 2026-10-12 | minor | Audited against `upstream/main` 470ea7a3: open-issue count and crash list, closed issues removed from rows 1, 3, 5, 6, 8–12, 15–17, 22–24, G8 and G9 (#759 `#include`), localization catalogs recounted (≈ 12% to ≈ 18%); rows 7, 14 and 21 left to open PRs #1102, #792 and #1098 |
+2 -3
View File
@@ -1,6 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-23 · **Change:** minor (Georgian: menus, panels and effect names; missing strings show in English; `missing_translations_report` lists them; previous: minor, the Chinese Han fallback reaches the installed Chinese faces, so Chinese text in compositions draws Chinese glyph forms, #453; previous: minor, catalogs recounted at `upstream/main` 470ea7a3: menus in 10 languages, panels in 6, Hangul probe (#599)) · **Target:** Adobe After Effects 2026
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-23 · **Change:** minor (Georgian: menus, panels and effect names; missing strings show in English; `missing_translations_report` lists them) · **Target:** Adobe After Effects 2026
How much of EffectCraft's interface is translated, language by language, compared with After
Effects. [gaps.md](gaps.md) gap 16 and workstream G9 hold the work;
@@ -71,7 +71,7 @@ the true percentages are slightly lower.
| Language | Code | UI strings translated | Dialogs / tooltips / help | Script support | Native review | Status | To `full` |
|---|---|---|---|---|---|---|---|
| English | `en` | ≈ 3,900 (100%, source) | All; docs in English | — | yes | full | — |
| Simplified Chinese | `zh-hans` | 1,130 (≈ 29%): menus 624 rows, panels 506 | Panels and dialogs partly; no help, effect names translated (306) and their parameter names not; Home screen literals unwrapped (#574, closed) | System CJK fallback; Han follows the UI language and now reaches the installed Chinese faces (#453); IME in text layers | no | partial | 6–9 h + review |
| Simplified Chinese | `zh-hans` | 1,130 (≈ 29%): menus 624 rows, panels 506 | Panels and dialogs partly; no help, effect names translated (306) and their parameter names not; Home screen literals unwrapped (#574, closed) | System CJK fallback; Han follows the UI language; CJK text in comps renders wrongly (#453); IME in text layers | no | partial | 6–9 h + review |
| Spanish | `es` | 624 (≈ 16%) | Menus only (panel catalog in open PR #996) | Latin, fine | no | menus only | 7–10 h + review |
| Hindi | `hi` | 0 | — | UI: Devanagari fallback font but no shaping; text layers shape Devanagari | no | none (After Effects: none) | 9–13 h + shaping in the UI |
| Arabic | `ar` | 0 | — | UI: Arabic fallback font but no shaping and no RTL layout; text layers: bidi + shaping | no | none (After Effects: none) | 9–13 h + 15–25 h RTL UI |
@@ -111,7 +111,6 @@ can give; only Georgian, contributed by a native speaker (#1843), has had it.
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Chinese text in compositions follows the fixed Han fallback: installed Chinese faces are tried before the Japanese face (#453) |
| 2026-10-12 | minor | Recounted: Russian, French, German and Czech menus (#873, #987), Brazilian Portuguese and Czech panels (#830, #987), Hangul probe (#599); overall ≈ 12% to ≈ 18%; Korean and Italian still absent |
| 2026-10-14 | minor | Georgian (`ka`): menus 628, panels 886 (191 Georgian-only, in `GEORGIAN_PANELS`), effect names and categories in `effects.rs` |
| 2026-10-10 | major | First localization parity document: catalog rows counted for all six languages, the twelve-language table, After Effects' 11 shipped languages from its bundle |
+2 -3
View File
@@ -1,6 +1,6 @@
# Roadmap detail
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (the Chinese Han fallback reaches the installed Chinese faces, #453; previous: major, new file: milestones moved from ROADMAP.md, current focus and next steps with Opus 5.5 estimates) · **Target:** Adobe After Effects 2026
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (new file: milestones moved from ROADMAP.md, current focus and next steps with Opus 5.5 estimates) · **Target:** Adobe After Effects 2026
Forward-looking detail behind the [ROADMAP](../ROADMAP.md). The work list itself, one gap at a
time, is [gaps.md](gaps.md); the numbers are in [target-app-parity.md](target-app-parity.md).
@@ -31,7 +31,7 @@ none blocks the workflow.
|---|---|---|---|
| 1. Set up: new comp, import footage, stills, PSD/AI and audio, arrange and trim layers | yes | `file.import` and image-sequence tests, `ui_drag_drop`, `ui_project_*`; rough: a still imports as a sequence (#431), an MP3 layer can go silent (#324), multi-file drops (#552) | 2–4 |
| 2. Animate: keyframes, easing in the Graph Editor, motion paths, parenting | yes | 168 live samples match After Effects 26.3 ([fidelity/](fidelity/README.md)); `ui_keyframes`; rough: Graph Editor jitter and clipped buttons (#456, #457), negative rotation drag (#569) | 3–6 |
| 3. Motion graphics: text layers with animators, shape layers with operators | yes | `ui_text_edit`, `ui_timeline_shapes`, text and shape engine tests; rough: paragraph alignment inverted (#316), Chinese text in comps follows the Han fallback (#453 fixed) | 3–6 |
| 3. Motion graphics: text layers with animators, shape layers with operators | yes | `ui_text_edit`, `ui_timeline_shapes`, text and shape engine tests; rough: paragraph alignment inverted (#316), CJK text in comps (#453) | 3–6 |
| 4. Composite: masks, track mattes, blend modes, keying and effects | yes | Compositor and mask tests, 306 effects with CPU/GPU agreement; rough: mask bugs (#510, #513, #527), 32-bpc blend differences (#494), effect bugs (#580 crash, #514) | 6–12 |
| 5. Preview with audio (RAM preview, cache) | yes | `ui_ram_preview`, `ui_live_preview`; rough: stutter on cached frames (#595, #304), late audio (#554, #504) | 10–20 |
| 6. Save, reopen and deliver: `.ecproj`, auto-save, Render Queue to H.264 / ProRes / sequences | yes | Versioned-schema reopen tests, crash recovery, render-queue and encoder tests; rough: the macOS default output folder is `/`, so a render fails until an output path is chosen (#446), ProRes 4444 colour tag (#340), projects don't relink when moved | 1–3 |
@@ -83,6 +83,5 @@ To beta (≈ 75% ready and `.aep` opening): **≈ 350–620 h**. To full parity:
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | The Chinese Han fallback reaches the installed Chinese faces (#453) |
| 2026-10-10 | minor | Alpha gate: six core workflows checked; all pass, stage stays alpha |
| 2026-10-10 | major | New file: milestones from ROADMAP.md, current focus, next steps with estimates |
+3 -4
View File
@@ -1,6 +1,6 @@
# Parity with After Effects
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-22 · **Change:** minor (the Chinese Han fallback reaches the installed macOS Chinese faces, so Chinese text in compositions no longer draws Japanese glyph forms, #453; previous: minor, issue, catalog and export-format references re-audited against `upstream/main` 470ea7a3; the 10 October re-measure is otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (issue, catalog and export-format references re-audited against `upstream/main` 470ea7a3; the 10 October re-measure is otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
The authoritative assessment of how close EffectCraft is to After Effects, and how much work is
left. [gaps.md](gaps.md) lists every known shortfall one at a time; the
@@ -119,7 +119,7 @@ compositing and effects dominate; audio is light). Hours are to full parity for
| Animation: keyframes, graph editor, motion paths, puppet | 15% | 97% | 55% | 35–60 | Only scalar ease is measured against After Effects (168 samples); puppet lag (#586); #456, #457, #355, #553 and #318 are closed; Proportional Scrubbing (26.2) |
| Expressions and scripting | 7% | 93% | 65% | 20–40 | `.jsxbin`, Startup/Shutdown folders, script shortcuts (`#include` landed, #759); external editor (#364); see [scripting-parity.md](scripting-parity.md) |
| Shapes, masks and roto | 9% | 93% | 45% | 30–50 | Open mask and shape issues (#1004, #999, #1042, #932); #510, #513, #527, #509 and #303 are closed; paste SVG/AI as shape layers (26.3; SVG markup pastes since #738) |
| Text and typography | 7% | 94% | 50% | 20–35 | Chinese text in comps now reaches the installed Chinese faces in the Han fallback (#453); paragraph alignment inverted (#316); soft text at Fit (#417, mostly fixed by #444); toolbar font options (#550); #483 and #583 are closed; variable-font filter landed (#741) |
| Text and typography | 7% | 94% | 50% | 20–35 | CJK text in comps renders wrongly (#453); paragraph alignment inverted (#316); soft text at Fit (#417, mostly fixed by #444); toolbar font options (#550); #483 and #583 are closed; variable-font filter landed (#741) |
| Effects and animation presets | 14% | 85% | 40% | 70–130 | All 306 effects exist, but rendered output is unmeasured against After Effects; 54 built-in presets (text, Transitions, Backgrounds) against After Effects' 621 ([effects-parity.md](effects-parity.md)); open effect bugs (#580 crash, #463, #369, #473, #474; #514 and #526 are closed); effect colour labels (26.5); see [effects-parity.md](effects-parity.md) |
| 3D (Classic and Advanced) | 6% | 82% | 35% | 35–65 | No FBX/USD/STL models, no Substance materials, mesh displacement unverified; no 3D transform gizmos (#392, #347); reflections (#261); camera aperture (#260); #489 and #315 are closed |
| Tracking and AI-assisted tools | 5% | 80% | 35% | 35–70 | Object Matte (26.2) has the effect, the viewer tool (click / box), background tracking, Freeze and the 26.5 disk cache, not yet compared with After Effects; Roto Brush (MobileSAM) and face tracking (MediaPipe) unmeasured against After Effects; camera-tracker view (#505) |
@@ -193,7 +193,7 @@ generative/cloud AI (Object Matte, Firefly), Adobe services and team features, s
| Keyframes, Graph Editor, easing, motion paths | 20% | 75% | 168 live samples match After Effects; #456/#457, #553 and #569 were counted here and are now closed |
| Expressions: `wiggle`, `loopOut`, linking, sliders | 6% | 80% | `crates/expr` tests; no live expression oracle yet |
| Shape layers and masks | 12% | 65% | #510, #513, #527 mask bugs and #303 Mesh Warp were counted here and are now closed; open: #1004, #999, #1042 |
| Text and text animators | 12% | 70% | #316 alignment inverted; #453 CJK fallback fixed; #583 font search (closed) |
| Text and text animators | 12% | 70% | #316 alignment inverted, #453 CJK (open); #583 font search (closed) |
| Everyday effects: blurs, glow, fill/tint, curves/levels, keying, drop shadow, displacement | 10% | 70% | All present with GPU paths; unmeasured against After Effects; #514 Apply Color LUT (closed) |
| Preview (RAM preview with audio) | 8% | 55% | #595, #304 stutter; #504 late audio (#554 closed) |
| Render to H.264 / ProRes / PNG sequences | 6% | 75% | Encoder tests; #340 ProRes 4444 tag (open); #446 default macOS output folder (closed) |
@@ -1168,7 +1168,6 @@ top of this page and in items 16–18 above.
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | Chinese Han text in compositions uses the installed macOS Chinese faces rather than the Japanese face (#453) |
| 2026-10-12 | minor | Audited against `upstream/main` 470ea7a3: open crash list, closed-issue references in the area tables, export formats 12 to 13, localization ≈ 12% to ≈ 18%, `#include`, built-in preset count, 12 October repository counts |
| 2026-10-10 | minor | Readiness-by-audience table: hours to ≈ 95% for each of the three numbers; the full number stays an additive weighted sum (unchanged) |
| 2026-10-10 | minor | Added mainstream practitioner (≈ 47%) and essentials user (≈ 52%) numbers with weights, discounts and GitHub user evidence; wrote down how the full ready number is built (unchanged at ≈ 45%) |