🪟 v0.26.0 — Layouts, Menus & Table Power
A grab-bag of remaining pre-1.0 work. Four new components land — a right-click context menu, a zero-JavaScript masonry grid, a resizable split view, and a Mastodon social preview — while TableComponent gains column resizing, inline cell editing, and multi-column sort. Hotwire modals become deep-linkable with real browser history, charts get a native export toolbar, and midwest_assets guarantees a component's CSS is present even when it arrives over Turbo. Three new MCP tools document the chart subsystem and Turbo integration patterns, bringing the server to 24 tools.
What's New
ContextMenuComponent — Right-click menus at the cursor
ContextMenuComponent wraps a trigger area; right-clicking anywhere inside it opens a floating menu at the cursor, rendered in the top layer via the native Popover API so it escapes overflow and stacking contexts. The midwest-context-menu Stimulus controller positions and clamps the menu to the viewport and dismisses it on Escape, outside click, scroll, or resize.
It shares the dropdown's shell and rows — items are supplied through the items slot as DropdownItemComponents, so you get the same link / button / header / divider / nested as: types, tooltips, confirmation dialogs, and one level of nested submenu. The entrance is composed through midwest_motion (trigger: :hover, default scale primitive), drawing on the same motion vocabulary, tokens, and app-wide Midwest.config.motion defaults as the dropdown, tooltip, and popover.
MasonryComponent — Pinterest-style layout, zero JavaScript
MasonryComponent uses the native CSS masonry syntax where supported (shortest-lane placement in true left-to-right reading order), progressively enhances two experimental syntaxes (Firefox's grid-template-rows: masonry and WebKit's display: grid-lanes), and falls back to CSS multi-column layout everywhere else. The column count is responsive (columns:, clamped 1–8, stepping down to 2 then 1 on narrower viewports) and the gap: is configurable, both driven by --masonry-* custom properties. Items are supplied through the items slot.
SplitViewComponent — Resizable panes with a draggable divider
SplitViewComponent exposes primary / secondary slots separated by a draggable divider. The primary pane's size is adjustable by dragging or with the arrow keys when the divider is focused (Shift for larger steps, Home/End for min/max), and — when a storage_key: is given — persisted to localStorage so it survives reloads. Double-clicking the divider resets to the initial size.
Sizes are percentages (size: / min: / max:); orientation: is :horizontal (default, vertical divider) or :vertical (stacked, horizontal divider), with the correct inverse ARIA aria-orientation. The midwest-split-view Stimulus controller handles dragging, clamping, keyboard resizing, and persistence.
SocialPreview::MastodonComponent — Fediverse post preview
A Mastodon post preview extending the new SocialPreview::BasePlatformComponent: a white card with the purple Mastodon wordmark header, a rounded-square avatar in a side-by-side layout, an optional "X boosted" attribution row, a zero-JS native content-warning disclosure (spoiler text in a <summary> with a Show more/less toggle; the post body stays hidden until expanded), and a five-button action bar (reply / boost / favourite / bookmark / share) with inline counts. There is no verification badge (Mastodon has none by design); counts accept raw integers (abbreviated automatically) or pre-formatted strings.
AssetsComponent / midwest_assets — CSS that follows a component over Turbo
Render midwest_assets alongside markup that may be delivered dynamically — into a Turbo Frame or appended by a Turbo Stream — and its midwest-assets Stimulus controller injects Midwest's compiled stylesheet (and, opt-in, its JS bundle) into <head> on connect. The injection is idempotent by resolved URL, so the component styles and behaves correctly even on a page that never loaded Midwest's assets, and never double-injects.
<%= turbo_frame_tag :panel do %> <%= midwest_assets(css: true) %> <%= render Midwest::MasonryComponent.new %><% end %>css: true resolves the app's compiled midwest stylesheet (gracefully no-ops if the asset isn't in the pipeline); a string URL is used verbatim.
Three new MCP tools
get_chart_relationships— documents how the chart subsystem is wired: theChartComponentfacade and its chart-type → chart-class map, theBaseChart → CartesianChart →concrete-type inheritance, theSupportvalue objects (Series,DataPoint,SeriesBuilder,Geometry, config structs), the data-flow pipeline from a rawseries:array to inline SVG, and how rendering and styling work. A drift-guard test keeps its type map in sync withChartComponent::CHART_CLASS_MAP.get_chart_data_requirements— documents theseries:data format each chart type expects — array shape, required keys, a worked example, and the most relevant options — for pie, column/bar, line/spline/area, stackedbar/stackedarea, and bubble, plus the sharedseries_from:collection builder. Scope it withtype:or omit for all.get_turbo_patterns— documents how Midwest works with Hotwire Turbo: the Frame-driven pieces (remote history-aware dialogs, theTurboTablesort/filter/paginate frame, command-palette and autocomplete frames), the Stream update patterns, how a Turbo Stream broadcast channel plugs in (the gem ships none of its own), dynamic per-arrival asset loading (midwest_assets), and the table-specific parsing gotchas.
The MCP server now exposes 24 tools.
What Changed
TableComponent — Column resizing, inline editing & multi-sort
Three interactive additions to the table:
- Column resizing —
resizable: truelets you drag column borders (a handle appears on each header's right edge). The controller freezes the current widths, switches the table to a fixed layout so a drag doesn't reflow the other columns, clamps to a minimum width, and supports double-click-to-auto-size. Withresize_storage_key:, widths persist to localStorage across reloads. - Inline cell editing — mark a column
editable: trueto edit its key-based cells in place: double-click (or focus + Enter/F2) swaps the cell for an input; Enter commits, Escape cancels, blur commits. Committing dispatches a bubblingmidwest-table:editevent ({ field, value, previousValue, rowId }) and, when the column suppliesedit_url:(String or callable), PATCHes the value with the CSRF token. - Multi-column sort — click a header to sort by it, Shift-click to add it as a secondary sort. The chain is URL-encoded as parallel comma-separated lists (
?sort=role,name&dir=desc,asc) and drives a multi-columnORDER BY; each sorted column shows a priority badge. Security-hardened parsing lives inMidwest::TableSortable(validates every column against a safe pattern plus anallowed_sorts:allowlist, de-dupes, honors a baredir) and backs both theTurboTablecontroller concern andTablePresenter, somidwest_table_fromwires it automatically. Single-column sort URLs stay backward-compatible.
DialogComponent — Hotwire modals with browser history
Modal frame navigation with real browser history, built on the existing remote (turbo-frame) dialog. Opt in with remote: true, history: true (or midwest_remote_dialog(history: true)): the persistent turbo-frame gains data-turbo-action="advance", so loading content into it advances the URL and pushes a history entry — the modal URL is deep-linkable and shown in the address bar while open. The dialog controller closes the modal on Back (popstate) and, on a manual close (Escape / backdrop / close button), pops the modal's URL off the stack so the address bar returns to the page behind it, while a form redirect still supersedes history restoration.
ChartComponent — Export toolbar
An opt-in export: toolbar (revealed on hover or keyboard focus) for downloading the chart, native and zero-dependency. export: true offers all formats; a symbol or array (%i[png csv]) narrows them. PNG and SVG rasterize the live inline <svg> client-side — the controller clones it, inlines every node's computed presentation styles (so exports render faithfully without the component stylesheet), serializes to standalone XML, and either downloads it or paints it onto a 2× canvas for PNG. CSV serializes the new public chart.data_table into an Excel-friendly, BOM-prefixed file. Suppressed for sparklines and empty charts; the filename derives from the title.
SocialPreview — Shared base component across all ten platforms
Extracted SocialPreview::BasePlatformComponent to share the common shell and header design, and migrated all ten platform previews (Facebook, Twitter/X, LinkedIn, Instagram, TikTok, YouTube, Threads, Bluesky, and the new Mastodon) onto it — not just the newest ones. The shell and header were updated to match current platform branding.
Eject a component into your app
bin/rails "midwest:eject:component[TableComponent]" copies a component's .rb plus its sidecar (.html.erb / .css / _controller.ts / locale .yml) out of the gem and into the consuming app's app/components/midwest/, where Rails' app-before-engine load order makes the local copy override the gem's. It accepts any name form (table, table_component, Midwest::TableComponent, social_preview/mastodon), skips generated .ast.* sidecars, refuses to clobber existing files without [name,force], and prints next-steps guidance for wiring the ejected CSS/JS.
What Was Removed
Nothing was removed, and there are no breaking API changes this release. For the record, one item scoped to this milestone was cut: TableComponent's advanced filtering (filter operators, typed filters, and a filter-bar UI). The existing param-based filtering — via TurboTable / TablePresenter and midwest_filter_form_tag — remains the supported approach.
Dev Process
- MCP drift-guards — the two new chart tools carry drift-guard tests keeping their type maps in sync with
ChartComponent::CHART_CLASS_MAP, so the docs can't silently diverge from the code. - Browser system tests — new Playwright suites cover the context menu, interactive table (resize / inline-edit / plain- vs shift-click sort), the history-aware modal (open advances URL, Back closes and restores, Escape closes and restores), and
midwest_assetsinjecting its<link>into the head. - Shared sort parsing — multi-column sort logic is centralized in
Midwest::TableSortablewith its own module, presenter, and controller test coverage. - Docs — the MCP server reference now documents all 24 tools.
- AST regeneration — the committed per-component AST sidecars and the aggregate
ast/graph were regenerated for the four new components and every changed one. - Gemfile locks — routine lock bumps for the Rails 7, Rails 8, and Bridgetown v1/v2 gemfiles.