'push-button "A package-owned button." :format "%[%v%]" :textui-measure #'textui-widgets-measure-bu…
انتشار: 2026/08/11 10:07 UTCدریافت: 2026/08/13 05:00 UTCآخرین مشاهده: 2026/08/13 05:00 UTC
'push-button "A package-owned button." :format "%[%v%]" :textui-measure #'textui-widgets-measure-button :textui-attach #'textui-widgets-attach-button):textui-measure receives a converted widget and returns its exact single-line presentation.:textui-attach receives that widget and the bounds of the already inserted presentation, then attaches ordinary widget.el behavior without changing its width.There is no adapter registry, no required TextUI superclass and no second control hierarchy. If either property is absent, TextUI keeps using the ordinary widget creation path for that phase.The bundled textui-button, textui-checkbox and textui-field types are examples of this protocol. They are not intended to become a replacement widget library. Package authors should normally keep defining their controls with widget.el.# 0.5: complete evaluation by default, explicit optimization when measuredAfter seeing the failure mode of textui-route-state, I tried removing its manually maintained dependency graph.The default update path now works like this:update textui-state ↓ coalesce updates in the current event cycle ↓ evaluate the complete render function ↓ compute the complete responsive layout ↓ compare named complete-line regions ↓ replace only the regions that actually changedI describe this as:>Complete evaluation, block-level commit.The complete render function remains the source of truth, so TextUI can see structural changes, responsive layout changes and effect dependencies.It does not perform element-level virtual-DOM reconciliation. It compares bounded, named blocks of complete lines. Unchanged regions keep their existing native widgets.In the byte-compiled btop fixture with 1,000 process rows at 120 columns:* Automatic complete reconciliation: 4.14 ms median* p95: 4.31 msThat was fast enough to make automatic reconciliation the default.However, I agree that developers should still have access to a manual performance path when measurement justifies it.TextUI therefore retains this form: (textui-update buffer (lambda (state) (plist-put (copy-sequence state) :selected next-row)) :region 'rows :producer #'my-package--render-rows)This skips the complete render function and refreshes only rows.In the same 1,000-row fixture:* Automatic reconciliation: 4.14 ms* Explicit region update: 2.36 msThe explicit path was about 1.75× faster.The difference from the removed textui-route-state is locality: the state change, target region and producer now appear together at the update site. There is no persistent key-to-region graph elsewhere in the render function.It is intentionally a caller-owned performance promise. The developer must ensure that the updated state does not also affect:* another region* the frame shell* responsive layout* a lifecycle effectThe intended rule is simple:>Start with automatic reconciliation. Introduce an explicit region update only after measuring a real problem.# A few practical widget.el lessonsBuilding these versions changed how I think about widget.el.# 1. Extend widget.el before wrapping itdefine-widget already provides inheritance and type properties. If a package needs faster measurement or attachment, adding inherited properties is usually simpler than creating an adapter registry or another widget hierarchy.# 2. Keep presentation and behavior separateFor layout, TextUI first needs to know the exact visible presentation. For interaction, widget.el needs markers, overlays, keymaps and callbacks.Separating those two phases made it possible to optimize layout without reimplementing control behavior.# 3. Understand :action and :notifyButtons normally use :action. Editable controls report value changes through :notify.TextUI schedules one automatic refresh after a successful :action, but it does not wrap :notify. An editable widget should copy its current widget-value into ordinary Lisp state from its :notify callback.If field contents are not synchronized before a complete