ποΈ v0.28.0 β Bare Form Controls & Interaction Polish
Two new form primitives β midwest_checkbox and midwest_radio β render just the styled input, with no label or wrapper, for dense and inline contexts like a per-row selector in a diff or table. Alongside them, three interactive components gain usability options: the avatar derives a stable per-name colour with color: :auto (and renders a single initial at its smallest size), the split view can now collapse its primary pane to zero on a double-click, and tree nodes can be forced to render as expandable branches even when empty (and forward arbitrary HTML attributes, so nodes can be made draggable).
What's New
Bare form controls β midwest_checkbox and midwest_radio
The full CheckboxComponent and RadioComponent always render a form-group wrapper, a label, and an optional description β the right shape for a labeled field, but too heavy for a dense selector. Both components gain a bare: true mode that renders just the styled <input> with none of that chrome, and two helpers set it for you:
<%# a per-row selector in a diff or list %><% lines.each_with_index do |line, i| %> <div class="flex items-center gap-2"> <%= midwest_checkbox name: "lines[]", value: i, size: :tiny, aria: { label: "Select line #{i + 1}" } %> <span class="whitespace-pre"><%= line %></span> </div><% end %><%= midwest_radio name: "view", value: "grid", checked: @view == "grid", size: :sm, aria: { label: "Grid view" } %>In bare mode the size: modifier (:tiny, :sm, :default, :lg) is applied directly to the input β the .midwest-checkbox/.midwest-radio wrapper isn't there to carry it β and any extra HTML attributes (aria:, data:, class:, style:) are forwarded onto the input. Always give a bare control an accessible name. For a labeled field, keep reaching for midwest_form_checkbox / midwest_form_radio.
Avatar auto colour
Pass color: :auto to derive a stable theme from the avatar's name. Each name gets a consistent β but different β colour, so a list of people reads as visually distinct without hand-assigning a theme to each one. Any named theme is still used as-is.
<%= midwest_avatar(name: "Ada Lovelace", color: :auto) %><%= midwest_avatar(name: "Grace Hopper", color: :auto) %>Collapsible split view
SplitViewComponent gains a collapsible: true option that turns the divider's double-click into a cycle: a pane you've dragged snaps back to its initial size, and the next double-click collapses the primary pane to zero width. The divider stays visible while collapsed, so a final double-click restores the initial size β a lightweight way to tuck a sidebar away without a separate toggle.
<%= midwest_split_view(collapsible: true) do |sv| %> <% sv.with_primary do %>β¦sidebarβ¦<% end %> <% sv.with_secondary do %>β¦mainβ¦<% end %><% end %>Tree nodes: forced branches and attribute forwarding
Tree nodes previously inferred whether they were a branch from whether they had children β an empty node was always a leaf. A new branch: option overrides that: branch: true forces a node to render as an expandable branch (chevron + child list) even when it has no children β an empty folder that can still be expanded or dropped into β while branch: false forces a leaf and nil keeps inferring.
Nodes also now forward arbitrary HTML attributes (data-*, draggable, id, β¦) onto their <li>, so callers can make nodes draggable or addressable for drag-and-drop and deep-linking.
<%= tree.with_node(title: "Empty folder", branch: true, draggable: true) %>What Changed
Avatar initials at the xs size
The xs avatar is too small to read two letters, so it now always renders a single initial regardless of how many words the name has (and a blank name shows M rather than MW). Every other size is unchanged β first + last initial where a full name is available.
MCP component guidance
The MCP server's curated guidance gains anti-patterns and best practices steering agents toward the new primitives: hand-rolling a bare <input type="checkbox"> or <input type="radio"> renders oversized (Midwest styles bare inputs globally at a large size) and outside the size system, so the guidance points at midwest_checkbox / midwest_radio instead. A new dropdown_item_component note documents that a confirm: item with no path: must use as: :button β a link item renders link_to(nil) and raises.
What Was Removed
.githooks/pre-commitwas removed and.githooks/is now gitignored β the lint-on-commit hook is no longer tracked in the repo. Runbin/lint -fyourself before committing.- npm package slimming β the published
@unabridged/midwestpackage no longer ships source maps (*.map), the bundlednode_modulesunder the async-asset collection, or the raw icon sources. These were never needed at runtime; the package is smaller with no change to what an app consumes.
No component options, slots, or helpers were dropped. bare: on checkboxes/radios, collapsible: on the split view, and branch: on tree nodes all default to their previous behaviour, so an app that upgrades and changes nothing renders exactly as it did on v0.27.5.
Dev Process
- Roadmap β the v0.25βv0.27 milestones are all complete, and none of this release's usability refinements were scoped roadmap items, so no roadmap items changed state. Like the v0.27.x additions before them, these land as net-new polish rather than milestone deliverables.
- Releases now carry the compiled gem β
bin/publish-release-notesattachesmidwest-<version>.gemto each GitHub release (building it on demand if it isn't already on disk), so a release can be installed straight from GitHub without RubyGems, alongside the existing language-server.vsix. - Tests β new coverage for avatar auto-colour and
xssingle-initial rendering, checkbox/radiobaremode (size folded onto the input, attribute forwarding), split-viewcollapsible, and tree-node forced branches. - Previews β
AvatarComponentPreviewgains Auto colour and Extra small examples (and:autois now a colour choice in its playground);TreeViewComponentPreviewgains an Empty branch example. - Version bumps β the gem, the
@unabridged/midwestnpm package, and the@unabridged/midwest-language-serverLSP package are all bumped to0.28.0.