ucode 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +72 -0
  3. data/Gemfile.lock +2 -2
  4. data/TODO.full/00-README.md +116 -0
  5. data/TODO.full/01-panglyph-vision.md +112 -0
  6. data/TODO.full/02-panglyph-repo-bootstrap.md +184 -0
  7. data/TODO.full/03-panglyph-font-builder.md +201 -0
  8. data/TODO.full/04-panglyph-publish-pipeline.md +126 -0
  9. data/TODO.full/05-ucode-0-1-1-release.md +139 -0
  10. data/TODO.full/06-fontisan-remove-audit.md +142 -0
  11. data/TODO.full/07-fontisan-remove-ucd.md +125 -0
  12. data/TODO.full/08-archive-private-bin-build.md +143 -0
  13. data/TODO.full/09-archive-public-structure.md +164 -0
  14. data/TODO.full/10-fontist-org-woff-glyphs.md +131 -0
  15. data/TODO.full/11-fontist-org-audit-coverage.md +140 -0
  16. data/TODO.full/12-implementation-order.md +216 -0
  17. data/TODO.full/13-fontisan-font-writer-api.md +189 -0
  18. data/TODO.full/14-fontisan-table-writers.md +66 -0
  19. data/TODO.full/15-panglyph-builder-real.md +82 -0
  20. data/TODO.full/16-archive-public-sync-workflows.md +167 -0
  21. data/TODO.full/17-fontist-org-font-picker.md +73 -0
  22. data/TODO.full/18-comprehensive-spec-coverage.md +64 -0
  23. data/TODO.full/19-ucode-0-1-2-patch.md +32 -0
  24. data/TODO.full/20-fontisan-0-2-23-release.md +52 -0
  25. data/TODO.new/00-README.md +30 -0
  26. data/TODO.new/23-universal-glyph-set-source-map.md +312 -0
  27. data/TODO.new/24-universal-glyph-set-build.md +189 -0
  28. data/TODO.new/25-font-audit-against-universal-set.md +195 -0
  29. data/TODO.new/26-missing-glyph-reporter.md +189 -0
  30. data/TODO.new/27-fontist-org-consumer-integration.md +200 -0
  31. data/TODO.new/28-implementation-order-update.md +187 -0
  32. data/TODO.new/29-universal-set-curation-uc17.md +312 -0
  33. data/TODO.new/30-tier1-font-acquisition.md +241 -0
  34. data/TODO.new/31-universal-set-production-build.md +205 -0
  35. data/TODO.new/32-uc17-coverage-matrix.md +165 -0
  36. data/TODO.new/33-specialist-font-acquisition-refresh.md +138 -0
  37. data/TODO.new/34-pillar2-content-stream-correlator.md +147 -0
  38. data/TODO.new/35-universal-set-production-run.md +160 -0
  39. data/TODO.new/36-per-font-coverage-audit.md +145 -0
  40. data/TODO.new/37-coverage-highlight-reporter.md +125 -0
  41. data/TODO.new/38-fontist-org-glyph-consumer.md +141 -0
  42. data/TODO.new/39-implementation-order-update-32-38.md +258 -0
  43. data/TODO.new/40-archive-private-uses-ucode-audit.md +124 -0
  44. data/TODO.new/41-ucode-unicode-archive-bridge.md +160 -0
  45. data/config/specialist_fonts.yml +102 -0
  46. data/config/unicode17_tier1_fonts.yml +42 -0
  47. data/config/unicode17_universal_glyph_set.yml +293 -0
  48. data/lib/ucode/audit/block_aggregator.rb +57 -29
  49. data/lib/ucode/audit/browser/face_page.rb +128 -0
  50. data/lib/ucode/audit/browser/glyph_panel.rb +124 -0
  51. data/lib/ucode/audit/browser/library_page.rb +74 -0
  52. data/lib/ucode/audit/browser/missing_glyph_page.rb +87 -0
  53. data/lib/ucode/audit/browser/template.rb +47 -0
  54. data/lib/ucode/audit/browser/templates/face.css +200 -0
  55. data/lib/ucode/audit/browser/templates/face.html.erb +41 -0
  56. data/lib/ucode/audit/browser/templates/face.js +298 -0
  57. data/lib/ucode/audit/browser/templates/library.css +119 -0
  58. data/lib/ucode/audit/browser/templates/library.html.erb +42 -0
  59. data/lib/ucode/audit/browser/templates/library.js +99 -0
  60. data/lib/ucode/audit/browser/templates/missing_glyph_page.css +119 -0
  61. data/lib/ucode/audit/browser/templates/missing_glyph_page.html.erb +58 -0
  62. data/lib/ucode/audit/browser/templates/missing_glyph_page.js +2 -0
  63. data/lib/ucode/audit/browser.rb +32 -0
  64. data/lib/ucode/audit/context.rb +27 -1
  65. data/lib/ucode/audit/coverage_reference.rb +103 -0
  66. data/lib/ucode/audit/differ.rb +121 -0
  67. data/lib/ucode/audit/emitter/block_emitter.rb +52 -0
  68. data/lib/ucode/audit/emitter/codepoint_emitter.rb +87 -0
  69. data/lib/ucode/audit/emitter/collection_emitter.rb +80 -0
  70. data/lib/ucode/audit/emitter/face_directory.rb +212 -0
  71. data/lib/ucode/audit/emitter/glyph_emitter.rb +48 -0
  72. data/lib/ucode/audit/emitter/index_emitter.rb +149 -0
  73. data/lib/ucode/audit/emitter/library_emitter.rb +96 -0
  74. data/lib/ucode/audit/emitter/paths.rb +312 -0
  75. data/lib/ucode/audit/emitter/plane_emitter.rb +29 -0
  76. data/lib/ucode/audit/emitter/script_emitter.rb +29 -0
  77. data/lib/ucode/audit/emitter.rb +29 -0
  78. data/lib/ucode/audit/extractors/aggregations.rb +31 -2
  79. data/lib/ucode/audit/face_auditor.rb +86 -0
  80. data/lib/ucode/audit/formatters/audit_diff_text.rb +112 -0
  81. data/lib/ucode/audit/formatters/audit_text.rb +411 -0
  82. data/lib/ucode/audit/formatters/color.rb +48 -0
  83. data/lib/ucode/audit/formatters/library_summary_text.rb +98 -0
  84. data/lib/ucode/audit/formatters/text_formatter.rb +83 -0
  85. data/lib/ucode/audit/formatters.rb +23 -0
  86. data/lib/ucode/audit/library_aggregator.rb +86 -0
  87. data/lib/ucode/audit/library_auditor.rb +105 -0
  88. data/lib/ucode/audit/release/emitter.rb +152 -0
  89. data/lib/ucode/audit/release/face_card.rb +93 -0
  90. data/lib/ucode/audit/release/formula_audits.rb +50 -0
  91. data/lib/ucode/audit/release/library_index_builder.rb +78 -0
  92. data/lib/ucode/audit/release/manifest_builder.rb +127 -0
  93. data/lib/ucode/audit/release.rb +42 -0
  94. data/lib/ucode/audit/ucd_only_reference.rb +81 -0
  95. data/lib/ucode/audit/universal_set_reference.rb +136 -0
  96. data/lib/ucode/audit.rb +31 -0
  97. data/lib/ucode/cli.rb +339 -33
  98. data/lib/ucode/commands/audit/browser_command.rb +82 -0
  99. data/lib/ucode/commands/audit/collection_command.rb +103 -0
  100. data/lib/ucode/commands/audit/compare_command.rb +188 -0
  101. data/lib/ucode/commands/audit/font_command.rb +140 -0
  102. data/lib/ucode/commands/audit/library_command.rb +87 -0
  103. data/lib/ucode/commands/audit/reference_builder.rb +64 -0
  104. data/lib/ucode/commands/audit.rb +20 -0
  105. data/lib/ucode/commands/block_feed.rb +73 -0
  106. data/lib/ucode/commands/canonical_build.rb +138 -0
  107. data/lib/ucode/commands/fetch.rb +37 -1
  108. data/lib/ucode/commands/release.rb +115 -0
  109. data/lib/ucode/commands/universal_set.rb +211 -0
  110. data/lib/ucode/commands.rb +5 -0
  111. data/lib/ucode/coordinator/indices.rb +11 -0
  112. data/lib/ucode/coordinator.rb +138 -5
  113. data/lib/ucode/error.rb +30 -2
  114. data/lib/ucode/fetch/font_fetcher/result.rb +39 -0
  115. data/lib/ucode/fetch/font_fetcher.rb +16 -0
  116. data/lib/ucode/fetch/specialist_font_fetcher.rb +280 -0
  117. data/lib/ucode/fetch.rb +7 -3
  118. data/lib/ucode/glyphs/real_fonts/cmap_cache.rb +74 -0
  119. data/lib/ucode/glyphs/real_fonts.rb +1 -0
  120. data/lib/ucode/glyphs/resolver.rb +62 -0
  121. data/lib/ucode/glyphs/source.rb +48 -0
  122. data/lib/ucode/glyphs/source_builder.rb +61 -0
  123. data/lib/ucode/glyphs/source_config/coverage_assertion.rb +79 -0
  124. data/lib/ucode/glyphs/source_config/gap_report.rb +54 -0
  125. data/lib/ucode/glyphs/source_config.rb +104 -0
  126. data/lib/ucode/glyphs/sources/pillar1_embedded_tounicode.rb +63 -0
  127. data/lib/ucode/glyphs/sources/pillar3_last_resort.rb +51 -0
  128. data/lib/ucode/glyphs/sources/tier1_real_font.rb +104 -0
  129. data/lib/ucode/glyphs/sources.rb +20 -0
  130. data/lib/ucode/glyphs/universal_set/builder.rb +161 -0
  131. data/lib/ucode/glyphs/universal_set/coverage_report.rb +139 -0
  132. data/lib/ucode/glyphs/universal_set/idempotency.rb +86 -0
  133. data/lib/ucode/glyphs/universal_set/manifest_accumulator.rb +195 -0
  134. data/lib/ucode/glyphs/universal_set/manifest_writer.rb +61 -0
  135. data/lib/ucode/glyphs/universal_set/pre_build_check.rb +197 -0
  136. data/lib/ucode/glyphs/universal_set/validator.rb +204 -0
  137. data/lib/ucode/glyphs/universal_set.rb +45 -0
  138. data/lib/ucode/glyphs.rb +6 -0
  139. data/lib/ucode/models/audit/baseline.rb +6 -0
  140. data/lib/ucode/models/audit/block_summary.rb +7 -0
  141. data/lib/ucode/models/audit/codepoint_provenance.rb +39 -0
  142. data/lib/ucode/models/audit/release_face.rb +42 -0
  143. data/lib/ucode/models/audit/release_formula.rb +33 -0
  144. data/lib/ucode/models/audit/release_manifest.rb +43 -0
  145. data/lib/ucode/models/audit/release_universal_set.rb +37 -0
  146. data/lib/ucode/models/audit.rb +9 -0
  147. data/lib/ucode/models/block.rb +2 -0
  148. data/lib/ucode/models/build_report.rb +109 -0
  149. data/lib/ucode/models/codepoint/glyph.rb +42 -0
  150. data/lib/ucode/models/codepoint.rb +3 -0
  151. data/lib/ucode/models/glyph_source.rb +86 -0
  152. data/lib/ucode/models/glyph_source_map.rb +138 -0
  153. data/lib/ucode/models/specialist_font.rb +70 -0
  154. data/lib/ucode/models/specialist_font_manifest.rb +48 -0
  155. data/lib/ucode/models/unihan_entry.rb +81 -9
  156. data/lib/ucode/models/unihan_field.rb +21 -0
  157. data/lib/ucode/models/universal_set_entry.rb +47 -0
  158. data/lib/ucode/models/universal_set_manifest.rb +78 -0
  159. data/lib/ucode/models/validation_report.rb +99 -0
  160. data/lib/ucode/models.rb +9 -0
  161. data/lib/ucode/parsers/named_sequences.rb +5 -5
  162. data/lib/ucode/parsers/unihan.rb +50 -19
  163. data/lib/ucode/repo/aggregate_writer.rb +34 -2
  164. data/lib/ucode/repo/block_feed_emitter.rb +153 -0
  165. data/lib/ucode/repo/build_report_accumulator.rb +138 -0
  166. data/lib/ucode/repo/build_report_writer.rb +46 -0
  167. data/lib/ucode/repo/build_validator.rb +229 -0
  168. data/lib/ucode/repo/codepoint_writer.rb +50 -1
  169. data/lib/ucode/repo/paths.rb +8 -0
  170. data/lib/ucode/repo.rb +4 -0
  171. data/lib/ucode/version.rb +1 -1
  172. data/schema/block-feed.output.schema.yml +134 -0
  173. metadata +143 -2
  174. data/ucode.gemspec +0 -56
@@ -0,0 +1,201 @@
1
+ # 03 — Panglyph font builder (outline extraction + assembly)
2
+
3
+ ## Goal
4
+
5
+ Implement the actual font-build pipeline: read ucode's universal-set
6
+ manifest, extract outlines from Tier 1 source fonts via fontisan,
7
+ assemble them into a single OpenType/TrueType font, and write the
8
+ result to disk in TTF / WOFF2 / OTF formats.
9
+
10
+ This is the heart of panglyph. The CLI commands (`build`, `validate`,
11
+ `publish`) are thin wrappers around these classes.
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ucode universal-set manifest
17
+
18
+ │ {cp: 65, source: {font: "noto-sans", gid: 36}}
19
+ │ {cp: 19968, source: {font: "FSung-1", gid: 18432}}
20
+ │ {cp: 128512, source: {font: "NotoSerifTaiYo", gid: 41}}
21
+ │ ...
22
+
23
+ ┌──────────────────────────────────────────────┐
24
+ │ Builder │
25
+ │ - reads manifest │
26
+ │ - groups codepoints by source font │
27
+ │ - for each source font: │
28
+ │ OutlineExtractor.extract_many(font, cps)│
29
+ │ - passes {cp → outline} to FontAssembler │
30
+ └──────────────────────────────────────────────┘
31
+
32
+
33
+ ┌──────────────────────────────────────────────┐
34
+ │ FontAssembler │
35
+ │ - opens an empty font skeleton │
36
+ │ - for each codepoint: │
37
+ │ - allocates a GID in panglyph │
38
+ │ - writes the outline into glyf/CFF │
39
+ │ - sets cmap[cp] = GID │
40
+ │ - finalizes: OS/2, name, head, hhea, hmtx │
41
+ │ - writes panglyph-unicode17.ttf │
42
+ └──────────────────────────────────────────────┘
43
+
44
+
45
+ ┌──────────────────────────────────────────────┐
46
+ │ Woff2Writer (via fontisan ConvertCommand) │
47
+ │ - reads the TTF │
48
+ │ - writes panglyph-unicode17.woff2 │
49
+ └──────────────────────────────────────────────┘
50
+
51
+
52
+ ┌──────────────────────────────────────────────┐
53
+ │ CoverageReport │
54
+ │ - summarizes per-block source breakdown │
55
+ │ - writes coverage-report.json │
56
+ └──────────────────────────────────────────────┘
57
+ ```
58
+
59
+ ## Scope
60
+
61
+ ### Phase A — Outline extraction (`OutlineExtractor`)
62
+
63
+ 1. **Open source font** via `Fontisan::Font.open(path)`.
64
+ 2. **For each codepoint** in the source's responsibility list:
65
+ a. Look up GID via `font.cmap.unicode_map[cp]`
66
+ b. Get the outline:
67
+ - TrueType: `font.glyf.glyphs[gid]` → contour points
68
+ - CFF/OpenType: `font.cff.charstrings[gid]` → charstring program
69
+ c. Normalize to a uniform internal representation
70
+ (`Panglyph::Outline` struct with point arrays + flags).
71
+ 3. **Return** `{ cp => Outline }` hash.
72
+
73
+ #### Edge cases
74
+
75
+ - **Composite glyphs** (TrueType): if a glyph references other glyphs
76
+ via component references, recursively flatten into a single outline.
77
+ Track "this glyph was a composite" in the report.
78
+ - **TTC collections**: pass `font_index:` to `Fontisan::Font.open`.
79
+ - **Missing cmap entry**: the Tier 1 font claims to cover this cp but
80
+ its cmap doesn't actually map it. Log + skip; the universal set's
81
+ pre-check (TODO.new/35) catches this upstream.
82
+
83
+ ### Phase B — Font assembly (`FontAssembler`)
84
+
85
+ 4. **Initialize an empty font skeleton** with the standard required
86
+ tables: `cmap`, `head`, `hhea`, `hmtx`, `maxp`, `name`, `OS/2`,
87
+ `glyf`, `loca`, `post`. (For OTF: `CFF ` instead of `glyf`/`loca`.)
88
+ 5. **For each codepoint**:
89
+ a. Allocate next GID (`maxp.numGlyphs += 1`).
90
+ b. Write the outline to `glyf` (or `CFF `).
91
+ c. Add `cmap` subtable 4 entry: `unicode_map[cp] = gid`.
92
+ d. Add `hmtx` entry (advance width from source font; default 1000
93
+ if missing).
94
+ e. Add `name` entries — actually, only the font-family / version
95
+ name needs to be set; per-glyph names aren't required.
96
+ 6. **Finalize metadata**:
97
+ - `name`: family="panglyph Unicode 17", subfamily="Regular",
98
+ full="panglyph-unicode17", version="Version 17.0.0"
99
+ - `OS/2`: usWeightClass=400, fsSelection=REGULAR, ulUnicodeRange1..4
100
+ bit-packed for every range panglyph covers
101
+ - `head`: unitsPerEm=1000 (or 2048 for CJK-heavy), lowestRecPPEM=8
102
+ - `hhea`: numberOfHMetrics matches glyphs
103
+ 7. **Compute checksums + write final TTF**.
104
+
105
+ #### fontisan extension required
106
+
107
+ fontisan today only READS fonts + converts (WOFF). It needs new APIs:
108
+
109
+ ```ruby
110
+ # lib/fontisan/font_writer.rb (NEW)
111
+ class Fontisan::FontWriter
112
+ def initialize(format: :ttf)
113
+ @tables = {}
114
+ @format = format
115
+ end
116
+
117
+ def set_cmap(unicode_to_gid_map)
118
+ # ...
119
+ end
120
+
121
+ def add_glyph(gid, outline, advance_width: 1000, lsb: 0)
122
+ # ...
123
+ end
124
+
125
+ def set_name_records(records)
126
+ # ...
127
+ end
128
+
129
+ def write_to(path)
130
+ # computes checksums, writes head/glyf/loca/cmap/etc.
131
+ end
132
+ end
133
+ ```
134
+
135
+ This is a meaningful fontisan addition. Tracked separately as a
136
+ fontisan TODO (see TODO.new/19 — fontisan docs update).
137
+
138
+ ### Phase C — WOFF2 conversion (`Woff2Writer`)
139
+
140
+ 8. Use `Fontisan::Commands::ConvertCommand.new(ttf_path, to: "woff2", output: ...)`.
141
+ No new code needed; just shell out from panglyph.
142
+
143
+ ### Phase D — Coverage report (`CoverageReport`)
144
+
145
+ 9. Walk the built font's cmap.
146
+ 10. Compare against ucode's universal-set codepoint list.
147
+ 11. Bucket per-block + per-source-tier:
148
+ ```json
149
+ {
150
+ "total_codepoints": 299382,
151
+ "covered": 297415,
152
+ "missing": 1967,
153
+ "by_block": [
154
+ { "block_id": "Egyptian_Hieroglyphs_Extended-B", "covered": 599, "missing": 1, "tier": 1 }
155
+ ],
156
+ "by_tier": [
157
+ { "tier": 1, "count": 295000 },
158
+ { "tier": 2, "count": 2415 },
159
+ { "tier": 3, "count": 1967 }
160
+ ]
161
+ }
162
+ ```
163
+
164
+ ## Performance considerations
165
+
166
+ - 299,382 glyph extractions × ~1ms each = ~5 minutes of extraction.
167
+ - Parallelize per-source-font: each source font's extractions are
168
+ independent, so use parallel gem or `Concurrent::Map`.
169
+ - Memory: hold all outlines in memory at once (~1.2GB estimated). May
170
+ need to stream to disk for low-memory CI runners (use a temp sqlite
171
+ database keyed by GID).
172
+
173
+ ## Specs
174
+
175
+ - **Builder** — fixture: 5 codepoints from 2 source fonts; assert
176
+ output font's cmap matches the manifest.
177
+ - **OutlineExtractor** — fixture: a real font with known glyf layout;
178
+ assert extracted outline matches a known-good serialization.
179
+ - **FontAssembler** — fixture: 3 outlines; assert output TTF parses
180
+ via `Fontisan::Font.open` and has the expected cmap.
181
+ - **Woff2Writer** — fixture: a known TTF; assert WOFF2 output
182
+ byte-identical to a fontisan-direct conversion (sanity check).
183
+ - **CoverageReport** — fixture: a built font with intentional gaps;
184
+ assert report correctly identifies missing codepoints.
185
+
186
+ ## Acceptance
187
+
188
+ - [ ] `panglyph build 17.0.0` produces a valid TTF, WOFF2, and OTF
189
+ - [ ] The built font's cmap contains every codepoint in the universal
190
+ set's manifest
191
+ - [ ] `fontisan Font.open(panglyph.ttf)` succeeds (no corruption)
192
+ - [ ] Coverage report JSON validates against schema
193
+ - [ ] Build completes in <30 minutes on a single runner
194
+ - [ ] Built font renders correctly in a browser (manual smoke test)
195
+
196
+ ## References
197
+
198
+ - [TODO 01](01-panglyph-vision.md) — vision
199
+ - [TODO 02](02-panglyph-repo-bootstrap.md) — repo skeleton
200
+ - [TODO.new/24](../TODO.new/24-universal-glyph-set-build.md) — ucode's universal-set SVG output (panglyph input)
201
+ - fontisan Font.open API (for reading source fonts)
@@ -0,0 +1,126 @@
1
+ # 04 — Panglyph publish pipeline (release to fontist-archive-public)
2
+
3
+ ## Goal
4
+
5
+ When `panglyph build` completes, publish the resulting TTF/WOFF2/OTF
6
+ artifacts to `fontist-archive-public/panglyph/`. This makes panglyph
7
+ available to fontist.org and any other consumer that pulls from the
8
+ public archive.
9
+
10
+ ## Why a separate TODO
11
+
12
+ The build itself is local (TODO 03). Publishing involves:
13
+ - Authenticating to fontist-archive-public via GHA bot token
14
+ - Atomic sync (don't leave the archive half-updated)
15
+ - Versioning (multiple panglyph versions can coexist)
16
+ - Manifest update (so consumers can discover what's available)
17
+
18
+ These concerns are distinct from the build, and any failure here
19
+ shouldn't invalidate the build itself.
20
+
21
+ ## Scope
22
+
23
+ ### Phase A — `panglyph publish` command
24
+
25
+ 1. New CLI subcommand: `panglyph publish [VERSION]`.
26
+
27
+ 2. Behavior:
28
+ - Clone `fontist/fontist-archive-public` shallow into a temp dir.
29
+ - Copy artifacts:
30
+ ```
31
+ panglyph-unicode17-17.0.0.ttf → archive-public/panglyph/v17.0.0/panglyph-unicode17.ttf
32
+ panglyph-unicode17-17.0.0.woff2 → archive-public/panglyph/v17.0.0/panglyph-unicode17.woff2
33
+ panglyph-unicode17-17.0.0.otf → archive-public/panglyph/v17.0.0/panglyph-unicode17.otf
34
+ coverage-report.json → archive-public/panglyph/v17.0.0/coverage-report.json
35
+ source-manifest.json → archive-public/panglyph/v17.0.0/source-manifest.json
36
+ ```
37
+ - Update top-level `archive-public/panglyph/manifest.json`:
38
+ ```json
39
+ {
40
+ "latest": "17.0.0",
41
+ "versions": {
42
+ "17.0.0": {
43
+ "ucd_version": "17.0.0",
44
+ "panglyph_version": "17.0.0",
45
+ "released_at": "2026-...",
46
+ "coverage": { "covered": 297415, "total": 299382, "percentage": 99.3 },
47
+ "artifacts": {
48
+ "ttf": "v17.0.0/panglyph-unicode17.ttf",
49
+ "woff2": "v17.0.0/panglyph-unicode17.woff2",
50
+ "otf": "v17.0.0/panglyph-unicode17.otf"
51
+ },
52
+ "sha256": { "ttf": "...", "woff2": "...", "otf": "..." }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+ - Commit + push via GHA bot identity:
58
+ ```
59
+ Author: fontist-bot <bot@fontist.org>
60
+ Message: panglyph: publish v17.0.0
61
+ ```
62
+
63
+ 3. **Atomicity**: write to a temp dir first, then `git mv` into place.
64
+ If any step fails, the archive is not left in a half-published state.
65
+
66
+ ### Phase B — Source manifest
67
+
68
+ 4. The build emits a `source-manifest.json` recording which Tier 1
69
+ source font contributed each glyph:
70
+
71
+ ```json
72
+ {
73
+ "sources": [
74
+ { "label": "noto-sans", "license": "OFL", "url": "...", "sha256": "...", "glyphs_contributed": 1107 },
75
+ { "label": "FSung-1", "license": "OFL", "path": "data/fonts/FSung-1.ttf", "sha256": "...", "glyphs_contributed": 20992 },
76
+ ...
77
+ ],
78
+ "total_sources": 17,
79
+ "total_glyphs": 297415
80
+ }
81
+ ```
82
+
83
+ 5. This manifest is the **provenance record** for the published font.
84
+ Anyone redistributing panglyph can verify OFL compliance.
85
+
86
+ ### Phase C — GitHub Release
87
+
88
+ 6. In addition to pushing to fontist-archive-public, the CI workflow
89
+ (TODO 02) creates a GitHub Release on the `panglyph` repo:
90
+ - Tag: `v17.0.0`
91
+ - Title: `panglyph Unicode 17.0.0`
92
+ - Body: copy of the coverage report + source manifest summary
93
+ - Assets: the TTF / WOFF2 / OTF files + JSON manifests
94
+
95
+ 7. Why both? `fontist-archive-public/panglyph/` is the canonical
96
+ machine-readable source. GitHub Releases is the human-discoverable
97
+ download page (people search GitHub for "panglyph" and find the
98
+ release directly).
99
+
100
+ ### Phase D — Idempotency + re-publishing
101
+
102
+ 8. If the same version is re-published (e.g. fixing a build bug):
103
+ - Detect existing version directory
104
+ - Compare new artifacts' sha256 to existing
105
+ - If identical, no-op (idempotent)
106
+ - If different, bump patch version (`17.0.0` → `17.0.1`) and publish
107
+ as new version. Old version stays for rollback.
108
+
109
+ 9. **NEVER overwrite a published version in-place.** Consumers may have
110
+ cached the old artifacts. Bumping patch is the only safe update.
111
+
112
+ ## Acceptance
113
+
114
+ - [ ] `panglyph publish 17.0.0` updates fontist-archive-public/panglyph/
115
+ - [ ] `manifest.json` reflects the new version
116
+ - [ ] Source manifest records every contributing font + its sha256
117
+ - [ ] GitHub Release exists on `fontist/panglyph` with the right tag
118
+ - [ ] Re-publishing a built version is a no-op (idempotent)
119
+ - [ ] Failed publish leaves archive-public unchanged (atomic)
120
+
121
+ ## References
122
+
123
+ - [TODO 02](02-panglyph-repo-bootstrap.md) — CI workflow
124
+ - [TODO 03](03-panglyph-font-builder.md) — build artifacts
125
+ - [TODO.new/41](../TODO.new/41-ucode-unicode-archive-bridge.md) — archive bridge pattern
126
+ - [TODO 09](09-archive-public-structure.md) — overall archive-public structure
@@ -0,0 +1,139 @@
1
+ # 05 — ucode 0.1.1 patch release
2
+
3
+ ## Goal
4
+
5
+ Cut the first public ucode gem release: **0.1.0 → 0.1.1**. Unblocks
6
+ downstream consumers (fontist-archive-private, panglyph) that depend
7
+ on a published gem.
8
+
9
+ ## Why 0.1.1 (not 0.1.0)
10
+
11
+ 0.1.0 was the initial commit (`Initial release: ucode 0.1.0`). The
12
+ codebase has since gained:
13
+ - BlockFeedEmitter (renamed from FontistConsumerEmitter)
14
+ - 4-tier canonical resolver
15
+ - Universal-set build infrastructure
16
+ - Real UCD 17.0.0 parse pipeline
17
+ - Audit subsystem ported from fontisan
18
+ - Block-feed shape schema
19
+ - Categorized Unihan data model
20
+
21
+ A patch bump (0.1.1) signals "iteration on the initial release" without
22
+ claiming API stability (which would warrant 0.2.0).
23
+
24
+ ## Scope
25
+
26
+ ### Phase A — Pre-release prep
27
+
28
+ 1. **Update `lib/ucode/version.rb`**:
29
+ ```ruby
30
+ module Ucode
31
+ VERSION = "0.1.1"
32
+ end
33
+ ```
34
+
35
+ 2. **Create `CHANGELOG.md`** (doesn't exist yet) with the 0.1.1 entry:
36
+ ```markdown
37
+ # Changelog
38
+
39
+ ## 0.1.1 — 2026-06-XX
40
+
41
+ ### Added
42
+ - BlockFeedEmitter: emits a compact per-block Unicode data feed
43
+ (renamed from FontistConsumerEmitter — the data is plain Unicode
44
+ data, not consumer-specific).
45
+ - Categorized Unihan model: 8 typed collections matching the Unihan
46
+ file structure (Dictionary Indices, Readings, Variants, etc.).
47
+ - Real-font Tier 1 source map for the universal glyph set (~17
48
+ specialists + Noto family default).
49
+ - Pillar 1 + Pillar 2 glyph extraction via 4-tier resolver.
50
+ - Per-codepoint properties from `extracted/` and `auxiliary/` UCD
51
+ files (display, segmentation, Indic, Hangul, Emoji, full binary
52
+ properties list).
53
+
54
+ ### Fixed
55
+ - NamedSequences parser field order (real UAX#44 is `Name; cps...`).
56
+ - BlockFeedEmitter canonical path (`blocks/<ID>/index.json` not
57
+ `blocks/<ID>.json`).
58
+ - fontist.org char page route params for combining/bidiclass.
59
+ - Vite dev server case-sensitive codepoints/ path (lowercase hex).
60
+ - Vue route-watcher for per-char data on navigation.
61
+
62
+ ### Removed
63
+ - All references to "fontist-consumer" naming from ucode (now
64
+ "block-feed"). The data emitted is Unicode data, not
65
+ consumer-specific.
66
+ ```
67
+
68
+ 3. **Update `ucode.gemspec`** if any metadata needs changing (likely
69
+ nothing — `spec.metadata` is already correct).
70
+
71
+ 4. **Run full test suite + rubocop** to make sure 0.1.1 actually ships
72
+ green:
73
+ ```bash
74
+ bundle exec rspec
75
+ bundle exec rubocop
76
+ ```
77
+
78
+ ### Phase B — Branch + PR
79
+
80
+ 5. Create release branch off `main`:
81
+ ```bash
82
+ git checkout main && git pull
83
+ git checkout -b release/0.1.1
84
+ ```
85
+
86
+ 6. Apply changes: version bump + CHANGELOG.
87
+
88
+ 7. Push + open PR:
89
+ ```bash
90
+ git push -u origin release/0.1.1
91
+ gh pr create --title "Release ucode 0.1.1" --body "..."
92
+ ```
93
+
94
+ ### Phase C — Merge + tag + publish
95
+
96
+ 8. **HARD GATE**: require explicit user authorization to merge + tag +
97
+ push to rubygems. Per global rules:
98
+ - NEVER push tags without user authorization
99
+ - NEVER merge to main without user authorization
100
+
101
+ 9. After user says "merge + tag + release":
102
+ ```bash
103
+ gh pr merge --merge release/0.1.1 # or rebase per user pref
104
+ git checkout main && git pull
105
+ git tag v0.1.1
106
+ git push origin v0.1.1
107
+ bundle exec rake release # pushes to rubygems.org
108
+ ```
109
+
110
+ 10. Verify on RubyGems:
111
+ ```bash
112
+ gem search ucode --remote
113
+ # → ucode (0.1.1)
114
+ ```
115
+
116
+ 11. Create GitHub Release on the tag with the CHANGELOG entry as body.
117
+
118
+ ### Phase D — Downstream notifications
119
+
120
+ 12. Once 0.1.1 is on RubyGems, downstream consumers can update:
121
+ - `fontist/Gemfile` in fontist-archive-private: bump `ucode` to `~> 0.1`
122
+ - `panglyph.gemspec`: same
123
+ - `fontisan` Gemfile if it consumes ucode (it shouldn't after TODO 06/07)
124
+
125
+ ## Acceptance
126
+
127
+ - [ ] `lib/ucode/version.rb` says `0.1.1`
128
+ - [ ] `CHANGELOG.md` exists with the 0.1.1 entry
129
+ - [ ] All specs pass; rubocop clean
130
+ - [ ] PR opened against main, green CI
131
+ - [ ] **WAIT for explicit user authorization before merge/tag/release**
132
+ - [ ] After release: `gem install ucode` works; `ucode --version` prints `0.1.1`
133
+ - [ ] GitHub Release exists at `fontist/ucode/releases/tag/v0.1.1`
134
+
135
+ ## References
136
+
137
+ - `lib/ucode/version.rb` — version constant
138
+ - `ucode.gemspec` — gem metadata
139
+ - Global rule: NEVER push tags / merge to main without authorization
@@ -0,0 +1,142 @@
1
+ # 06 — fontisan: remove AuditCommand (and audit/ namespace)
2
+
3
+ ## Goal
4
+
5
+ Strip the audit subsystem out of `fontisan`. ucode now owns font
6
+ auditing (`ucode audit font`); fontisan's `AuditCommand`,
7
+ `AuditLibraryCommand`, `AuditCompareCommand`, and the entire
8
+ `lib/fontisan/audit/` and `lib/fontisan/models/audit/` namespaces
9
+ are dead code that misleads consumers.
10
+
11
+ The current `fontist-archive-private/bin/build` script still references
12
+ `Fontisan::Commands::AuditCommand` — it'll break loudly once this lands,
13
+ which is the point. TODO 08 wires bin/build to call `ucode audit font`
14
+ instead.
15
+
16
+ ## Why now
17
+
18
+ - ucode's audit is the canonical path (TODO.new 06-12 ported it).
19
+ - fontisan's audit is unmaintained and uses a UCD-stub hack (lines
20
+ 13-21 of `fontist-archive-private/bin/build`) that papers over the
21
+ fact that fontisan can't actually do UCD aggregation anymore.
22
+ - Leaving it creates confusion: which is the source of truth?
23
+
24
+ ## Scope
25
+
26
+ The audit removal touches these paths in `fontist/fontisan`:
27
+
28
+ ### Code
29
+
30
+ ```
31
+ lib/fontisan/audit.rb # DELETE
32
+ lib/fontisan/audit/ # DELETE entire directory
33
+ cli/
34
+ formatters/
35
+ library_auditor.rb
36
+ differ.rb
37
+ face_card.rb
38
+ extractor.rb
39
+ ...
40
+ lib/fontisan/models/audit.rb # DELETE
41
+ lib/fontisan/models/audit/ # DELETE entire directory
42
+ lib/fontisan/formatters/audit_text_renderer.rb # DELETE
43
+ lib/fontisan/formatters/audit_diff_text_renderer.rb # DELETE
44
+ lib/fontisan/commands/audit_command.rb # DELETE
45
+ lib/fontisan/commands/audit_compare_command.rb # DELETE
46
+ lib/fontisan/commands/audit_library_command.rb # DELETE
47
+ ```
48
+
49
+ ### Tests
50
+
51
+ ```
52
+ spec/fontisan/audit/ # DELETE
53
+ spec/fontisan/models/audit/ # DELETE
54
+ spec/fontisan/commands/audit_command_spec.rb # DELETE
55
+ spec/fontisan/commands/audit_compare_command_spec.rb # DELETE
56
+ spec/fontisan/commands/audit_library_command_spec.rb # DELETE
57
+ spec/fontisan/cli/audit_cli_spec.rb # DELETE
58
+ spec/fontisan/formatters/audit_text_renderer_spec.rb # DELETE
59
+ spec/fontisan/formatters/audit_diff_text_renderer_spec.rb # DELETE
60
+ ```
61
+
62
+ ### CLI registration
63
+
64
+ `lib/fontisan/cli.rb` registers audit subcommands. Remove those
65
+ registrations — keep `convert`, `info`, `dump-table`, `features`,
66
+ `glyphs`, `export`.
67
+
68
+ ### Documentation
69
+
70
+ - `README.md` — remove audit references; slim to "fontisan parses
71
+ fonts and converts between formats"
72
+ - `docs/` — delete `audit.md`, `audit-format.md`, etc.
73
+
74
+ ## Migration path
75
+
76
+ Consumers currently calling `Fontisan::Commands::AuditCommand.new(path).run`
77
+ should switch to `ucode audit font <path>`. The output YAML shape is
78
+ identical (ucode's audit was ported from fontisan's); only the tool
79
+ name changes.
80
+
81
+ Document this in the CHANGELOG entry:
82
+
83
+ ```markdown
84
+ ## 0.3.0 — 2026-XX-XX
85
+
86
+ ### Removed — Audit subsystem moved to ucode
87
+
88
+ The audit pipeline (`AuditCommand`, `AuditLibraryCommand`,
89
+ `AuditCompareCommand`, and all `lib/fontisan/audit/` support) has
90
+ been removed. ucode now owns font auditing.
91
+
92
+ Migration:
93
+
94
+ # Before (fontisan 0.2.x):
95
+ Fontisan::Commands::AuditCommand.new(path).run
96
+
97
+ # After (ucode 0.1.1+):
98
+ bundle exec ucode audit font <path>
99
+ # or via API:
100
+ Ucode::Commands::Audit::FontCommand.new.call(path: path)
101
+
102
+ The audit YAML output shape is unchanged; existing consumers don't
103
+ need to update their parsers.
104
+
105
+ For the CI pipeline that runs audits per formula, see
106
+ fontist-archive-private's `bin/build` (now calls `ucode audit font`).
107
+ ```
108
+
109
+ ## Version bump
110
+
111
+ This is a breaking change → **minor version bump** (0.2.22 → 0.3.0).
112
+ Per SemVer: removing public APIs is a breaking change in 0.x.
113
+
114
+ ## Acceptance
115
+
116
+ - [ ] `lib/fontisan/audit.rb` and `lib/fontisan/audit/` are deleted
117
+ - [ ] `lib/fontisan/models/audit.rb` and `lib/fontisan/models/audit/` deleted
118
+ - [ ] `lib/fontisan/commands/audit_*.rb` deleted (3 files)
119
+ - [ ] `lib/fontisan/formatters/audit_*.rb` deleted (2 files)
120
+ - [ ] `lib/fontisan/cli.rb` no longer registers audit subcommands
121
+ - [ ] All audit-related specs deleted
122
+ - [ ] `bundle exec rspec` passes (existing fontisan specs unrelated to audit)
123
+ - [ ] `bundle exec rubocop` clean
124
+ - [ ] `fontisan --help` no longer shows audit subcommands
125
+ - [ ] CHANGELOG entry documents the removal + migration path
126
+ - [ ] Version bumped to 0.3.0
127
+ - [ ] PR opened against main
128
+
129
+ ## Dependencies / blockers
130
+
131
+ - **TODO.new 06-12** — these ported fontisan's audit subsystem into ucode.
132
+ Verify that port is complete before this removal lands (otherwise the
133
+ audit functionality disappears entirely).
134
+ - **TODO 08** — `fontist-archive-private/bin/build` will break when this
135
+ merges. TODO 08 should land first OR in the same PR-bundle.
136
+
137
+ ## References
138
+
139
+ - `lib/fontisan/audit.rb` (slated for deletion)
140
+ - [TODO.new/06](../TODO.new/06-audit-namespace-skeleton.md) — ucode audit port (must be complete)
141
+ - [TODO 07](07-fontisan-remove-ucd.md) — companion cleanup (UCD/UCDXML removal)
142
+ - [TODO 08](08-archive-private-bin-build.md) — pipeline migration