Repository navigation
TXT1a-1b (v2): real-font Latin through HarfBuzz in the engine, restacked onto INL1a C2 - #102
Conversation
… own change (txt1a-1a..txt1a-1b 926262959, without its regenerated outputs). One conflict, in translate/harness/harness.ts: the shape-transcript replay (ReplayCall, decodeShaping, replayMeasurer) inserts before runEngineCase, whose doc comment master had already deduplicated; the replay and the new comment are kept
…ixed point after 2 passes). Only the translated engines change (the shaping core, script and East Asian width tables, harness replay); every capture, vector, break, pixel, profile, corpus digest and lane output is byte-identical
…cc (NextLine and HandleText located); THIRD_PARTY_NOTICES.md unchanged (pnpm notices:gen)
…room, on master since the branch was written): '-' before a Latin-1 code point above U+007F reads the pair alone (fastBreak), not UAX #14 over the whole run; inline-latin.test.ts's equality with lineBreakOpportunitiesWith holds again ('A-ä' has no break)
…/tmp/heavy-lease.sh pnpm regen (fixed point after 2 passes; only native-gen reran). Only the translated inline engine (Inline, Unions, Strings) changes
…he translated animator on the state machine (mount, setter event, clock step) with frame colours and patched lengths, and the display drivers emit/runtime/anim.ts writes a state program's compiled animation tables (lower/anim-program.ts animTablesOf) as constructor calls of the translated rt-animator.ts classes, held to the generated Swift and Kotlin constructors by test; DragonAnimator runs the translated animatorStart/Event/Advance/Frame over them with every fault off. The state machine starts it at mount (R7, inputs resolved at scale 1 by the engine's resolver, R14), raises one event per setter call (R4) and moves it by the script clock (R2); build() draws the frame's colours (background, border sides, text runs, with the R9 closure) and input() patches its lengths (R16). A mount renders a clock step when its tree is next read; the display drivers (CADisplayLink, Choreographer) run only on a mount built with display: true and only while the animator is busy, so the lanes never read the wall clock. The host apps carry every frame case's state program with its tables (anim-cases.ts frameEmits), so native:build compiles the literals on both targets.
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 7a30611 Run: https://github.com/compiled-run/dragoncss/actions/runs/37397616241
…) into txt1a-1b-v2 (local prep; lane-contract queued-parent exception); no conflicts
…prep; lane-contract queued-parent exception) Conflicts, resolved keeping both sides: - layout/src/unsupported.ts: #101's text-script (after text-glyph), master's grid-layout and TextRefusalCode, and #102's text-shaping-run appended last; #102's duplicate text-script dropped. - layout/src/text.ts: both imports (ShapedItem, TextRefusalCode); #102's non-Ahem refusals in the font data measurer carry code text-glyph. - translate/harness/harness.ts: both import sets; runEngineCase reads both optional keys (shaping and viewportDirection) and keeps #102's transcript replay and master's scroll suffix. R4 on the real-font path (#101's latinScopedMeasurer): - ShapedItem's failure carries a TextRefusalCode; latinScopedMeasurer forwards shaped() with the same Latin check, so the item real-font line breaking reads refuses non-Latin text with text-script; inline.ts shapedItemsOf reports that code. checkShapedText's refusal uses #101's spec section. - The harness wraps its replay measurer in latinScopedMeasurer, as shapedMeasurerFor does (engine root added in translate/src/generate.ts so the harness may reach it), and refuses shaping plants only for a case without a shape transcript: with one, every plant acts on the replayed measurer. - decodeShaping refuses a face listed twice and a partial glyph record. New test packages/parity/test/text-latin-engine.test.ts: the engine breaks all 1,120 Latin gate cases (Inter, Roboto, Noto Sans, Lato, Lato Bold) as Chrome 145 does, line count and every line width; seven plants caught against Chrome there; R4, the Script_Extensions refinement and the run refusal on the real-font path; the harness replay equals the Node host on 45 inputs with and without each of the eight shaping plants, each plant changing at least one. Generated outputs are stale until a regen on CI after #101 merges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
state.ts conflict: the box background write keeps master's dragonBackground paint writer (rounded fill) and feeds it this side's animated frame colour, as the Kotlin side already merged; anim-runtime.test pins both on both backends.
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 5fb1369 Run: https://github.com/compiled-run/dragoncss/actions/runs/37873130074
…flicts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 9550680 Run: https://github.com/compiled-run/dragoncss/actions/runs/37877635236
`vitest list` (the test-files workflow's collection step) runs module scope but no hooks, so their module-scope mkdtempSync left dragon-states-* in TMPDIR and failed run 37877256593. tmpdir-guard.test now plants the module-scope case under `vitest list`, proves the beforeAll form passes list and run, and scans every *.test.ts for a module-scope mkdtemp.
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 4447c5f Run: https://github.com/compiled-run/dragoncss/actions/runs/37879039149
…ibe-scope ones into beforeAll The review of 5e9fa90: the indentation scan missed describe bodies, which `vitest list` runs too. tmpdir-guard.test now walks each *.test.ts with the TypeScript AST and reports a mkdtemp/mkdtempSync (direct, or through a local helper) that runs at module scope, in a describe body or in an inline callback, but not one in a hook or test. Planted: scan-cases.planted.ts (7 reported, 8 not) and describe-scope.planted.ts (leaks under `vitest list`). It reported 16 sites; each now makes its folder in beforeAll with a guarded afterAll, no assertion changed: floor-merge (2), sorted-merge (2), calc-probe (1), land (5), merge-train (3), pr-review (3).
…rywhere; prove real-font min- and max-content against Chrome Independent review of #102 at b1aa3d5: - TextMeasurer gains hasFace. A face the measurer does not hold is refused with text-glyph at the node that names it before metrics() or lengths() is asked: the strut and inline boxes (inline.ts), and ex, ch, cap and lh in the environment pass (environment.ts, which now runs inside layoutWithFaults' refusal handler and tracks the node it resolves). Before, these threw an untyped Error. - text-latin-engine.test.ts: each entry point (strut, inline box, ex, ch, lh with line-height normal, the strut at DPR 2) is refused on measurerFor's Ahem measurer, a shaped measurer without the face, and the harness with and without a transcript; real-font max-content equals the gate's Chrome nowrap width on all 224 face, paragraph and size combinations; min-content equals Chrome 145 measured live on those and on 6 soft hyphen paragraphs, where softHyphenWidthMissing is caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 1b5d816 Run: https://github.com/compiled-run/dragoncss/actions/runs/37886196195
…e PR conflicting; no source conflicts, generated files regenerate on CI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 5c647f7 Run: https://github.com/compiled-run/dragoncss/actions/runs/37888811923
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: f325e4e Run: https://github.com/compiled-run/dragoncss/actions/runs/37889487728
…ith the caller's measurer Re-review of #102 at 3d204e0: scrollMetricsWithFaults and hitTableOf re-resolved the input through zoomInput's Ahem measurer, so a real-font input with ex, ch, cap or lh laid out and then threw. layout.ts gains layoutMeasurer and resolvedInput, the resolution layoutWithFaults lays out with; both passes use it. A refusal there is scrollMetrics' typed refused result and hitTableOf's HitError, never a throw. text-latin-engine.test.ts: scrollMetrics, hitTableOf and the harness's scroll suffix (a line with a shape transcript and a viewport direction) on such an input, and their refusals for a measurer without the face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: eda3b86 Run: https://github.com/compiled-run/dragoncss/actions/runs/37893548724
Commands: pnpm regen --skip lanes-host --skip tw-sweep (macos-26) and pnpm regen --only lanes-host (xcode-27), alternated to a fixed point, then pnpm regen --only tw-sweep (macos-26). Base: 7550264 Run: https://github.com/compiled-run/dragoncss/actions/runs/37897002598
|
LAND-READY Lane receipt (PM session A): base retargeted to master after #101 landed; master merged (#101, #210, #208, #198, #216, #218). pnpm typecheck pass; all of packages/layout/test plus dragon registry-claims, profile-index pass (local, 49 files, 1457 tests, at d542a45); ci:regen https://github.com/compiled-run/dragoncss/actions/runs/37897002598 (regen commit d542a45); ci:test-files https://github.com/compiled-run/dragoncss/actions/runs/37901099669 (tested d542a45 at d542a45: 36/36 files incl. native-swift, native-kotlin, native-dpr-swift, native-dpr-kotlin, translate.test, text-latin, text-latin-engine 34 (1,120 Latin gate cases equal Chrome 145; max-content equals Chrome nowrap on 224; min-content equals Chrome measured live on 230), text-shaper gate and the fixture-wide set); pr:review 102 --once exit 0 (UNREVIEWED: Macroscope spending limit). Reviewed size ~174 KB, one theme (stated in the body). Native equality for the real-font path is proven in TS only here; transcript vectors are TXT1a-2's per notes/T084-txt1a-2.md Phase F, and no profile row claims native real-font support. Independent Claude review history (Medium or higher only): at b1aa3d5, three findings (unknown face crashed layout instead of refusing; real-font intrinsic sizing untested; translated real-font path not exercised natively), fixed in 1b5d816 with tests or scoped to TXT1a-2 with the spec citation; at 3d204e0, one finding (scroll metrics and hit tables re-resolved with the Ahem measurer and threw), fixed in 7550264 with tests that fail without it. Re-review at d542a45 is clean: {"pr":102,"head":"d542a4541951f76cd31ca1478827095d44aa4cb2","findings":[]}Generated by Claude Code |
…er with #101, #210, #208, #198, #216, #218) into txt1a-1-v2 (prep, no PR changes until #102 merges). Conflicts: ios-layout's Lowerer carries both TXT1a-1's lowering mode and master's propagated viewport-overflow element, and lowerTree passes both; project.ts checkCases takes master's @Property registrations and checkComputed faults argument and keeps TXT1a-1's font deferrals beside master's native-scroll check; the band passes pass both the shared font deferrals and the registrations; native lowering runs master's per-band loop in native mode, then TXT1a-1's engine-mode lowering of the native band's cases. Generated outputs: this side's, regenerated after #102 merges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen; pnpm run parity:devices; pnpm regen
… for the text-latin vectors, and the audit's fixes - text-latin-engine.test: #103's referenceShapedMeasurer registers every vendored face, Lato included, so "a shaped measurer without the face" is now an explicit Ahem-only shaped measurer (ahemShapedMeasurer); a new test pins that it lacks Lato and the reference one holds it. Intent kept: each refusal is still judged on a measurer that does not hold the face. - line-breaks engineTextLines re-resolved the input with zoomInput (Ahem) after laying it out with the caller's measurer, so a real face's ex, ch, cap or lh in the break check threw text-glyph; it now uses layout.ts resolvedInput with the same measurer (exported from @dragon/layout), with a test that fails without it. - inline-box-placelines.test (placeLines == placeIfcLines on every vector): the text-latin vectors name real faces, which the Ahem measurer refuses on both sides, so the check passed without placing a line; they are now measured by the HarfBuzz reference measurer and every placement must succeed (28 shaped vectors). - text-latin-capture CLI: an unknown argument exits 2 before it deletes anything (a mistyped --vectors ran the live capture, which first cleans the committed captures), and a case that writes nothing (no web CSS, a vector of a failing case) exits 1 instead of logging; test added. - The text-latin vectors get a regen step (text-latin-vectors, after dpr-vectors, from the committed text-latin captures) and a .gitattributes line; the captures stay MANUAL (live Chrome). The vectors predate master's grid keys, so they must be rebuilt by regen, which a cloud lane cannot run by hand. - text-latin.test BASE pin: 569 -> 664 layout cases and their id hash, the FIXTURES of #102's head d542a45 (identical ids, checked on a d542a45 worktree); intent kept: TXT1a-1 changes no FIXTURES case. - parity.test: the fixture-file existence check covers the text-latin fixtures and probes too, not only the font and env ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
Commands: pnpm regen; pnpm run parity:devices; pnpm regen
…103's parent merged; no conflicts, generated files regenerate on CI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WSkSiXMPXTAsNpS9BD9LDR
…#216/#102) into ANIM-b1 part 2 Conflicts: state.ts keeps this branch's hooks-only form (the adapter in anim.ts draws the background through dragonBackground); anim-runtime.test keeps the hooks test and carries master's check that the frame colour never goes straight to the platform property; device-jobs.ts imports both ANIM_LANE and isBlockReason.
…on-runtime-evd0n6: GitHub reported the PR conflicting; no source conflicts, generated files regenerate on CI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019gAHotrCwxo86PAgtrhRUT
TXT1a-1b (v2): real-font Latin through HarfBuzz in the engine. Second level of the text stack; #101 (its parent) has merged, so this PR's base is now
master. Host-only: device lanes run in the landing driver.What changed
GlyphShaper. The engine then breaks lines with Blink's LineBreaker over the shaped items (shaping.tsbreakItemLines, porting ShapingLineBreaker::ShapeLine, BreakText's hyphen retry, HandleTrailingSpaces and HandleOverflow). Real-font intrinsic sizes follow LineBreaker for max-content and HandleTextForFastMinContent for min-content.latinOpportunities(inline.ts) gives the soft wrap opportunities without linebreak-data.ts, and is proved equal tolineBreakOpportunitiesWithon every Latin code point it covers.TextFont.family/FontSpec.familyname a face id:Ahem, orsha256:<hex>.validate.tsaccepts any string.text-glyphat the node that names it, at every point that reads it: text measurement, the strut and inline boxes (inline.ts), andex,ch,capandlhin the environment pass (environment.ts, which now runs insidelayoutWithFaults's refusal handler).scrollMetricsandhitTableOfresolve the input with the caller's measurer, throughlayout.tsresolvedInput, the same resolutionlayoutWithFaultslays out with. Before, they re-resolved throughzoomInput's Ahem measurer, which threw on a real-font input usingex,ch,caporlh.scrollMetrics' typedrefusedresult and ashitTableOf'sHitError.line-breaks.ts(engineTextLines) andexpected-dump.tsstill callzoomInput. T083J keeps those Ahem-only, as they mirror the device runtime, and T084 Phase F moves them toshapedMeasurerFor.TextMeasurer.shaped()exposes the shaped item that line breaking reads, andTextMeasurer.hasFace()says which faces the measurer holds. The font-data measurer holds only Ahem.text-shaping-run: text across an inline box boundary in one face and size, which Blink shapes as one run and Dragon would shape leaf by leaf.GlyphShaper, and validates the transcript: a face listed twice, or a partial glyph record, is a harness error.docs/research/text-spike/italic, 400 cases, exact).The two Latin rules (R4), on purpose
Both refuse with
text-script, citingnotes/T056-txt1a-spec.md R4, and thelatinCheckSkippedplant skips both.latinScopedMeasurer(every face, including Ahem through HarfBuzz) is the spec's literal rule: every code point is Latn, Zyyy or Zinh.inline.tscheckShapedText) runs first on real faces and is stricter. It also refuses a Common or Inherited code point whose Script_Extensions exclude Latin (for example U+060C), because Blink shapes such a code point in a run of its own.#101's rule is left as it is, so Ahem outputs don't move.
Merges
unsupported.ts: TXT1a-1a (v2): Ahem through HarfBuzz, and the zoomInput platform rule, restacked onto INL1a C2 #101'stext-script, master'sgrid-layoutandTextRefusalCode, plus this PR'stext-shaping-run.text.ts: both imports. This PR's non-Ahem refusals carrycode: 'text-glyph'.harness.ts:runEngineCasereads both optional keys,shapingandviewportDirection.ShapedItemfailure carries aTextRefusalCode.latinScopedMeasureralso refuses non-Latin text inshaped(), andshapedItemsOfreportstext-script.latinScopedMeasurer, asshapedMeasurerFordoes.Native coverage of the real-font path (not in this slice)
checkShapedText,latinOpportunities,breakShapedLines, ShapeLine andbreakItemLines.notes/T056-txt1a-spec.md) requires TS, Swift and Kotlin equality through transcript replay. The slice that delivers it is TXT1a-2:notes/T084-txt1a-2.mdPhase F, "corpus-dpr.ts appends one text-latin suite", with the text-latin vectors from TXT1a-1's registry (TXT1a-1 (v2): the text-latin registry against Chrome, restacked onto INL1a C2 #103). TXT1a-2 (v2): native real faces and the text-latin cases in FIXTURES, restacked onto INL1a C2 #104 does this (textLatinVectorCases).font-family:Ahemonly, and native-mode lowering still refuses other faces.Outside the spec
packages/translate/src/generate.ts: one engine root,latinScopedMeasurer, so the harness may reach it (the translator lets the harness use only what the engine roots reach).packages/parity/test/text-latin-engine.test.ts: new test file.Tests changed, with reasons
packages/parity/test/text-latin-engine.test.ts.text-latin.test.tscatches it on T082's Chrome Ahem runs.text-scriptrefusal fromshaped(), andtext-shaping-run.text-glyphat each entry point: the strut, an inline box,ex,ch, andlhwith line-height normal, all at DPR 1, plus the strut at DPR 2. It checks this onmeasurerFor's Ahem measurer, on a shaped measurer without the face, and on the harness with and without a transcript. These tests throw without the fix.scrollMetrics,hitTableOfand the harness's scroll suffix (a line with both a shape transcript and a viewport direction) handle a real-font input withexandlhwidths. With a measurer that lacks the face,scrollMetricsreturns a refusal andhitTableOfthrows aHitError. All three tests fail without the fix.width: min-contentblock, on those 224 combinations and on 6 soft hyphen paragraphs. There, thesoftHyphenWidthMissingplant is caught. The live max-content also equals the gate's nowrap width, which checks the method.packages/layout/test/inline-latin.test.ts.latinOpportunitiesequalslineBreakOpportunitiesWith.shaping-gate.test.ts: the engine decides every Latin gate line (1,200 cases) and every italic line (400).text-shaper/test/gate.test.ts: the R6 italic gate.font-data.test.ts: the old reference measurer gains ashapedstub.text-latin.test.ts: comment only. It now says the three plants act on real-font lines (breakShapedLines), which Ahem text does not take. No assertion changed.What passed (head d542a45)
pnpm typecheckpassed, locally on d542a45.packages/layout/test, pluspackages/dragon/test/registry-claims.test.tsandprofile-index.test.ts. That is 49 files and 1457 tests.pnpm ci:test-files, run on CI at d542a45 (https://github.com/compiled-run/dragoncss/actions/runs/37901099669): 36 files, all passed.pnpm pr:review 102 --onceexits 0: CIcheckspassed and no finding is unanswered. Macroscope skipped this head at its monthly spending limit, so the PR is UNREVIEWED..macroscope/ignore.md. That is over the ~150 KB guide; about 35 KB of it is the new test file. It is shipped as one theme rather than split.🤖 Generated with Claude Code
Generated by Claude Code