Changelog¶
0.5.0.4¶
Added¶
- Progress: thicker sizes, the circular gap, the two-bar sweep, the contained loading indicator and wavy shapes (#249).
thicknesssets a bar's height or a ring's width (4 is the standard; a thicker ring keeps the standard ring's outer edge). With atrack, a determinate ring and its track are a 4 px gap apart.two_bar: truesweeps a wait as a long bar and a shorter one behind it.contained: trueputs the loading indicator's shape in aprimary_containercircle.wavy: truedraws a bar as a sine wave flowing along a straight track (the control is taller by the wave) and a ring's arc as a wave whose crests flow round it. All are opt-in: the default look is unchanged. The widths of the two bars are fixed (the engine cannot animate a width), and the wave's size, its speed and the thicker sizes are written from memory of the spec and have not been checked against it. Not done: making MD3's current look (a track and a stop dot) the default.
0.5.0.3¶
Added¶
- RangeSlider: two handles (#250, part 2 of 2).
widget: RangeSliderpicks a least and a most value withlowandhigh(two-way,low <= high: the handles never cross). Each handle is its own Tab stop with its own state layer and focus ring, and a slider of its own for a screen reader ("Price, minimum", "Price, maximum", reaching up to or down from the other handle); the arrow, Page and Home/End keys move the handle that has the focus. A press on the track moves the nearer handle, a drag keeps the handle it began on. It takes the slider'smin,max,step,ticks,value_indicator,vertical,sizeandicon; with asizethe track is three pieces, a gap from each handle.controls.RangeSlideris the Python class;on_inputandon_changehear(low, high). - Slider: Expressive sizes, vertical and an inset icon (#250, part 1 of 2).
size: xs | s | m | l | xldraws MD3 Expressive's slider: a track 16, 24, 40, 56 or 96 px thick in two pieces with a 6 px gap on each side of a thin 4 px handle (44 to 108 px across it); the touch target grows to hold the handle.vertical: truestands it up: the bottom ismin,style.heightis its length, the up and down arrows step it, and the ticks and the value bubble go beside the track.icon: nameinsets a glyph at the start of the track (from sizesup), in the on-colour while the active piece covers it and in the track's colour when it does not. The standard slider is unchanged. The sizes are written from memory of the spec and have not been checked against it.
0.5.0.2¶
Changed¶
- A function name that does not exist is found when the view loads (#251).
{{ nosuch(1) }}in a template, anif:, afor:or a stylesheet rule, andx = nosuch(1)ornosuch(1)in a handler, used to load without a word and fail when the expression ran (a traceback on stderr when a handler was pressed, an expression that raised at render). Now the view does not load: the error names the file and line and offers the near names (did you mean 'clamp'). In an expression the only functions are the built-in ones, so it is found as the text is compiled. In a handler a call may also be a built-in action (copy,focus,after,navigate_to, ...), a method of the ViewModel or a handler parameter, so it is found as the view is composed. A handler whose names cannot be judged (a scope with no app to list the built-in actions) loads as before. A ViewModel method added to the object after its view loaded is now a load error: define it on the class.
0.5.0.1¶
Point releases between 0.5.0 and 0.5.1: one issue each, in the order the backlog was recommended (#252, #251, #250, #249, #248).
Fixed¶
- A flex-expanded Slider maps the pointer with the width it was laid out at (#252).
The slider fixed its span at the width it was built with (200 by default), so in a row with
flex: expand_horizontal(or a percentage) its track and handle were drawn at that width and a press in the middle gave the wrong value (a 280 px slider read 62 as 90). It now takes the width the engine laid it out at when it paints, when a pointer is mapped, on a drawn frame and on a window resize, and moves the track, the tick marks and the handle with it. A slider with an explicit width is untouched.
0.5.0¶
The YAML language redesign (#209), then the components and the windows work.
The language is specified in design/yaml-language.md; the phases land one at a time.
Changed¶
-
Bindings are a Python subset (phase 2).
tesserae.expris a sandboxed evaluator (anastnode whitelist, name resolution through a scope, limits on source size, depth, nodes, steps, ranges, sequences, exponents, integers and text) that replaces the port oftre's binding grammar. Python's arithmetic and comparison rules apply (1 == 1.0isTrue, unary minus,None,if/else, comprehensions, f-strings, whitelisted functions and methods), and a Signal reads as its value (countandcount.get()agree). Operators, truth tests, subscripts, formatting and built-ins touch only plain values, built-in containers, mappings, dates, decimals and enums, so an application's own object can be read by attribute but none of its methods run from a view.tesserae.bindingkeepsparse_binding,evaluate,evaluate_value,HandleandBindingErrorover it, and errors now carry the position and a hint. See Binding Expressions. -
The node model (phase 3).
tesserae.spec.widgetsis the widget registry (Propertywith type, default, choices, required and model;@widget,declare,register_widget,decl_from_paramsfor a view'sparams:, and a JSON schema per widget inschema/tesserae-widget-schema.json).tesserae.spec.nodes.parse_viewreads a view in the 0.5.0 syntax (widget:, optionalname:, properties,if,for,key,slot,state,style,classes,handlers,a11y,interaction,window_region,route,children): every key and property is checked, every expression compiled, every node given an id from its path, and an error names the file, line and column with a suggestion.tesserae.spec.translateturns 0.4.x YAML into it; 128 of the 131 files in the repository translate and load, and the other three are reported (a dynamic widget name, and a property a call binds that its fragment does not declare). Nothing builds from the new syntax yet: that is phases 4 and 5. -
Composition (phase 4).
tesserae.spec.compose.Composerturns parsed views into a live tree ofInstances: a view call is replaced by the callee's root, its arguments are evaluated in the caller's scope and stay reactive inside the callee,Slotholds the call's children (built in the caller's scope, with errors for a view that takes none or a slot it lacks),for:expands a node per element and a reactive one is reconciled bykey:(kept, added, removed, reordered),if:builds and disposes as its expression changes, andstate:is a per-instanceSignalthat survives a recomposition for the same ids. Disposing a composition releases every subscription. Nothing buildstrenodes from it yet (phase 5). -
ViewModel binding and a renderer (phase 5). A
ViewModellists the named views it serves inviews; one instance serves every view with one of those names, wherever they are, so a pie chart and a list of the same rows follow oneSignal.app.bind(DataViewModel)makes it once, when its first view opens;app.bind(instance)uses yours;app.bind(factory=DataViewModel)makes one per view instance; a view without aname:is not bound.app.open_view("Main")composes a view written in the new syntax and shows it with the existing builder, keeping it in step with its Signals (for:andif:add and remove real nodes, handlers run in the scope they were written in, amodelproperty given a bare reference writes the user's edit back).ViewModel.views[name]is a handle (.node,.state,.show,.hide),self.show(new, old)swaps views, andapp.check()/check_viewcompare every name, action andexpects:entry of a view with its ViewModel without a window.ViewModel(view)still works. -
Style rules (phase 6). A stylesheet may hold rules by
widget,variant,size,shape,classes,name,partandstate(tesserae.spec.rules). The most specific rule wins (a name, then the number of properties matched, then a state, then the widget alone), a later rule wins a tie, an app's stylesheet beats the looks a widget ships, and a node's inlinestyle:beats every rule for the fields it sets.hovered,focusedandpressedare Signals a rule can select on and an expression can read;disabled,selected,checkedandexpandedread the widget's own property.foregroundis no longer valid on every widget: the widgets that draw text or glyphs declare it, and the error names them.self.show(new, old)swaps one of a ViewModel's views for another. -
Migration (phase 7).
tesserae migrate-yaml [PATH] [--write] [--force](tesserae.migrate) moves a project to the new syntax: views and fragments are translated (a fragment becomes a*_View.yaml), stylesheets become rules (a component's bypart, an app's bywidgetandname), a view with a*_ViewModel.pyis named after it, header comments are kept, every result is checked with the app's loader and nothing is written unless all of it loads (--forcewrites what did). Python is not rewritten: the report lists what each ViewModel needs. A view in the old syntax still loads and says once per file how to move on. New: the guide The View Language, whose YAML examples are tested, and the migration table in Migrating. -
The text field component (#184, the first of the components).
widget: TextFieldis the Material text field, a view Tesserae ships with its look as rules: filled and outlined, a label that rises with focus or text, placeholder, leading and trailing icons, prefix and suffix, supporting text, a character counter, an error state with its message, disabled, read-only, multiline, and a password field with a button that shows the text.textis two-way;on_change,on_keyandon_submitare events. The bare input is the new widgetTextInput(placeholder,multiline,obscured,max_length,read_only); the old builder'skind: TextFieldtakesplaceholder,multilineandobscuredin itstext:as well, andtesserae migrate-yamlwrites the old kind aswidget: TextInput. New with it: shipped views (tesserae/views/, found after the project's, replaceable by a view of the same name),focused,focus_visible,errorandread_onlyas states a rule can select, a widget's state read by its parts' rules, and the iconsvisibility,visibility_offanderror. -
More accessibility states (#210).
a11y:takesexpanded,selected,checked,value,value_min,value_maxandvalue_step, in both syntaxes and intesserae.a11y; every field butroleandlivecan be bound, andnullclears a state tre holds unset. A control still sets its ownchecked,selectedandvalue. -
Text extras (#211).
max_lines,letter_spacing(andselectableonText) are properties ofTextandLinkin the new syntax and keys oftext:in the old;max_linescuts a long text to that many lines,letter_spacingis tracking in pixels, and aTextwith a fixedwidthand noheightis as tall as the lines it wraps to. -
Alpha on colours, and the
fullshape (#212). A colour may end in@N%to scale its alpha:primary@12%,on_surface@38%,"#6750A4@50%", wherever a style, a rule, a boundbackground/foreground/border_coloror acolorproperty takes one.corner_radius: fullis a pill or a circle. -
Focus control and keyboard navigation (#213).
focus('name')in a handler gives the focus to a named node in the view;on_pressis a handler for the pointer pressing a node (it does not make the node a button), so a container can focus the input inside it;focus_group: horizontal | vertical | bothgives the focusable nodes under a node one tab stop, arrow-key,Home/Endand type-ahead movement. The text field's box focuses its input when pressed. -
Transitions (#214).
style: {transition: {background: 150, scale: {duration: 300, easing: spring}}}makes a change to those properties ease instead of jump (opacity, colours, border, corner radius, elevation, blur and the newscale,translate_x,translate_y,rotation_deg), with Material's easings, cubic beziers or a spring; the first draw does not ease, an app that reduces motion gets the value at once, and a stylesheet rule can carry the transition. - Canvas (#215).
widget: Canvaswithdraw:, a list of{rect: [x, y, w, h]},{circle: [cx, cy, r]}and{path: [points], width: N}commands, each with acolor(a role, a CSS colour orrole@N%).draw:may be one{{ }}expression, so a canvas repaints when the Signals it reads change. It is how a widget draws wavy progress, ticks and graph edges. - Icons from a path (#216).
widget: Icontakespath:(SVG path data) andview_box:instead of a name, so any glyph can be drawn and tinted like the built-in ones.tools/import_material_symbols.py DIRturns a folder of Material Symbols SVGs intosrc/tesserae/icon_data/material_symbols.json, whichtesserae.iconsmerges under the icons built in. The 18 built-in names are unchanged; no Symbols are bundled yet (see the issue). - Material Design Icons (#216). 147 icons from Pictogrammers' MDI (Apache-2.0) are in the icon set beside the built-in ones (names in
snake_case:account_check,format_bold; a built-in name keeps its own drawing), each drawn in its own 24 x 24 box.tools/import_mdi.py mdi.json [--names ...|--all]adds more from the IconifymdiJSON. - Timers (#217). Handlers can call
after(ms, action[, name]),every(ms, action[, name])andcancel(name);actionis an action name or statements run in the handler's scope, and a name restarts a timer of that name (a debounce).tesserae.timers.Timers(window)is the same for Python. They run on the frame loop until tre has timers (#235) and stop with the view. - Scroll state (#218).
ScrollView.scroll_offsetis drawn (a literal or a Signal; a bound Signal follows the user's scrolling, and setting it scrolls), andat_top,at_endandscroll_directionwrite the position to the Signals or state names they are bound to, for collapse-on-scroll and hide-on-scroll. - Pointer capture, cursor and drag events (#219). New events
on_moveandon_release, and handler actionscapture(),release()andcursor(name)that act on the widget whose handler is running, so a drag keeps following the pointer outside the node. - Per-corner
corner_radius(#220).style.corner_radiustakes a list of four (top_left,top_right,bottom_right,bottom_left) or a mapping of corners and edges ({top: 12},{left: full, top_right: 4}), with tokens or pixels; one value is unchanged. - Windows, title bars and docks in composed views (#221). A
widget: Windowroot opened withapp.open_viewsets the OS window (it becomes the app's window view, askind: Windowis forapp.load), and the bindings aTitleBarexpands to (inactive dimming, the maximize/restore glyph, the OS's inset and buttons) are wired without a ViewModel. Docks already worked and are now covered by tests. - Input masks (#222).
mask: "(###) ###-####"on aTextInputorTextFieldformats what is typed or pasted (#digit,Aletter,*either,\\a literal);tesserae.spec.mask.Maskis the same for Python. - Clipboard actions (#223). Handlers can call
copy(text)(true if it reached the OS clipboard) andpaste()(the text on it, or''). open_url(#224). Handlers can callopen_url(url)to open a link in the OS's browser or mail program; onlyhttp,https,mailtoandtellinks are opened.tesserae.urls.open_urlis the same for Python.- Divider (#152).
widget: Dividerwithvariant: full | inset | middle,orientation: horizontal | verticalandthickness, a shipped view with a shipped look (a line inoutline_variant, hidden from a screen reader). The 0.4.xcomponent: Dividerfragment still works. - Image (#196).
widget: Imagetakesalt(a screen reader's description; without it the picture is decorative and hidden from one, unless it handles clicks) and checksfit; its shape isstyle.corner_radius, per corner too, which the engine clips the picture to. Asrcthat follows a Signal now changes the picture shown (a changed picture was missed by the reconcile). - Svg (#197).
widget: SvgtakesaltasImagedoes, and needs one ofsrcandcontent(a load error otherwise);currentColoris theforeground, so a monochrome icon follows the theme. - Elevation that moves (#226). No new language:
elevationinhoveredandpressedrules withtransition: {elevation: ms}eases the shadows (also from a level a view computes, for a drag). Documented in Gradients & Effects and tested. - Overlay (#227).
widget: Overlaywithopen(two-way),anchor,placement,modalanddismissibleshows its children in a layer over the window: anchored with flipping, or a modal scrim; Escape or a press outside closes it and writesopenback; focus returns. - VirtualList (#228).
widget: VirtualListwithitem_heightandoverscanand a singlefor:child builds only the rows in view and builds more as it scrolls (a list of 10 000 opens with about a dozen rows). It has a ScrollView'sscroll_offset,at_top,at_endandscroll_direction. - Window size classes (#230).
app.window_width,app.window_height,app.width_class(compacttoextra_large) andapp.height_classfollow the window's size and can be read in any expression.appitself is now readable in views whose ViewModel is the new kind (it had only worked for a 0.4.x ViewModel). - Screen transitions (#231).
app.transition(orApp(transition=...)) isnone,fade_through,shared_axis_x,shared_axis_y,shared_axis_zorcontainer_transform, played bynavigate,backandforward(reversed going back);app.navigate_with(name, transition=..., origin=node)picks one for a single navigation. - Text (#195).
widget: Texttakesheading: 1to6(a heading of that level for a screen reader). Type roles have atrackingfield:tokens.MD3_TRACKINGis Material 3's per role, off unless a theme'stypography:sets it (tokens.MD3_TRACKING_TYPOGRAPHYfor all). Every role's size and line height is tested against the MD3 scale. - Splitter (#203).
widget: Splitterwith two child panes,orientation, a two-wayposition,min_first,min_secondandcollapsible: a handle you drag (the pointer is captured), move with the arrow keys, Home and End, a screen reader can set, and double click to collapse. - Image masks (#236). A picture's rounded, circular and pill shapes are
style.corner_radius(also from a rule); no new code was needed, and they are tested. - The accessibility vocabulary (#237).
a11y:takespressed,invalid,description,describedby,controls,current,value_now,value_textandbusy, checked and bindable; the engine has no property for them yet (requested: tre#160), so they are sent one at a time as far as it takes them, with one warning naming each it lacks. tooltip:on every node (#238). Text, or{text, title, delay}for a rich one: shown after a hover delay (500 ms) or at once on keyboard focus, gone on leave, press, blur or Escape; the text is the node's accessibilitydescriptionwhere it has none.transition:for layout (#239).width,height,x,y,gap,paddingandmarginease like the rest. tre cannot animate them yet (requested: tre#161), so they are set frame by frame along the curve; when tre can, its own animation is used without a change.- Badge (#141, #142).
widget: Badge: a dot, or a pill with avaluecapped atlimit(999+),show, andanchored: trueto sit over the top right corner of its host (the children). One view replacesBadgeDotandBadgeLabeled, which still work. - Link (#174).
widget: Linkis primary text inbody_mediumby default withhref(opened byopen_url, so only web and mail links), a two-wayvisited(andstate: visitedin rules),disabled, andunderline(hoverby default: under the pointer or the keyboard focus,always,never). - Progress indicators (#143, #144, #145).
LinearProgressandCircularProgresswith novalue(or an expression that gives nothing) are a wait with no end (the builder had always made them a bar at 0); newtrack, linearbufferandstop_indicator, andlabel; a screen reader hears busy, or the value as a percentage. - Checkbox, RadioButton, Switch (#175, #176, #177).
label(beside the control, part of what you press),error(checkbox and radio), a Checkbox that is neither on nor off (checked: null), Switchicons, and the colour isstyle.foreground. A patch no longer reset a control's own accessibility states (checked,value) to nothing. - Slider (#178).
min,maxandstep(the builder ignored them),ticks,value_indicator,label, andon_inputfor every step of a drag; a boundvaluenow follows the drag instead of waiting for its end. A bound value is written before the node's handlers run, so a handler reads what the user just did. - SegmentedButton (#193).
widget: SegmentedButtonwithoptions, a two-wayselectedandmultiple, written in the view language (the first shipped component that is built from the language's own parts: per-corner pill,focus_group, icons, handlers that set a Signal). Ana11y: rolemay be worked out from a param (never from a Signal), and a roving focus group starts on its checked item. - Tabs (#166, #167).
widget: Tabswithtabs, a two-wayselected,variant(primary, secondary) andtab_width: a shipped view with icons, badges, an indicator that slides, and arrows/Home/End that move and choose. A bar that scrolls waits for the engine to scroll sideways. - Several windows (#105).
app.open_window(view, modal=, parent=)opens a view in a window of its own (tre 0.5.6 opens windows while the app runs), withapp.windows,app.window_of,app.close_window, and the handleropen_window(view[, modal]). Window handlers act on their own window; a modal window dims and blocks its parent; closing the main window closes them all. - Animated gradient focus borders (step 81).
App(focus_ring="gradient")turns the keyboard focus ring into a sweep gradient through the theme's secondary, primary and tertiary, one turn in three seconds (tesserae.focusring). Still when motion is reduced. ATextField's focused border is a style rule and stays solid. - Frosted navigation, search and menus (step 72).
frosted: trueonNavigationRail,NavigationDrawer,NavigationDrawerModal,Menu,SearchBar,SearchViewandTopAppBarmakes the surface 72% of its colour with a 20 pixelbackdrop_blur, so what is behind shows through softly. Off by default (Material 3 has solid surfaces). - Icon toggles that morph (step 55).
transition: {icon: ms}on anIconeases a changed glyph into the next (tre morphs a path'sdata);IconButtonuses it forselected_icon. Glyphs from different sets switch at once. - Ripple as a shader (step 48).
App(ripple="shader")draws the press ripple with one anti-aliased fill shader per node (three presses at once, Material Web's timing) instead of a circle node per press (tesserae.ripple). The default staysnodes. - Routed views: nested routes, guards and lazy screens. A routed call inside a routed view is a nested screen under its parent's path;
route:may be a mapping withpath,guard,redirectandlazy;app.guard(screen, check)is the same from Python. A lazy screen's view is built when first reached. - Window:
remember.remember: true(or a name) keeps the window's size, place and maximized state in the user's config directory and puts them back next run (tesserae.windowstate;TESSERAE_STATE_DIRmoves it). Position is saved only where the platform reports one. - Toolbar:
fit. Items that do not fit the width move into the overflow menu. It needed a way to know a size, so aContainergainsmeasured_widthandmeasured_height: outputs, written after layout and when the window resizes, bound like a scroll view'sat_top. - SplitButton: the keyboard. Arrow-down on either part opens the menu and moves the focus to its first row.
Overlaygainsfocus_first(andMenupasses it on) for any layer that should take the focus when it opens. - Routed views (#208). A view call with a
route:is a screen in a window view written in the current language (it was only wired for the old syntax);app.paramsgives the screen the params it was reached with (a route likenotes/{id:int}, back and forward restoring them), and the handlersnavigate_to(screen, params)andnavigate_route(path)go there. - Dialog: the full-screen form.
fullscreen: trueonDialogfills the window onsurfacewith a close button, the headline and the actions along the top and the content scrolling under them; it followsapp.width_classwhen written so. - Window (#204).
kind: Windowandwidget: Windowtakefullscreen,maximized,transparent,blur_behindandclick_through, applied when the view loads and only when written (the size classes were #230). - Video (#198). An
Imagenow shows itsframein a view (it was refused as "not drawn yet"), andwidget: VideoPlayerputs a play/pause button, the time, a seek slider, a mute button, a buffering ring and a caption band under it. The app decodes and supplies frames; the player owns none of the playing. - Toolbar (#172, #173).
widget: Toolbar: docked (64 tall, full width) or floating (a pill with a shadow, a row or a column), standard or vibrant, withIconButtonitems, an overflowMenu, an end action button, your own content in a slot, andhiddento fade it out of the tab order.IconButtongainstint, a colour role for a standard button's icon. - SplitButton (#124 to #128).
widget: SplitButton: a main action and a menu button joined, in all five Button types and sizes, with aMenuunder the trailing part whose chevron turns over while it is open. - NodeGraph (#202).
snap(to a grid),arrows,fit(show every node), choosing a node, and a fit-to-view method. - DatePicker (#186, #187, #188, #189).
DatePicker(the modal dialog: a month grid, a typed date, Cancel and OK) andDatePickerDay(one day, in all its states). The expression language gainsdate_add_days,date_weekday,days_in_month,format_dateandcurrent_date. - TopAppBar (#165).
TopAppBarin four sizes (small, centre, medium, large) with a navigation icon, up to three labelled actions, a collapse to the small form and the on-scroll colour. - TextField, what was left (#244). The label glides, a filled field has 4 pixel top corners and a square bottom, the input is described by the help line, and
suggestionsoffers autocomplete in aMenuunder the field. - Dock (#206, #207). A
DockPanelcan beclosable(a close button on its tab) and aDocktakes a two-wayclosedlist of the names of the shut panels: closing adds a name, removing one opens the panel again. - Tree (#157, #158).
Tree(branches that open to show their children, from nested data, withexpandedandselectedtwo-way and the tree keyboard),TreeNode(a branch or a leaf row) andTreeLevel. - Carousel (#199).
Carousel(uncontained, multi-browse, hero and centre hero rows of rounded items that scroll sideways and settle on one). AScrollViewtakessnapand any node asnap_alignstyle (tre 0.5.6's scroll snap). - Accordion (#156).
Accordion(a header button with a chevron that turns, and content while it is open) andAccordionGroup(single or multiple open, dividers, arrows between headers). - Search (#170, #171).
SearchBar(a pill with a leading icon or menu button, a clear button, a trailing icon, an avatar, a two-wayqueryandactive) andSearchView(the docked panel of rows under it, with an empty state and the result count announced). - NavigationDrawer (#163, #164).
NavigationDrawer(standard, collapses by easing its width),NavigationDrawerModal,NavigationDrawerScreens(the app's screens) andNavigationDrawerItem, with sections, dividers, badges, header and footer slots and a list that scrolls. - Menu (#168, #169).
Menu(an anchoredOverlayof rows with icons, shortcuts, checks, dividers, headings and submenus) andMenuItem. AnOverlay'sanchoris looked up in the view that called it when it is not found in its own (a menu names its button in the calling view), andanchor: parentsits against the node the overlay is written inside. - TimePicker (#190, #191, #192, #194).
TimePicker(the dialog),TimeInput(typed hour and minute),PeriodSelector(AM / PM, one view for both states), and the dial gains a two-waymode. A view'sstate:may be seeded from the view's params; aTextFieldtakes anon_edithandler; reconcile now notices a changed SpinBox option or dial mode. - SpinBox (#185). Holding a button repeats the step, Page Up and Page Down step ten at a time,
wrapgoes round the ends,decimals,prefixandsuffixformat the number, andSpinBoxFieldadds a visible label and supporting or error text. - Snackbar (#146).
Snackbar(a message near the bottom that goes by itself, with an action and a close) andSnackbarHost(several, one after another). AnOverlaytakes atimeout(it closes itself, held off while the pointer is over it) and anon_dismisshandler. - SideSheet (#154, #155).
SideSheet(standard, inline, its width eases),SideSheetModal(over a scrim, from either side) and the sharedSideSheetPanel: a title, a back and a close button, scrolling content and actions. - Chip (#179 to #183).
widget: Chip(assist, filter, input, suggestion; icon, avatar, elevated, a close button) andwidget: ChipGroup(none,single,multipleandinputmodes, wrapping, arrows). A view can take ahandlerparameter: the caller's action or statements, which the view runs by calling the parameter by name (on_remove()). - Pagination (#200).
widget: Paginationwith a two-waypage,pages,compactanddots: the numbers with an ellipsis, the current page filled, previous and next disabled at the ends. - Title bar (#205). The window buttons show a tooltip (Minimize, Maximize, Close); a
widget: Tabs,TextFieldorIconButtonin the bar's children works. A node's spec may carry atooltip, which the composed view shows. - Card (#148, #149, #150).
widget: Cardwithvariant(elevated, filled, outlined),headline,subhead,text,media, anactionsslot,actionableandselected.interaction:may now be an expression worked out from the view's params when the view is composed (never from a Signal). - NavigationRail (#159, #160, #161, #162).
NavigationRailwithitems,selected,expanded,alignmentandheader/footerslots;NavigationRailItemwith a badge;NavigationRailScreensandNavigationRailScreenfor the app's screens (app.current_screen,navigate_to). A focus group starts on the item that is current. - ListItem (#153).
widget: ListItemwith a headline, supporting text and overline (56, 72 or 88 pixels), a leading icon, avatar or picture, trailing text or icon,leadingandtrailingslots,selectable/selected,disabledand adivider. - StatusBar (#201).
widget: StatusBarwithitemsat the start, centre and end (text, icon, tooltip, pressable ones that setclicked), aprogressorbusyedge, announced politely. - Fab (#133 to #140).
widget: Fabwithicon,label(the extended form),variant(primary, secondary, tertiary, surface),size(small, medium, large),collapsedanddisabled; it rests at elevation 3, lifts to 4 on hover and settles on press. - Model parameters of a view may be written by the view even when the call gave an expression: the view holds its own copy, which follows the expression whenever it changes.
Buttongainsflip(off, a toggle leaves flipping to the caller). - IconButton (#129, #130, #131, #132).
widget: IconButtonwithvariant(standard, filled, tonal, outlined), five sizes, three widths,shape,toggle/selectedwith aselected_icon,disabled; itslabelis its accessible name and tooltip. A handler a call gives for an event the called view's root handles itself now runs after the view's own (a toggle flips, then youron_clickruns). - ButtonGroup (#123).
widget: ButtonGroupwithitems,mode(none, single, multiple),selected,chosen,variant,size,connectedandorientation. A model parameter nobody binds now holds its own value (a view's handlers may write it), a style given as an expression that works out to nothing leaves the field to the stylesheet, and a focus group starts on the item that is pressed. - Dialog (#151).
widget: Dialogwithopen,headline,text,icon,actions, a two-wayresult,dismissibleand your own content as children; a shipped view on a modalOverlay. AScrollViewwith amax_height(ormax_width) and no size of its own is now as long as its content up to that limit. - Tooltip (#147). A rich
tooltip:takesplacementand up to twoactions; it stays while the pointer moves onto it, closes when an action is pressed, and a long press shows it. - Button (#118, #119, #120, #121, #122).
widget: Buttonwithvariant(filled, tonal, elevated, outlined, text), five sizes,shape, icons,toggle/selected,loadinganddisabled; as wide as its label; lifts on hover; a pressed pill squares off. A shipped view with a stylesheet.ContainerandRecttakedisabled. - ScrollView
orientation: horizontal. A strip that scrolls sideways: children in a row, the offset along x,scroll_directionrightorleft,at_top/at_endfor the start and end of the strip. - Requires tre 0.5.6. Timers run on the window's own
afterandevery; layouttransition:is the engine's own animation (a change to or fromautoor a percentage is made at once); the accessibility states (pressed,invalid,description,current,busy,value_text, relations) are set on the node, and aDivideris aseparator. The hand-stepped layout transitions and the dropped-state tolerance are gone. Scroll snap points are asked of tre (mindderivative/tre#166); ascroll_viewwithorientation="horizontal"already scrolls sideways.
Removed¶
tests/test_binding_parity.pyand its recording: they assertedtre's quirks (1 == 1.0false, 64-bit wraparound, no unary minus).
Added¶
tests/test_expr.py: the grammar table row by row, every limit, an escape corpus of about 190 inputs run as expressions and as handlers, grammar-based and mutation fuzzers, and a manual mutation check of the sandbox rules (each rule switched off fails a test).
0.4.6¶
The removal stubs are removed (#113).
Removed¶
- The messages for the names 0.4.5 removed:
tesserae._removed(RemovedError),tesserae.shell, and theuse_shell,load_shellanddecorationsmembers ofAppthat only raised them, the--shellargument oftesserae new, and the_Shell.yamlcheck inApp.load. An old name now fails as any unknown name does (TypeError,AttributeError,ModuleNotFoundError, "unrecognized arguments"); Migrating says what replaced each. Nothing that works changes.
Added¶
tests/test_no_shell_left.pykeeps the shell file,AppShellanddecorationsout of the code, the examples, the tools and the workflows.
0.4.5¶
One way to describe a window (#103). The shell file, AppShell and
decorations= are removed in the phases below; this release is breaking, and Migrating says how to move.
Documentation¶
- Windows, Docks & Embedded Views is the guide for the window frame; App Shell & Docking is now Docking from Python
(
guide/docking.md), abouttesserae.docking.Dockalone. The tutorials, the CLI, hot reload, editor support, projects and custom title bar pages, the README andARCHITECTURE.mddescribe the window view. Migrating has the step-by-step for a shell file,AppShellanddecorations. - CI and the release workflow run
examples/window_dockand buildtesserae new --windowapps in place of the shell ones.
Removed¶
-
The shell file:
*_Shell.yaml,App.load_shell, its hot reload, its schema (tesserae-shell-schema.json, in the package, the site andtesserae schema), its project folder kind, thetesserae new --shelltemplate and flag, and theexamples/app_shell_fileexample. Each says what replaces it (akind: Windowview) when it is used. -
The Python app shell:
tesserae.shell.AppShellandApp.use_shell, and theexamples/app_shellexample.tesserae.docking.Dockstays, for docking panels from Python. Using either removed name says what replaces it. -
decorations:App(decorations=)andapp.decorations(the opposite ofborderless, which is the one name now:App(borderless=True),app.borderless,borderless: trueon akind: Window).app.decorationsandApp(decorations=)raise an error saying so.tre's ownWindow(decorations=)is the engine's and is unchanged.
Changed¶
- A removed name says what replaces it.
decorations=,app.decorations,App.load_shell,App.use_shell,AppShell, a*_Shell.yamlandtesserae new --shellraise aRemovedError(aValueError) naming the replacement and the migration page, instead of anAttributeErrorthat says nothing. The messages are intesserae._removed.
0.4.4¶
A window, a dock and embedded views in the same YAML as every other view (#95).
Added¶
kind: Dockandkind: DockPanel: panels docked around the middle of a view. ADockholdsDockPanels, each in the zone itsstyle: {zone: left|right|top|bottom|center}names (zoneis a style field, and an error anywhere but on aDockPanelin aDock); a left or right zone is its panel'swidthwide, a top or bottom one itsheighthigh. Panels sharing a zone are its tabs, titled bytitle:, which the user drags to another zone; a handle between a zone and the middle resizes it (drag, or the arrow keys, Home and End). ADockPanelinside aDockPanelis a split of it, side by side or top and bottom (flex_direction), with a handle between the halves, which can be split again. A reload leaves a panel the user moved, and the sizes they set, where they are, and adds, removes, renames and moves panels the file changes.view.dock_host("dock")haslayout()andrestore(layout)(zones, tabs, sizes and splits as plain data),size(side)andset_size(side, size). One dock per window; it doesn't depend on the app shell or the shell file. Not yet: dropping a dragged panel onto a split half (a drag docks into the five zones).- Routed views:
- {id: settings, view: Settings_View.yaml, route: settings}in a window view makes the view a screen of the app: registered under its name (Settings) and the route, shown in its node while it is current, and hidden (out of the layout, with its state) when it isn't.app.navigate,show,navigate_to,backand the history work as before. New handlersnavigate.<Screen>,navigate.backandnavigate.forwarddo them from YAML,app.current_screenis a value bindings can read, and a colour binding can name a theme role.NavigationRailScreens(andNavigationRailScreen) is a navigation rail whose destinations go to screens and show which is current. A window view and an app shell can't both be the frame. kind: Window: the root of a*_View.yamlthat is the whole OS window, withtitle:,borderless:,min_width:,min_height:, atitle_bar:(aTitleBar's keys; the window's own whenborderless),children:for the content, andstyle:width/heightfor the window's size andbackgroundfor its colour.app.load("Window")(orApp(window_view="Window")) loads it, sets the OS window from it, and mounts it for good; it needs no ViewModel. There is one per app, and it can't be nested or embedded. Editing it while the app runs sets the window again.borderlessis the new name for an OS window without its title bar and borders:App(borderless=True)andapp.borderless(decorationsstill works).- A
TitleBarin a dialog or a sheet:buttons: [dismiss]makes a bar with no window buttons, no OS-controls room and no drag region, whose button closes the surface it is in (surface.dismiss, a new handler next towindow.*;dismiss_surface(node)from Python).ViewDialog(window, "Settings_View.yaml")is a dialog whose content is a view, with or without a ViewModel (.content,.viewmodel). A view with no ViewModel now has itswindow.*andsurface.*handlers wired, so a title bar's buttons work in a static view. An embedded view takes the colours when its host is re-coloured. tesserae new --windowmakes a project whose app is oneWindow_View.yaml: a title bar, aNavigationRailScreensand two routed screens, Main and Settings (--custom-title-barmakes itborderless).tesserae add screenin such a project says whichview:node to add to the window instead of editingapp.py.- Style fields in sections: the YAML reference's style table, and the schema (an
x-groupon each field, and the group in its description, so autocomplete shows it), put each field in one of Size and spacing, Flex, Alignment, Grid, Position, Docking, and Paint and effects. Files stay flat.tesserae.spec.cascade.STYLE_GROUPSlists them, and a test keeps every field in one. examples/window_dock/: the studio ofexamples/app_shell_file/written as oneWindow_View.yaml(title bar, rail, a dock ofview:panels, routed screens, status bar), with no widgets made in Python. It checks itself, and the tests read what it draws back from the window's snapshot.- A guide page, Windows, Docks & Embedded Views, and component pages for Window, Title bar, Dock, Dock panel and Routed views.
view:shows another*_View.yamlwhere it is:- {id: left, view: Left_View.yaml, with: {size: 3}, style: {width: 220}}(or by name in a project). It is a view of its own, with its own ids and its own ViewModel when there is one (Left_ViewModel.py), and it needs none: a view with no ViewModel is static.view.embedded(id)andview.viewmodelread them from Python;tesserae.component.embedis the function. See Components & Embedding.
0.4.3.3¶
tre 0.5.5 (#94).
Requirements¶
tre0.5.5 or newer, below 0.6 (0.4.3.2 needed 0.5.4). On KDE Wayland, tre 0.5.4 presented every frame with a vsync barrier, and while a window was being resized the compositor then held back the next resize for up to a second, so the window trailed the mouse and kept catching up after the button was released: undecorated and native windows both. 0.5.5 presents without the barrier while a window is being resized and goes back to vsync after it. Tesserae's code is unchanged.
0.4.3.2¶
A TextField in dark mode (#93).
Fixed¶
- A
TextField's text was dark in a dark scheme, dark ink on a dark field. What is typed is the theme'son_surfacenow (or the style'sforeground, if it has one), and the caret is the theme'sprimary, and both follow the app between light and dark. - A sweep of every component for the same fault found no other: in both schemes no component's text or icon is
unreadable against what is behind it, and a component built in one scheme and switched to the other by the app
looks as one built in it (
tests/test_component_colours.py).
Added¶
- Text fields has a component page (it was missing): the
TextFieldnode kind, how its colours come from the theme, binding it two ways, and its limits. The Components index names a node kind (kind: TextField,kind: Svg) where it used to say "Python only".
0.4.3.1¶
Style files (#92).
Changed¶
- A
*_Style.yamlsays what it is. Its fields go under astyle:key, with an optionalid:naming the style, like the style in a view:{id: row_style, style: {flex_direction: horizontal, gap: 8}}. A file that is only the fields still works, so nothing has to change.
Added¶
tesserae-style-schema.json, a schema for*_Style.yaml(tesserae schemalists it,--settingsmaps it), so an editor checks a style file and says what each field is. The tutorials'row_Style.yamluse the new form.
0.4.3¶
Documentation (#91).
Added¶
- Tutorial: The Same App as a Project builds the Tutorial's Tasks app as the project
tesserae newmakes, in six steps (examples/tutorial_project/, run by the tests): files inViews/,ViewModels/,Components/,Themes/andStyles/found by name,Repeaterrows by name, a theme and stylesheet by name,tesserae add screen, a shell by name, with a table comparing a flat folder with a project. The flat Tutorial and the Getting Started, Overview and Projects pages point to each other.
Fixed¶
- The Projects guide listed the
Repeaterandinstantiaterow twice.
0.4.2¶
Names for lists and embedded components (#89), SVG (#88), and what the engine's 0.5.4 adds (#90): gradients and effects, rich text, touch and files, window options, the OS's preferences for less motion and more contrast.
Requirements¶
tre0.5.4 or newer, below 0.6 (0.4.1 needed 0.5.1). 0.5.4 adds thesvgnode, and lays a text node's width out rounded up so a width frommeasure_textfits its text.
Added¶
kind: Svg: the engine draws a whole SVG document (shapes, gradients, patterns, clips, masks, text, a blur or drop-shadow filter), scaled to fit its box.svg: {src: logo.svg}is a.svgor.svgzfile next to the view,svg: {content: "<svg ...>"}the document itself, andstyle.foregroundis whatcurrentColormeans, so a monochrome icon follows the theme. Tesserae decodes the raster pictures in a document (<image href="...">: PNG, JPEG, GIF, WebP) for the engine, which decodes no image format: found next to the SVG file and inside the view's folder, at most 8192 pixels on a side, adata:URL's picture too (itshrefis rewritten to a key of its own). A picture that can't be found or decoded is an error naming it. Hot reload watches the file and its pictures.tesserae.widgets.svg(window, source, width, height, color=)is the Python form.Repeaterandinstantiatefind views and ViewModels by name, asapp.loaddoes:Repeater(view, items, "Row", into=node)andinstantiate(view, "Row", into=node)useViews/Row_View.yamlandRowViewModel, found in the app's project.viewmodel_clsandintoare now optional in the signature (intois still needed: it is an error without it), and a path and a class still work. A name with no app is an error that says there is no project. One function intesserae.project(resolve_view) does the resolving forapp.load,instantiateandRepeater.- Gradients in a style's
background,foregroundandborder_color: a CSS-like string (linear-gradient(90deg, primary, tertiary),radial-gradient(at 30% 30%, ...),conic-gradient(from 90deg, ...), stops that are theme roles or colours, with optional places) or a mapping ({gradient: linear, angle: 90, stops: [...]}). See Gradients & Effects. - Effects as style keys:
blur,backdrop_blur(with a translucentbackground, a frosted surface),blend_mode(CSS's sixteen),filter(saturate,brightness,contrast,grayscale,hue_rotate,invert,sepia),sticky(a sticky header in a scroll view) andcursor(a name, or{src: file.png, hotspot: [x, y]}read with the view). - Rich text:
text.runsmakes aTextof styled pieces (color,weight,italic,underline,strikethrough,font_size,font_family,link), measured run by run; a run with alinkisprimaryand underlined and calls the node'son_linkhandler withevent.href.text.selectable: truelets the user select and copy aText. See Rich Text & Links. - Touch, gestures and files as handlers:
on_tap,on_long_press,on_pan,on_pinch,on_touch_start,on_touch_move,on_touch_end,on_touch_cancel,on_file_hover,on_file_hover_cancel,on_file_dropandon_link, andapp.on_file_drop(handler)for a drop anywhere on the window. See Touch, Gestures & Files. - Window options on
App:dpi_scaling(on by default),present_mode,transparent,blur_behind,click_through,glyph_cacheandsystem_fonts(alsotesserae.set_system_fonts()), withapp.scale_factorandapp.transparent_active. See Window Options & the OS. - The OS's wishes:
reduced_motionandhigh_contrastfollow the OS (or are fixed) and can be set withapp.set_reduced_motion()andapp.set_high_contrast(). Reduced motion makes every animation take no time and stops the indeterminate indicators; high contrast re-themes at the highest contrast level (Theme.contrast,tokens.color_scheme(contrast=)).tesserae.motion.duration(window, ms)is for your own animations. - A spring easing:
Theme.easing("spring")andTheme.spring(bounce). - Frame statistics:
app.frame_stats(),app.profile_nodes,app.start_trace(path)/stop_trace(),app.stats_handle(), andapp.stats_overlay = True(orTESSERAE_STATS=1) for a readout of the frame rate in the window.
Changed¶
- A window is scaled to its screen by default (
App(dpi_scaling=False)is the old way), and colours are drawn exactly as written. See Migrating.
0.4.1¶
Projects (#87).
Added¶
- A project's files are found by name.
Views/,ViewModels/,Components/,Themes/andStyles/under the app's root are the standard places:app.load("Main")findsViews/Main_View.yamlandViewModels/Main_ViewModel.py(classMainViewModel),component: Name,App(custom_theme="Name"),stylesheet="Name",app.load_shell("Name"), a shell panel's view and astyle: x_Style.yamlfind theirs.App(root=)is the root (the script's folder by default),search=[...]adds folders,recursive=Truelooks through the whole project, and a name in two places is an error naming both. Paths still work.app.projectis thetesserae.project.Project. app.load(view)takes the ViewModel class from the view's name when it isn't given.
Changed¶
tesserae new <name>makes a project: a.venvwith Tesserae installed in it (--no-venvskips it), the five folders,app.py, and aMainscreen inViews/andViewModels/.tesserae add screenwrites into those folders and loads the screen by name. An app laid out the old way still works, andadd screenfollows its layout.
0.4.0¶
A simpler layout vocabulary (#86). Breaking: see Migrating.
Changed¶
align_contentplaces a node's children as one of nine positions (top_lefttobottom_right), replacingalign_itemsandjustify_content;spread(between,around,evenly) shares out the room along the layout;flex(none,expand_horizontal,expand_vertical,fill) is a node's own sizing, replacingflex_grow,flex_shrinkandflex_basis;align_selftakes the nine positions.align_wrapped(a wrapping node's lines), andalign_tracksandalign_cells(a grid's tracks, and where items sit in their cells) are for the nodes that have them; they replace the oldalign_content,justify_contentandjustify_itemsthere. The engine's names are refused, with what replaces each.- A node is never squeezed unless it says so (
flex: noneis the default). Every hand-madeflex_shrink: 0is gone from the built-in components, bars and widgets. Two built-in components that were being squeezed look right now: a selected filter chip's check mark is 18 px, not 14, and a dialog's headline has its height. - The 77 built-in components, the title bar, the tutorial, the examples and the guide use the new names.
tesserae.spec.layoutturns them into the engine's in one place.
0.3.6¶
text_align on the built-in labels (#85).
Fixed¶
text_align: centerandendwork on aTextwith no width. The engine aligns text within the width it is laid out in, and aTextsized to its text has no room to move in, so a centred label was drawn at the left of its button. ATextthat iscenterorendaligned and has nowidthof its own now fills its parent's width, with its text's width as the least. The built-in centred labels (buttons, chips, split buttons, badges, date and period cells) are plaintext_align: centeragain, with nowrap: none(0.3.5 gave them it, which stopped the alignment).
0.3.5¶
Text that wrapped when it shouldn't (#84).
Fixed¶
- A label no longer wraps onto a second line when it fits. A
TextorLinkwith nowidthis measured to fit its text, and the engine rounds an explicit width down to a whole pixel, so a label a fraction of a pixel wider than its box wrapped:ButtonFilledwithlabel: Add a taskshowed "Add a / task" at any button width. Measured sizes are rounded up now, for everykind: Text,Link,tesserae.widgets.textand dock tab.
Added¶
text: {wrap: word | none, overflow: clip | ellipsis}onTextandLink.wrap: nonekeeps one line,overflow: ellipsisends a line that doesn't fit with an ellipsis. In the schema, the YAML reference and the layout guide. TheTextandLinkcomponents take them as parameters.
Changed¶
- The labels of the built-in components are single lines. Buttons, chips, split buttons, badges, tooltips, list, menu, tree and drawer items, date and period cells, and the accordion, top and status bars end a label that doesn't fit with an ellipsis; extended FABs, rail items and tabs keep one line without one. A dialog's words and a snackbar's message still wrap. A test lays every component out with short and long labels and fails if one wraps.
- A button's or chip's stylesheet has 12 px of padding either side of the label.
0.3.4¶
The documentation, rebuilt (#83), and the stylesheets of the built-in components.
Added¶
- A stylesheet for each built-in component. Every
<Name>_Component.yamlnow holds a component's structure, and its look is in<Name>_Stylesheet.yaml: a list of{id: <part>, style: {...}}rules whose values may be the component's{{ parameters }}. What a component expands to is unchanged. A<Name>_Stylesheet.yamlnext to your views goes over the built-in one, field by field. - Your own fragments are found next to the view. A
*_Component.yaml(and its stylesheet) in a view's folder is used bycomponent:with nocomponent_dirs=. - Documentation: a generated Python API and YAML reference; a page for
each of 43 MD3 components and 76 stylesheets; a
Themes page; a Tutorial; Migrating; and a guide to the
tesseraecommand. Tests keep the generated pages current and run every example on them. - The theme schema also covers
*_Stylesheet.yaml(including a component's) and*_Theme.yamlintesserae schema --settings.
Changed¶
- Getting Started is shorter; the Guide, Installation and Architecture pages describe what Tesserae is, with no release or milestone history (that is here and in Migrating).
- Docstrings that appear on the API page are free of history.
0.3.3¶
Three things a user found testing: the shell's bars, theme roles without a theme, and styling the shell's parts.
Added¶
- Every part of a shell takes a
style:(#81): the shell itself,top_bar,navigation,status_bar,contentand each zone ({size, style}), in a*_Shell.yamland in code (style=ontop_app_bar,status_barandnavigation_rail;AppShell(styles=);shell.set_style). The top bar's height was impossible to set before. Hot reload applies a changed style and puts back the shell's own values when one is removed. The shell schema knows them.
Fixed¶
- A shell's top bar and status bar no longer shrink when the window is
short (#80). They
kept
tre's defaultflex_shrinkof 1, so a short window squeezed the top bar from 64 px down to its title's height (28) and the status bar from 24 to 16. They are fixed-height bars now, wherever they are placed. (The navigation rail never shrank.)
Changed¶
- Theme roles work in an app with no
theme_seed(#82).background: surface, and the built-inkind: TitleBar, failed there with "unknown color identifier". A view with no theme resolves roles from MD3's baseline palette (the light one), as an unthemed widget already did; atheme_seed=still themes the app and lets it follow light and dark. A misspelt role now says what was meant (did you mean 'surface'?); a name that is no role keepstre's wording.
0.3.2¶
YAML schemas for editors (#79),
tre 0.5.1, and the work-around for the bug it fixed off by default
(#78).
Requirements¶
tre0.5.1 or newer, below 0.6 (0.3.1 needed 0.5.0.1). 0.5.1 lets other Python threads run while a window sits idle (tre#92), the cause of the hot-reload stall 0.3.1 worked around. It also addstre.Shaderand paintsfill, borders andcorner_radiuson more node kinds than before; Tesserae sets none of those on a kind that used to ignore them, so nothing in its views changes.
Added¶
- YAML schemas for Red Hat's YAML language server (#79):
tesserae-yaml-schema.json(a view), andtesserae-shell-schema.json,tesserae-component-schema.jsonandtesserae-theme-schema.json, JSON Schema draft-07. An editor suggests node kinds, style fields, colour roles, layout values and a built-in fragment's parameters, shows what each means, and flags a typo. They come with the package, are published athttps://mindderivative.github.io/tesserae/schema/, and are written bytools/generate_yaml_schema.pyfrom Tesserae's own code. New command:tesserae schemasays where they are, andtesserae schema --settingsprints theyaml.schemassetting. See Editor Support.
Changed¶
run(keepalive=)is off by default, where 0.3.1 turned it on withhot_reload=True. A hot-reload watcher, or any thread callingthread_handle().call_soon, now reaches an idle window without a tick.Trueand a number still tick, for code that wants the loop woken regularly;Noneis accepted asFalse. A thread busy running Python can delay a wake-up by up to about 5 ms (the GIL's switch interval).
Tests¶
- The two tests that held an idle window with no keepalive, written to
fail once
trewas fixed, are gone;test_keepalive_idle.pynow checks that threads and hot reload reach an idle window with the default.
0.3.1¶
Things the new Getting Started walk-through ran into (#72).
Added¶
App.run()needs no screen (#73). It starts when the window has something to show: a screenshow()made current, or nodes added toapp.window.rootby calls. An app built entirely in Python no longer has to make an emptyViewfirst, and an empty window runs. Registering screens and showing none is still aRuntimeError, now saying both ways out (an undecorated window's border doesn't count as content).-
text_alignin a YAMLtext:block (#74):start(the default),centerorend, on aTextand aLink, astrenames it. Before, the key was silently ignored. A wrong value is an error naming the widget, and aTextFielddoesn't take it. -
run(keepalive=), on by default withhot_reload=True(#77).tre's window, left idle, stops other Python threads from running, so a hot-reload watcher couldn't hand a reload over until something else woke the window: an edit to a view could sit unseen while the window was dragged, resized and clicked.keepalivekeeps the window ticking (every 0.02 s, or the seconds you give).None, the default, followshot_reload;TrueandFalsechoose, so anything else that needs threads to run in an idle window (an IDE, say) can use it without hot reload. A work-around fortre: oncetreis fixed the default becomes off. Found by a user trying Getting Started. - A closest-match hint on a misspelt name (#76): a misspelt style
field, node field,
kindora11yfield now says what was probably meant, as inunknown style field(s) ['foregorund'] -- did you mean 'foreground'?. A name that resembles nothing is not guessed at. Hot reload logs it the same way.
Documentation¶
- The Getting Started page walks from a fresh Linux install to a first
window and node, declaratively and imperatively, with runnable programs
in
examples/getting_started/; the API page listsApp's window options, routing members andApp.of; the index, README and installation pages are brought up to 0.3.
0.3.0¶
Custom windowing on tre 0.5.0.1: Tesserae draws the window's title bar and
borders (#24).
Following the custom-windowing design.
Requirements¶
tre0.5.0.1 or newer, below 0.6 (0.2.x needed 0.4.4 or newer, below 0.5). 0.5.0.1 rather than 0.5.0: a real click in a text field panicked (RefCell already borrowed) intre0.4.4 and 0.5.0, and 0.5.0.1 fixes it (#63,tre#51).
Checked by hand¶
Moving, resizing, maximizing, the buttons and the window border were tried by hand on Linux, under X11 (it found the text-field panic above and an example's overlapping text, #62). Wayland, Windows and macOS were checked by the headless tests and by CI, which builds and runs a custom-title-bar app on each, but not by hand: tell us if a title bar misbehaves on one of them.
Added¶
- The window on
App(#40):decorations(a window with no OS title bar or borders, for a title bar the app draws),resize_border(6 px by default when undecorated),min_width/min_height,fullscreen,system_menuandicon(an image file), asAppoptions and live properties, andapp.platform. The actionsminimize(),maximize(),restore(),toggle_maximized()andclose(), andapp.maximizedandapp.activeas read-only Computeds for bindings. - Title bars in YAML (#41):
kind: TitleBar(icon, title, the app's own content, and minimize, maximize/restore and close buttons; Material 3 colours by class, faded while the window isn't focused; on macOS, room for the traffic lights);window_region: drag | noneon any node; the handlerswindow.minimize,window.maximize,window.restore,window.toggle_maximizedandwindow.close;app.titlebar_insetandapp.native_controls. Three window glyphs in the icon set, andvisiblecan be bound. A TitleBar hot-reloads. - Undecorated app shells (#42):
in an undecorated app the shell's top bar is the title bar
(
top_app_bar(..., window_controls=), from code or a shell file, which hot-reloads as one); a 1 px window border inoutline_variant, hidden while maximized or fullscreen and on macOS (window_border, and thewindow_borderclass);tesserae new --shell --custom-title-bar; andexamples/custom_title_bar. - A "Custom Title Bars" guide (#59), bringing the title bar, the border and each platform's differences together.
Changed¶
- Every pressed widget (state layers, the split button, splitters, dock
tabs, sliders and other drags) releases on
pointer_cancel, whichtre0.5.0 sends when the OS takes a press, as onpointer_up.
0.2.1¶
Releasing an app (M77, M78) and tre 0.4.3 and 0.4.4 (M79, M80), tracked
from here on in the GitHub project
(release #23).
Requirements¶
tre0.4.4 or newer, below 0.5 (was 0.4.2 or newer).tre0.5, with custom windowing, comes with Tesserae 0.3.0.
Added¶
tesserae build: an app as one executable, which its users run with nothing else installed.pip install "tesserae-ui[build]"adds PyInstaller;--checkruns the result and checks it draws frames. Built and checked in CI on Linux, macOS and Windows. See Releasing Your App.tesserae build --installer: the platform's installer, from a one-folder build. On macOS, a.app(its version, identifier and publisher inInfo.plist) in a.dmg. On Windows, an Inno Setup installer that needs no admin rights, with a Start-menu entry and an uninstaller; Tesserae fetches a pinned Inno Setup if there's none. On Linux, an AppImage, a.deb, a pacman package, an.rpmand a Flatpak (the.deband pacman package written by Tesserae,appimagetoolfetched and pinned, the.rpmand Flatpak made whenrpmbuildandflatpak-builderare installed, and reported otherwise). A PNG--iconworks on every platform.- The Releasing Your App guide: building, what goes in, each installer and how its users install and uninstall it, and signing and notarizing with your own certificates.
TESSERAE_MAX_FRAMES=nstopsapp.run()afternframes, andTESSERAE_FRAMES_REPORT=<file>writes how many it drew, for automated runs.
Changed¶
- A built (frozen) app runs without hot reload, which has no source files to watch.
- On macOS,
tesserae buildwithout--installermakes only the single executable, which runs from a terminal; PyInstaller 7 won't make a.appfrom one file, so the.appcomes with--installer. - A bare number as a grid track list (
grid_auto_rows: 96) is passed totre, which takes it since 0.4.3; Tesserae no longer converts it. - On
tre0.4.4, scrolling chains as in a browser: a wheel or scroll key an innerScrollViewcan't use (at its end, or its content fits) goes to the one outside it. - On
tre0.4.3, ascroll_offsetset past the end is held at the end at once (and written back throughtwo_way), and scroll keys held with Ctrl, Alt or Meta no longer scroll.
Fixed¶
- Hot reload no longer reads a file in the middle of a save. On macOS a save could arrive as one event while the file was still empty, so a reload failed ("'NoneType' object has no attribute 'get'") and missed the edit; the watchers now wait for a change to settle (#39).
0.2.0¶
Everything since 0.1.0 (M53-M76).
Requirements¶
- Python 3.12 or newer (0.1.0 said 3.9).
tre0.4.2 or newer (tesserae-engineon PyPI; 0.1.0 needed 0.3.5.2), installed with Tesserae.- One
pip install tesserae-uiinstalls everything, as prebuilt wheels, on Linux x86-64, macOS on Apple silicon and Windows x64 -- checked in CI on all three, on Python 3.12 and 3.14, where the full test suite also runs.
Added¶
- App structure
- Shared state:
App(state=...), reached asself.state(and the app asself.app) from any ViewModel on the app's window, and as{{ state.x.get() }}in any binding;App.of(view). - Routing:
app.navigate(name, **params),back(),forward(),on_navigated(params)on the screen's ViewModel,can_go_back/can_go_forward; routes and deep links withapp.route("notes/{id:int}", "Note"),navigate_to("notes/42")andlocation. Alt+Left/Right and the mouse's side buttons go back and forward. tesserae new <name> [--shell]makes a runnable app, andtesserae add screen <Name>adds a screen to it.
- Shared state:
- Layout
- The rest of flexbox in a view's style and stylesheets:
flex_wrap,align_self, min/max sizes,aspect_ratio,positionwithx/y,z_index,clip_children. - CSS Grid:
display: grid, track templates (fr,repeat(),minmax()), auto tracks and flow,grid_column/grid_rowplacement, row and column gaps, grid alignment. kind: ScrollView, with keyboard scrolling, focus reveal andtwo_way: scroll_offset.
- The rest of flexbox in a view's style and stylesheets:
- Components and styles
- A
component:call takeshandlers:,bindings:,two_way:,a11y:,interaction:andclasses:for its root, so a button fragment can be clicked. disabled:as a key or a binding on any node (announced, not focusable, no feedback, handlers off, faded).- A node's
style:can name a*_Style.yamlfile;*_Stylesheet.yamland*_Theme.yamlname the other kinds. - Fragment params with defaults,
when:and{if:, then:, else:}; per-item styling forrepeat:; a theme'scomponents:in the cascade. - YAML kinds for
SpinBox, video (aframebinding onImage) and node graphs (NodeGraph,GraphNode). tesserae.widgets.text, and every widget follows its theme'stypography:.paginationwindows long runs with an ellipsis.- CSS wide-gamut colours:
oklch(),oklab(),lch(),lab(),hwb()andcolor().
- A
- Development
- Hot reload for components in any view, and for components first used while the app runs.
- A shell file's removed panel is undocked while the app runs;
App(dark="system")starts in the OS's appearance. - An open overlay follows a window resize.
Changed¶
- A shell file's navigation rail now
navigates (with history) instead of jumping withshow. load_themeandload_stylesheetrefuse a file named for another kind (*_Style.yaml,*_Stylesheet.yaml,*_Theme.yaml); other names load as before.- A long
paginationrun's buttons are slots (slot0, ...), not one per page. App.run()raisesRuntimeErrorwhen a window's GPU can't be set up (fromtre0.4.0; before, the process exited with status 0).
Fixed¶
- On macOS, a hot-reload watcher could reload files that hadn't changed (writes from just before the watch began); it now skips an event that changed nothing.
- Windows: Tesserae's own file reads name UTF-8, rather than the system code page.
0.1.0¶
The first release on PyPI, as tesserae-ui (2026-09-28).