glyph 0.8.0
Everything that changed between v0.7.0 and v0.8.0. 228 non-merge commits; 115 documented symbols added, none removed. Nothing was taken away from a 0.7.0 user in this release.
Upgrading from 0.7.0
Three behaviour changes can break working code with no compile error. go fix cannot help with these.
- Fixed-height containers now clip.
OverflowHiddenis the zero value, so a container with an explicitHeightwhose content overflowed silently loses the overflow..Overflow(visible)restores the old behaviour. Most likely of the three to bite. - A present dynamic width binding means explicitly sized. A binding evaluating to 0 now reads as "explicitly sized, currently zero" rather than unset, so it no longer falls through to implicit flex.
- Explicit text widths clip in both render paths, where one path previously let content through.
Deprecated, removed in 0.9.0
22 renamed identifiers, all still working in 0.8.0. Each carries a //go:fix inline directive, so go fix ./... or a gopls quick-fix migrates call sites automatically - the whole list is a one-command job.
NewFilter -> Filter
Scroll -> Scrollbar
Widget -> Custom
BasicColor -> Ansi16
PaletteColor -> Ansi256
BlendColor -> Blend
LerpColor -> Lerp
BlendColorDodge -> BlendDodge
BlendColorBurn -> BlendBurn
SEDimAll -> SEDim
Bool -> FormatBool
Bytes -> FormatBytes
Currency -> FormatCurrency
Number -> FormatNumber
Percent -> FormatPercent
PercentChange -> FormatPercentChange
LayerViewC.ViewWidth -> Width
LayerViewC.ViewHeight -> Height
App.Push -> PushRouter
App.Pop -> PopRouter
MatchNode -> MatchC
SwitchNode -> SwitchC
Added
Components
Complete- native autocomplete over any field: anchored dropdown, prefix filter, single-router navigation. Esc and arrows fall through to the parent when the dropdown is closed.KeyHelpand named bindings -Key(...).Named(),App.ActiveBindings, live what's-bound-now help styled withKeyFG,DescFG,GapandRaw.App.LoadKeyBindingsreads a user rebind config.Trigger- focusable form control withActivate,OnActivateandBindFocus.termexperimental - embeddable pty-backed terminal in its own package.term.New()forks a shell;term.Stream(rw, onResize)drives a byte stream the caller owns. A VT interpreter renders pty output into a cell grid composited as an ordinary Layer-backed component: SGR to truecolor, UTF-8 and DEC special graphics, double-width cells, the alternate screen, cursor-position and device-attribute replies. The API may change.
Animation and rendering
- Oscillators and self-gated animation -
Osc(hz)value nodes bindable to widths and parameters, withSine,Saw,Square,Triangle,Steps,Phase,Range,Speed,EaseandLerpshaping. Self-animating spinner viaSpinnerC.Fps; effects can request animation throughPostContext.RequestAnimation. - Cache-effects frames - effect-only frames re-run screen effects over the cached render instead of re-executing the template; effect-param oscillators drive those frames.
- Paint-only animation frames -
App.EnablePaintFrames(opt-in): an animation tick that proves nothing geometric moved skips the two geometry passes and repaints from the last layout. App.Apply(fn)- render-thread closure queue drained at frame top, so off-thread state changes marshal safely onto the render goroutine.- Adaptive render pacing - input renders coalesce when the terminal is behind.
Template.ExecutePaintandTemplate.RunEffectEvals- the paint-only and effect-only entry points.App.ForceRedraw;App.SetViewDiagnosticsilences the view-redefine nudge.Buffer.PushClip/PopClip- explicit clip-rect stack.
Layout and text
MaxWidthon VBox and HBox - a pure upper-bound cap: containers wrap at a bound and hug the longest line..Overflow(visible)- opt out of the new fixed-height clip.TruncateWidth/TruncateWidthEllipsis- display-width-aware clip that never splits a wide rune.Rich(&s).Markdown()- bound inline markdown (bold, italic, code, strike, bullet), parse-on-change cached, ForEach-composable.MarkdownWidthmeasures for content-hug sizing;MarkdownSpansand the zero-allocMarkdownSpansIntoexpose the spans.PreserveBGon text and rich text - a write mode that keeps the destination cell background.TextViewC.WordWrapand an explicit wrap text mode; char-wrap chainable on rich text.- New exported types:
RichTextNode,OverflowMode,StyleRange,ForEachFn. - ForEach
Limitand remaining bindings via the configure-then-call chain.
ScrollView, Layer and List
- Feather - scroll-overflow edge fade, native in the blit, fading toward the actual background behind each cell.
Layer.SetFeatherfor raw layers. - Bound scroll offset (
ScrollState/ScrollOffset) - Animate can drive scrolling from a single source of truth. AnchorBottom- hug underflowing content to the bottom edge.- ScrollView scrollbars;
ListC.ScrollStateplusScrollbarDynso a List carries a live scrollbar like a Layer. ListC.BindHalfPageNav- half-page up and down.ListC.ScrollEase- a list paints through an eased row offset instead of snapping to its window;Layer.DisplayedScrollYexposes the displayed offset.- Scrollbar surface:
ScrollbarForLayer, and on ScrollViewScrollbar,ScrollbarVisible,ScrollbarOpacity,ScrollbarOpacityMode,ScrollbarThumbStyle,ScrollbarTrackStyle;ScrollbarC.OpacityandOpacityMode. ScrollViewC.ScrollTo,ScrollViewFn.Height,LayerViewC.HeightPtr.
Input and focus
InputC.MultiLine- wrap long text across lines instead of horizontal scroll, with alt+enter / ctrl+j newlines wired toAllowNewlines.Input.StyleRanges- style sub-ranges of the live editable value (mention or search highlight); zero cost when unset.CheckListC.Selection(*int)- external-selection setter, parity with List.FilterListC.OnFilterChange/OnSelect.
Jump mode
App.EnterJumpScope- restrict jump targets to NodeRef regions;JumpItem/JumpItemRefper-item constructors;App.JumpScopeKey.JumpMode.Activate()/Deactivate().- Incremental label feedback - the typed prefix dims along with non-matching labels; backspace undoes a typed key and restores the previous labels.
- Per-item jump targets for ForEach templates; off-screen targets culled.
App and diagnostics
App.Suspend/App.Resume- gate rendering while an external program owns the terminal.App.LastInputUnixNano()- last-input stamp for idle detection.App.CurrentView();Screen.FrontBuffer(); rich span rendered refs; whole-frame guardrail benches.
Changed
- The example apps moved from
cmd/toexamples/, all 60 in one place;go run ./cmd/xbecomesgo run ./examples/x. - Fixed-height containers now clip their content to the box;
.Overflow(visible)opts out, and the blit honours the clip. WidthPctfails loud on a wrong type at build; static over/under-percent is accepted for animation headroom, and the [0,1] range clamp is gone.MaxWidthis a pure upper-bound cap, decoupled from content-hug;maxwidthpctremoved before it ever shipped.- Present dynamic width bindings mean explicitly sized - zero stays zero, no implicit flex.
- Checklist
Style/SelectedStyleaccept live*Style, restoring theme-reactivity in build-once views. - Explicit text widths clip content in both render paths.
OnFilterChangefires on every query-mutation path with changed-only semantics; a FilterList query change resets selection to the top.- Initial terminal size is delivered as a resize event.
- ForEach per-item caches are bounded - realloc-orphaned per-item state and orphaned markdown span keys are evicted.
- Redefining a named view emits a one-time stderr nudge toward If/Switch.
- An animation ticker could keep running after its view left the screen. Tickers stop on every routing path and retire when the animation settles.
- An async render requested right after an input flush was silently dropped. Requests carry a generation now, so none are discarded.
- The App running flag is atomic, so
Stopis callable from any goroutine. - ForEach item height is pinned as intrinsic in layout; text measurement is memoised per unchanged string.
Fixed
Races, deadlocks and memory
- Two goroutines writing to the terminal at once could interleave mid-escape-sequence and corrupt the display. Writes now go through a single lock.
- Toggling jump mode while a frame rendered could race, or deadlock the app outright. The active flag is atomic, targets rebuild by swap, and no lock is held across a render.
- Scrolling a layer while it rendered raced on the scroll position. The scroll fields sit behind a mutex that is never held across the render callback.
- The render goroutine could read the cursor's scroll position while the input goroutine wrote it. Reads are guarded now.
- Rich-text span strings were stored as raw pointers the garbage collector could not see, so the strings could be freed while still on screen. They are typed pointers now.
- Resizing the terminal mid-render could tear the frame buffer's storage. Resize waits for the frame to finish.
- Drawing vertical rules allocated on every frame. A VBox of any child count renders allocation-free.
Layout and rendering
- A ForEach used directly as a branch or as the root view failed to lay out. It lays out like any other component.
- Scrolling a list could leave a blank row at the bottom for one frame. The window fills in a single pass from the final offset.
- A list with no
MaxVisiblecould not scroll back up once clipped. It scrolls both ways. - Rows partially visible at the list edge were dropped instead of clipped. They render, clipped to the viewport.
- A
MaxWidthcap was ignored when the parent sized itself from content, so parent and child disagreed on width. Both sizing paths clamp. - Content inside an If branch could overflow the parent's width, and flex children in a branch could land in the wrong place. Branches size from their data, clamp to the parent, and redistribute flex after stretch.
- Wrapped text could spill below a fixed-height container. It clips.
- TextBlock wrapped by rune count, so wide characters (CJK, emoji) overflowed their line. It wraps by display width with a placeholder for the cut rune.
- A layer re-rendered when its viewport width changed but not when only the height did, leaving a stale frame on screen. Height changes re-render too, and the viewport height can bind to a pointer.
- ScrollView capped its render buffer at 500 rows, truncating tall content such as a long chat. The buffer grows to fit and the fitting height is cached across frames.
- A queued scroll could fire late and yank the view away from where a manual scroll had put it. Stale pending scrolls are dropped.
- Rich text reported the wrong natural width; jump wrappers claimed more rows than their content; dynamic styles applied one frame late. All three measure and prime correctly.
Per-item (ForEach) binding
- A per-item border colour pointer silently dropped the border. It binds to each rendered element.
- Numeric text, fill and flex values bound to an item field could show the first item's value on every row. Each row resolves its own field.
- Spinner, leader, counter, sparkline, tabs and scrollbar bindings inside a ForEach read from the wrong item. Every leaf binding is offset to the rendered element.
- Tweens inside a ForEach could animate the wrong row, and per-item exit animations detached from their item. Both pin to the rendered element.
- A tween's
OnCompletecould fire mid-frame, so mutating bound state from the callback raced the render. It fires at the end of the frame.
Focus, modals and forms
- Closing an overlay with an exit animation left its key handling active behind it. The modal router releases while the overlay exits.
- Swapping route branches under a modal dropped the modal's handlers. They survive the swap.
- Escape could blur a field and have the focus snap back the next frame. Focus managers push on the show edge only, so the blur persists and reaches the modal router.
- A form could validate stale values because bound inputs synced after validation ran. Values sync first, then validation, then the submit callback.
- Text typed into an input was lost when a jump pushed a new view. The input carries over.
- A closed or hidden node kept reporting its last on-screen position. Its ref reads zero when the node is not rendered this frame.
- Jump-label feedback could fail to engage while typing. Typed keys accumulate directly.
- KeyHelp stamped solid default-background chips over styled fills. It composites over the underlying fill.
- Leaving the alternate screen after a resize could land the cursor on the wrong row. It lands on the row it left.
- A log scrolled away from the tail jumped when new lines arrived. The position holds unless the log is following.
- Shrinking a pane could drop its bottom rows. The component keeps them, not only the grid.
- Jump targets over a slice of pointers resolved the slice slot rather than the struct it points at, so a jump could land on stale data. They resolve the pointed-to struct.