#588

feat(ui): allow per-mount text renderers

Openthejustinwalshwants to merge intofrom

Applications cannot supply their own text metrics and GPU drawing without changing Warren internals. This adds an optional per-mount createTextRenderer factory while Warren retains layout, boxes, clipping, paint order and GPU ownership.

System

A backend implements measure, prepare, draw, and dispose. Layout uses its synchronous metrics; paint records text runs between quad ranges; the renderer invokes text drawing in the existing pass with the viewport and ancestor scissor set. Each mount owns its backend and disposes it before its device. Runs may include offscreen text whose intrinsic glyphs overflow a fixed layout box; scissors determine visible output.

The motivating integration is @pmndrs/glyph with Slug and TypeGPU. This PR adds no library dependency and leaves existing JSX and the built-in default intact.

Change

ElementChangeWhy
UI mountOptional asynchronous text-backend factoryLoad fonts and prepare resources before mounting
Layout and paintPer-mount metrics and ordered text runsMeasure with the same backend that draws
Warren rendererPrepare before acquiring a frame; interleave text with quads; restore quad statePreserve ownership, clipping and painter order
Documentation and testsNative API contract, failure recovery and disposal checksMake the extension's limits explicit

Blast radius

Measured against main: 9 changed files, 9 direct dependent files and 6 changed exported signatures. Existing callers retain their defaults. uiTagHost.ts carries UIMountOptions into registered views; ui.ts consumes layout/paint; entries/ui.ts re-exports the UI API. No browser UI renderer changes.

Verify

  • node package/node_modules/typescript/bin/tsc --noEmit --project package/tsconfig.json: passed.
  • With Hutch configured, node package/scripts/run-bun-test.js package/src/sdks/main/ui/__tests__: 140 passed, 1 existing Windows-keyboard skip, 0 failures. The child-process fixture covers prepare/encoder/view/draw failures, next-frame recovery, pass ending, state restoration and disposal order. A separate scroll regression preserves intrinsic text overflow outside a narrow layout box.
  • Local macOS ARM64/Dawn integration: actual Glyph/Slug readbacks at 1×, 1.25× and 2× verify text, retained updates, removal, physical scissors and quad/text overlap. The application typecheck and native build passed with this text-only hook. Additional native ownership checks clear all 57 tracked reference handles at each scale; four application tests cover live-root GC, root teardown and derived-resource cleanup.
  • Reviewer: run the package typecheck and Warren suite above with the supported Hutch toolchain.

Risks

  • The borrowed device/pass are Electrobun's partial native adapter classes, not the complete browser WebGPU interfaces. Third-party integrations still need a compatibility adapter for methods and native reference lifetimes; casting alone does not retire objects that expose explicit release(). The docs state this explicitly.
  • Backend metrics can change layout. The backend must measure and draw consistently, set its own pipeline/bindings, respect the supplied viewport/scissor, and release only resources it owns.
  • A thrown draw discards the command recording and resets the pending surface before a later frame. Preparing resources occurs before surface acquisition.

Evidence

Same JSX, Inter font family, strings, sizes, colors and 380×180 logical scene; both captures are native 760×360 GPU readbacks at 2×. The baseline uses main at c54c0c1 and its built-in CoreText atlas; the proposed hook uses Glyph 0.1.0's Slug backend. Each backend supplies its own matching metrics.

Before: built-in atlasAfter: application Glyph/Slug backend

These offscreen captures use the same native adapter from #585 (bc055406) and explicit physical attachment sizes. They demonstrate the text extension and are supplemental local evidence; they do not test automatic window DPI selection from #584.

Unknowns

Windows/Linux native integration and window presentation with the isolated text branch have not been verified. The local demo additionally combines the separate DPI/WebGPU proposals and its existing first-frame startup wait; those changes are outside this PR.

Refs #587

Agent context
Review the text extension against the linked issue and measured facts. Confirm default behavior, matching metrics, painter order, clipping, borrowed-device/pass ownership and failure recovery. Treat import-based reach as a bound on static visibility, not a proof about runtime calls.

Facts: gh-wat-dis 0.1.1 impact --base origin/main --head HEAD
Sources: git, ast-grep, manifest. No CODEOWNERS; coChange is empty.
Full facts encoding: gzip+base64; SHA-256 of decoded JSON: ddc75345de24876a2909e69be8ad2cae8383d9adfb082e0a394c8dbc4bd40ee4
H4sIAAAAAAAC/+1d63LbuJL+n6dA+c9xTvl+TezNTHkcZ8ZnnMQVO+PanZNyQSQkYUwRXAC0rJOTqn2afbB9km2AF/EC6kKLEkUrP2IRBMDG1w2gu9EAvr9CaE1YXdLDaydod0M9trAg8LBmHR5YO9buwZvjo4PDQ/tgH+8d4vZO+61lH+zig9buAdnbfXN4sKZLdQm2VamD/bZ99GZnp3X05hgf77/d3T180zq0dvbbePf42N639lp4p0XsoBTHbkd97Ds8hJ/+QtqqHsZph7rbPUxdnTX8RPj2t4uz91Fyn/EH6nZuOVE1tbEjSPjGJp7sQto+PP4Ivkc8JqhkfKBq6UrpiZPt7Q6VXb+1ZbHedsvB1kOLYW6L7jZxiCU5a/kBCWuSEi6G1ALx8PCnfoBHqCUkCR6wkJsd+NowpYdd2iZCrumEbyGJDgDg4w4RyZr+wo9YWJx6iQq5LxJPcuCRMEeqOt+FotTBLYckKvwe/lUFGXM0c+FfXJ0GBgummrPmMomoKyR2HGJvoS++e4Jc7wl1upt9LDdtKpDNLIAQbW6q2kSUey2s7UdAUYy58Hs9rAEPcWtTh4jzrmK9Epm3Ie1QDeGSMldhcXB8GDPRIVHq/nGY2Br8Tl07rlJlY5aIRFgnCOZzS6FwPERNwQ959POPsCqovkdcmSBoL/oy5cD/98Qjrq1yJGglTx7jktg3tONi6XMSlFZZjuKG63bGbIgp9bAWSk3xtuDWtsVcCfVv64ShzG1jj4ptn26zR8IfKelv9eynoQgA5tJX1a/1mE3bNOxSBij34vQklgdx6kMAZQBhnBpJJrxxfceJ00O88gVY3w26x5/f4jSr63P37Y6d+l7Xdx8++K4VUZLI73ForItdzThAGVsJoY85qrtakok5bD3oxUC7hlfYD0KPIwrL+/s2fVIME/f325I8yS+KuZzwLSlM2GLbLgR29+DYiOxODtmQchO2ya5swjhsycQw79YEZtXX8hhDYhmgd3YnBFr38HnAvFcLmKHV5KkA0DGjwlH9RHcEpsOJKhh50XfUgxkL+vEtyBf6gdqc9dA/17a22zCY/nPtNJqM5sgMBw+YL8txY/fQyI7j5WHHmQNT4Qb6B6gptD1AP06T2gXMcAK0CnHOHL/nondoXYK2doK+UqW1bSBqq0mm1yL8NXr3E1IvtzpEXnPmrVN7A6kfW++pevvuHdpN1d0OCYsEorhq0BZQP3o6Rd3oJ4jPd2ONHiiDZD35KmxunANURs+X5Eqzfn0BQufB35Iyt2/WCw5rL3O0F8oclg4WF67kA+Cz+IQlfdTjwZmlfiVHBTULqVEhJ5Ofry8+3Z9fXV6foHOHeiCa39HTCdrcJccbaBD9AKnZU3+7+m9GuGNhAE5A20B9UzWt46DGDdQKfrwOv/DdIE+6ZBuEDV0rfv7it9sglqmcFjRVoEulaoM03XEqszmGYqvqUDisF7/+xOzRkq1zpfoSNCBN3fylnY9W1sYI/PFbo8Dvvl0eiefwJbCR70ir4/nnym5IzX5b2/3kmy+kQwX0jozk54TuK400NKN0YjFwrcSYB7aiJMMyixj2nqm5HxytFHczsD7tQ9NZv2QPM8K6tzz9K5DtP8DY/sh8V17RNrEGlpOaSx5zb8f2r0ud/7OnP53uY5nO1VMZZ9GjXoWl18Sg12KOwQ/h4p4u09Lj+VqOSXqCTPAgdHmkfWwa7NCJl+1yIu0cyZd0qEvSYoPbgFn8cRSQlpp3htUr98oMbP1RsmUnfT+F3RA+wJzHVMtjhsR+QGN3HLKAOjJ2dC6EB7vFPMjQ1iDcrWCeXCju+0XA54hrEPA2eaTJCWD+uO8V4p6lrUmwc9z/gKkDtNZyrDHS1yD8iWuB3cXrzIIiEpvEhUf9jVqir0k7QaCquqDY/vntz2+NZAGoxWoR9hw7Tj0ZkSQwwY5GMsPDYrFM2CviQpqyJkEOX6Fu5wNXz3WUfzOBTeIA9YhCoZ7oh8TFI08zWQA5MCd11oaKSGwQFyKf+iLxL7TF8sQ1CvmsHzRCPnIHzgX8g2LwA/rUeq2i7AQJyWFSUGu0jWSIsKgQjNdyJAppi9asG6qKLtwbd1SEv6JMr4Qm4GskC9TyRp2nZCN9DcK/SMQiFnASoG7kguR+xUyIA37SdEYLZH/bSqH7tylYlA9mqx87PgRugWXhSkhu05nju7VniO82nAlqzTyNdz05EdHZWHakQhKnNC0KuJGP+jAwJFU2b9i1SJtxkqQgEzyJ0nGbfWrLOEJzA3UJ7XTl8Pn1CXpkNEFRzO+ZVK9E4mMQT3qCbocPYAol4o4NZIwVGkOU8ORSE4eNDHf0TKb8RNH4XSpzQfjjaAjDfN7Tdjvb70YJcJzlx8azyY5AWz7Kw/jc5SNcWJw5zmabWb7YpK63nPCP3H0yHf3J8W1e5Pt0eUDPzYZ1hdcU4lg1rWPi+Z4/64ez0oxcieUn/N2dUTP+M7ZojJrpo2pRUb3FE7qewsd/s9ppvcIlFrWDZeFCcTxKJoJNNiNY9zT8ORj+7CeUt6nUwpl+b6RYzVMzrE6EEq3KC5IK1l6UO1B9O6ObqyTWTqrop8vPALVrKI98HF0+D7PaEKAxjG4PdjUNu83psNucGgfW06lG1vwOt8rtpSL/8NJrCentYwb3vpMKuJnVpDCuS1cqA1XGTQFVi/awjJ5bx2ziGzlP5spuIOgXhLvY0Zuu3wV8fV12e8YCOvbKLl/Z5SsLdznnLuMg8yxt6BlWrmHQLdjDjWws8Qn64DAs9/fOOMeDU2SpXX5GdcigX6WrUz1H/ByOueHS0p/fTqf8TD0H5wmW6Ova5XgT1cX4IIF5ehAKlMWdZdcWkx122gFsDiF/mfEmWrRWGp9ri8Qy9ndkgcEXHGZxirb//nd1KAXhEskuFXp4QsGgGCT8t4/t4IQ4QBJJpkJqIfsjCRRMGNMYh56zhf6+HZaLjJT5D1wVLspmzkxYtNWQOBNuOIONO+cBScw7BCaUXz3/TisY6N/o7tfrr2pNfQPEgmB+zhwVpBgEisLryFFGXSopdm7ov4j2tyaXX08zy6+AonKfXXPWo4L8x5CCnwzzZJ2IDj6eDMD5+cQckTO+gaVmnNUk/fL04lGDSq3U4uQpM2Fg/7rhgIcxsRmhX/1URadDv1yfLLNNhccEWY8TRqvcGVKxzVxnoOe2XIc+rVNTqh40qg38vikKvy8zN5ZVjd6OcIkliFwfKkEbOiRZ0XOSiTF7PQVnZhCT/DK9Mo20vMYFgc919edo9FCZCgP/Plzj1xsmAp1qAwmtQ41d5Q8Miutg1x3yPYdhMD6wa8MYKSkkRXuSBIIJJrA2cEsAm4NoTm1ytNWmVW1QhNv31rnvipPhQB4bMzCgFXfeaHxVFL3nuK8OdGLwBYJajHPWJ7benwwjMkPq0GygEEQSUbmBAA39F0gUfqtHJTwBQegOcxBXJIgUuqLo27qFakSJTCeCra4+DUM3Q/1YV98CLfb6a4D1NTxeBGc1bKD/+5//XZKhZom1x6Vx/76AEbFwH0ZhQMKMx8SDgngE8/6Ld2g9PM1JDX3BAUO6L7/XP0/VYNbDMmeCgnKnD8TNDLL/TpiOyRc/nS7FKLDqSCvb9tn7e+aqAb2ZQAPSrtFhHNkpSipApykFSK3MOMO9vKcFsTM/Vr15lj0k5xluSPco3nlVNysh3ns1xpd6ijz6RJy7dB6d9lsm46qPLCjeXC0tL35Z8Ghy7htPNq7F/snU6cB1d+JmjjIO7HW9kmer0+tA2dXmNxiyXaBfW7n6Oge9vgcQdziUt38eToyqAn3MFJLUegAjG/XAztWRTchjjoOw08cDsRH6WZG24PtdMKNtyuVAV6xKflRREZNFVBgbceN7XujkjXwYSjS0aa4McE1saMAHpr5AQcSwzgLKPAqOou5STxM1+WrQaf1RrLp7ldZ6b3HnNzbn+LfZja7xKd0pNL4n7x8z3+M2Ghmoh1Mi9P4xkTqafBQq4THoaX6gSaa1OPe3RG2PFGdr2tqKi5nLJA4rTyZTcRtc3ZZhVgb4LvE5FSDG8VV0G9MiN8Fu2RmDONx1MAWKQaGxGCZLpfeFG0vGMKcVjjEd4pkg/yWeqgc51v2nwDgI7y4lpvPEr3Bv9EpOZ4pzUcR4ZTBvVCbZTvpsiOkEvphXGYILK1sI+yaKm1/xcil4adwWv+JdXcbMirXO6jljZsxzFILReuuMUDeaQjMGP2nmTY5nVGoqoc74RUZL9ZQApy7Nlhy7gqorA8221mgE9SaL1NGR6rJvMC/Dy8d3C6kKvQx20cXZWd7EniMV6lAsCxbjZDuOdk3Z45KNKRYFxmZKXYyw4yW7NJrS2r+VuWcrqj5/v1W/yxwywkfou1bothpndBt9bZUgFlzWEPy56FEpM27l2eFmkzb2HdkM2IAAa9vVl3VWBFe7TesIVUHnWgnYbFFbydekSBkvC60IM9PNpTUD0ew1HYmg8RTIiUFLrb/EmOXRKFjjqRSM4l60UiFmBNdLH94nPpZ7TA/Mb/l7Zg8cApfcdD41eJlIivoBx81lZoRdbs9j8wAcUW5GIGY2AtQMwlELZeOw02UqGe2g5vMufKWOw91zEDOcTza7zmryIDYFOB06UY2sfWI2+V0F6iS8PNececnn4PSkOoNqWoMdByqU2eS+K2mvKmw/JPYCqkeOO7reRNpHLK1uMuGmy/qp5z7N5Ph6GdGX8iMak6GNTWNbhcqSh7kgei9900ADTcaq0J8QaEpfGEsJn6Mc0kk3uQrgc1IpUsXnNQzr1WBdAag5l8asIP1KzzPX1Cc0f8MrnyafYHDuDrPVGPOCIJtxsLeZW5WSmzjjtoHAVanqnjkwkCal8B++kLQ9SC/qLYk6XBLfCgfZSeBtxkBsjghb4JjQcQZe94s/XCxvDmor27c0dBVq/B+uPp/d3txfX3y5v/x0c3v26fwi2YXTByQvjYVQDuaV3vr8CMyVz7BiAB/IoIe9ijD8naQm+Y8sJbBYba+6sKnMZAP0+AfGM6muOhDBof8iYYlz6AIN581qipsNjoue75qN7soVtjDolZPkMihZCfZx/U3HcaWpVY7xyuH4PBaMPIpxiTWJ+cQjPAu9SlfISikISwBa9ZEwyRNiG4ngHF2yVU5T84HasAVw5TyYMWArg7Qcbi8+xKIEZiuzcm5QVzjR6DmkWWitDJkykIdbZsVq60g1cL3EPW5lcJrDzOIQ7PopzZm6hMtmgFfZ6HcGjWX8izrfLIHcl5BXybQOkUXjYe0gzpsYC52Jl8DpaNo9WUaKZzZHkJa6lCGsTqGYOkzjruP5N74+RTL3rh8UTSUNszcA+pcazTg1UC/R0zA1SPPfP8J4rs9OtKdEJ+SK5naawHM+U277SZCSy/j18rxLHfs5G1XSj6na9WnNwQeWX3DqHcOy/PiuYq9ni+cq/rIaXCs27M4si6SuI4S0K/jcL0AodTvjzJYbos5zSKUoX2I+uZWdbgwWpdl9GdiZqcnhKkNF1sPZIz2W8m8S+Z48flTBdcvoB51aZqoOobmN6s9N8DUOrimF4lyPPdgoepO9BwyZ7gxCuZtSarWoOT38Fa4Uf7393JSNOlPjOmdn1y/sKTdKgAZ5oY4DunTbqXH6munbK4zvlGTn7YkpVxeKvGzMzai66hLNpV2WKCEShkNVZ7kIdKkm9fDoNZy6OUdjrdIJD3I1As6+6azIWU0al39k/G9fL4OzKU1p2ft2hv0jV02Ufpc6xLZOfMhrNKtjx2YGWPWLbTcgYSTV9aGpLYeoFctoShDm18mZwZRFi6yaIvI5arnENx1rqnYHLcc2pKkxC4T5Cg8I/5WwHqnu2E8vEM9bpsbUK2aBWbn88FWmKKbUv9rhVOyxX6SfZpxPpikwViZ0JjvEaFPUccXd6Kwe67yodhWzESitDggpC/b4ybVOXogaW+umhZNFBSj8evWf17/dn73/I7vGErz4LeVLqe8xLFNDWuFoiaWDxYUr0+5bKj7p/qNmoLOazuClUJyLw9x36+Xbnh6p1d7cKU7qLnk3y8wGxaj+pgD2YgPQeYnDz8N16htJvKqiMcFMOk98pRm4LS4eJHmbwZJEhfCSp/JXpUPfXp3d3N9c/tdFdm3/TGkzzcGvtsv7td0GPjXSwe1LZzb2qluBgQlH4elzUm7eeXGA1XbJqiRaoXH/O6nKwd8h8i7zpaaBt7oZzTyprjaLTBFhuLg1zKKg9eVY2yyF56L2fI6IvFXPNxbzTIG2zQB9PquiJXeLGsLWaol6ze6/O3eoVy+tsRRIc1K135NHaqXEERIDYq+xEBeuBUNszcyW6S47XcjeoNp11Okwe6HHAU0HUr0381gOpr2rXGLBPh290TI319tUeEzkdQCYsT733bQXZMKdPbm6uO/eUdnN1VdXPWM6GVnFJDwDaHMc+2o9ZFZ4raKqS4A2hy0BZ166g15+VHvhDcH/xWH/dRVRI3CrJeHZAvaCh8Ap4VqNgNNjNp9z3JJOvzi4qgHorYIQSuM2hyOKkvaHvlDgFnMwds4aInm0wmMEsJTY6tb0uIAyYFV/2NPY05IageMLPpK7DFzz2+dmwfhGLBkeHxiE2n/E4qEh00X97sSoI0oVOzHzJwWNOBaoIYBWGYUWHYcRx6E1ArMKox9h+LzQu8o/NcyKmHfEYxTXWLOVwBLAvbS7BMqANOcdfWXOEJr2uKCan/NThktKb9O+4SvaJtbAcqq9i+SP3OeaAWNQYo5A3hk+WD8ox0rXyss1BsEJBGuF4SgMRwVBrVaNZg3Zy1w3KgdY9asgCq5fOLU7pBGArfZ2zA6+ind33Jm+ldeyA4s6vQfEcBJkpja9cSSdTyeNrW0RO05ehZWvRVe4xEDFzKa2KkeUJ5Wzlu9uPqjLEhIn42mEx+WRA0/nsaBlmCZD4dYksbouc1hnoDJ88YVEFgf4i5rQJT4H4Kk1zCCYz4OXPeyCIiKG0K61qaP5v3t8NMwvsfRFAWC6wHmYeoJ24hc28ZSvAFA6a4OyI4PXxh6SBW0UWHUCaW/eEHFoyWbbYdbDprqYJ49TYYYaSNT80Uq6lWOI1lXyaxMyPY+5yeW3adrdoaYm7867yUBG12/lG72VfdGoVosH6jjC0OrsiypbPe9G28wyNDmdWmF735gaPK65u+Wb63q9fGtTiVUy92De3M1pbFGT86pcdc0+2N8pweQ3o1u9ezSqJ1ucetIg17kXFTZ7/3De3Jak5zkw5RoabnhVZdP3jits+1B/BjPWoDxHltJI1ZgV6YN9QjtdOflQXKDqG6DCoFR1OPFG23fjqc713Zjkw8WQnB4/x9C4txgaDbbrOLQXLQkFJBu68qIpLbYXatvN8jMBy4nyomW2gMbivnW0GDpNk8uCR4F4jlCWKsdW4GQJkoBQ/Ri0I1ZEht6q4rWT+3td+P4+EylBgr0+rxKOozXaU1csUbdzXuoDOjCZZPYQTVDuL/FUqlwUUVmiaBgUV6JkEA+92WaWLzbDQOIS1ajAghQLXoXCCuw/j/x0IfcjzcKiJCETSglyyBOVg2EaU7tHk3nwI8EJb11CSUGYE6SVGdTvMkFQwHyBQpFFkiGMQknTGU+RhcGoRLLLmd/popisAaLuXzBeUuZuQPG2E/0WkoMwIQ/LrkCMow4B8qDX2UjtptcUuEwiMbxAQytIKOhgaF2HzGxz0oMuZb9GqsVQBqpynQGQMaSZtcOmUBfSqUA2bbcR7gDsQqIWFmQjapcdJ6sKugQHjUMOjX03a9D9XEGVHxZlAQPesz5Ush9+G3WZJ04BKV2J7GIZE4Xhk5vkKexSqMdsH7JYahVUILXVBoCH/6hEvlCNAno40m5opIYpFKupay5D55/fX3y++3Tx5SbkRcBqTRSMZVJ797+9+vHq/wGEJUvlFzoBAA==

Activity

5
thejustinwalsh11d ago

Clarified that frame runs may include offscreen text and added a scroll regression for intrinsic glyphs overflowing a narrow fixed-width layout box. Culling by that box would hide visible text. The Warren suite now passes 140 tests with one existing skip; package typechecking passes.

thejustinwalsh11d ago

Additional adversarial review found no new defect in the generic text hook, but exposed a native-reference lifetime gap in the application compatibility adapter. The adapter now retires dropped bind groups, layouts, pipelines, shader modules, samplers and views, with deterministic root teardown while preserving the borrowed device. This PR now documents that requirement; the application adapter stays outside the generic API change.

Four focused application ownership tests pass, including 1,000 groups collected during a live mount and 1,000 retained groups plus their layout released on root teardown. Native Glyph readbacks at 1×, 1.25× and 2× clear all 57 tracked reference handles at each scale and retain the rendering, update/removal, scissor and painter-order checks. App typechecking and native build pass.