All notable changes to this project are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning.
Unreleased
[0.8.2] - 2026-09-20
Changed
Dense-app primitives fold into existing components (flicker #2890). Five 0.8.0 names were reskins of library components that already existed. The old functions remain as deprecated aliases for this release and are removed in 0.9.0. Each alias logs a one-per-node warning.
Before After <.icon_button label="Filter" kbd="F"><.button size="icon" variant="ghost" label="Filter" kbd="F"><.progress_ring value={7} max={19}><.progress shape="ring" value={7} max={19}><.segmented id="scope" value={@scope}><:segment><.tabs_list id="scope" variant="segmented" active_tab={@scope}><:tab><.property_row label="Repo"><.description_list layout="dense"><:item label="Repo">buttongainslabel/kbd(tooltip wrap onicon-*sizes).progressgainsshape="ring"(value/max, completed/scope, thicker value stroke andborder-strongtrack).tabs_list variant="segmented"gains the pill border, optionalid+LanternTabsarrow keys (LanternSegmentedis the same hook), androle="radiogroup"when passed.description_listgainslayout="dense"for the inspector rail.side_panel_toggleis implemented throughbutton/1. Docs, recipes, and skills use the consolidated names.
Added
group_bandclient-side collapse viagroup=and thedata-lantern-collapsebehaviour. Click / Enter / Space togglesdata-collapsedon the band andhiddenon siblings with matchingdata-lantern-group(list_rowgroup=). Compose withdata-lantern-persistusing the same key so the collapsed set survives LiveView patches. CSS rotates the down chevron when collapsed. (flicker #2888)
Deprecated
icon_button/1,progress_ring/1,segmented/1, andproperty_row/1. Use the after column above. Removed in 0.9.0.
Fixed
icon_buttontooltips rendered open..lui-icon-btn-tip .lui-tooltip(display: inline-flex) out-specified.lui-tooltip[hidden], so every icon-button tip was visible on load. The override is now scoped to:not([hidden]). (flicker #2889)
[0.8.1] - 2026-09-20
Added
state_glyphkind="sync"andkind="source"— sync states (syncing,live,failed,empty) and source kinds (repo,doc,ticket_memory,upload), plus flicker-shapedsync_glyph/1(state=) andsource_glyph/1(source=). Unknown sync values drawempty; unknown source values drawdoc. (flicker #2882)state_glyphkind="run"— a generic CI/pipeline set (queued,claiming_env,running,verifying,passed,failed,blocked) plus a flicker-shapedrun_glyph/1alias (state=). Unknown values drawqueued. Token-tinted like the status and priority sets.- Recipes page +
lantern-recipesskill. Kitchen-sink copyable HEEx for Linear-shaped pages — list row, grouped list, record rail, 3-column inbox, project overview, icon toolbar, capacity strip — built only from lantern components and sourced from flicker #1404 / #1407 / #1408. Showcase: docs/recipes.md.
0.8.0 - 2026-09-20
Dense-app primitives, compact type/grey scale, and the esbuild hooks
pipeline land together. The old zero-dependency unbundled-hooks goal is
retired (@floating-ui/dom is inlined in the committed bundle; consumers
still import priv/static/lantern_ui_hooks.js with no JS toolchain).
text-foreground-softer is a deprecated alias of -soft.
Added
Dense-app primitives — Linear-shaped rows, rails, and glyphs (flicker #2861). Eight components extracted from the flicker tickets/suggestions/hub pages:
list_row/1,group_band/1,inspector/1+property_row/1,icon_button/1,segmented/1,state_glyph/1(with flicker-shapedstatus_glyph/1/priority_glyph/1aliases),progress_ring/1, andside_panel/1+side_panel_toggle/1. The progress ring keeps a visible muted track and a thicker value stroke, so 7/19 no longer reads as an empty circle.LanternSidePanelpersists open/closed in localStorage (default open ≥1280px);LanternSegmentedhandles arrow-key activation. Showcase: docs/dense-app.md.esbuild hooks bundle + data-attribute behaviours.
lantern_ui_hooks.jsis authored inassets/js/and bundled topriv/static/(npm run build).@floating-ui/domis the first JS dependency and is inlined, so the consumer import path is unchanged. Markup-only behaviours:data-lantern-list-nav(j/k, arrows, Home/End, Enter) anddata-lantern-persist="<key>"(localStorage open/collapsed, failures swallowed). Seedocs/behaviours.md. The old “zero JS dependencies / unbundled ESM” goal is retired; consumers still do not need a JS toolchain.Compact type scale + four grey roles, with
mix lantern.lint. Dense UI now has named sizes (text-meta11px,text-caption12px,text-mono-meta) instead oftext-[11px]. Grey roles aretext-foreground(primary),-soft(labels),-softest(timestamps and ids), andtext-muted-foreground(disabled / placeholder).text-foreground-softeris a deprecated alias of-softso existing markup keeps compiling.mix lantern.lintfails arbitrarytext-[Npx]/w-[Npx]/ palette colors / page-local hex greys in consuming apps; allowlist via.lantern-lint.jsonorlantern-lint:ignore. Seedocs/scale.md.--lui-waterfall-activetoken — the color of in-flight waterfall bars. Defaults to--lantern-accent, so nothing changes for existing hosts. A host whose accent resolves near danger red (flicker's orange-red) overrides it so a healthy running phase never renders in the failure color at a glance.waterfall/1+waterfall_lane/1— horizontal spans on one shared axis. Rail column, a ruler drawn once, and lanes whose bars are positioned by caller-supplied percentages of a common axis. This is deliberately NOT a mode oftimeline/1: that component is a vertical sequence sized by content, and a waterfall is horizontal and sized by duration, so one component holding both would have to choose which of the two its status, spacing and rail meant. Gridlines are one layer under the lanes rather than per-lane copies — an earlier cut inherited ticks through process state and, because HEEx defers slot evaluation, a second waterfall on the same page drew its axis over the first one's lanes. A lane with no position on the axis isqueuedand renders dashed at the right edge; it is never given a fabricatedleft. The lane glyph is a:iconSLOT rather than an icon name: the first cut rendered a name through the library's own curated set, and flicker — which has its own heroicon pipeline — crashed onMap.fetch!for a name that set does not carry. A shared component does not get to dictate the host's icon vocabulary.log_view/1+log_line/1— command output with a timestamp gutter. Monospace lines with achannel, per-line severity banding on a colored left edge, trailingmeta, and an optional:detaildisclosure. The scroll box is the root, so a long line scrolls inside the component instead of pushing the document sideways, and cells are placed by explicitgrid-columnso a line missing its timestamp does not slide its body out of the column. Severity is also announced via an sr-only word, so it is never carried by color alone;:neutralannounces nothing, because labelling every ordinary line is noise in a 400-line log. This exists because build and job output kept landing in read-only code editors across the portfolio, which offer a caret and a selection model for content nobody can edit.Public agent skills. Added canonical skills for authoring LanternUI components, migrating Phoenix applications onto LanternUI, and designing consistent LiveView pages without private portfolio paths or policy.
Fixed
An unknown
<.icon>name no longer 500s the page.icon/1usedMap.fetch!/2, so a typo'd or renamed key raised aKeyErrorand took the whole page down. Thevalues:attr check does not cover this — it is a compile-time check on literal names, and a dynamicname={@var}reached the lookup unchecked. It now renders the<svg>with no path (the layout keeps its box) and logs a warning naming the offending key, so a content mistake degrades instead of becoming an outage. (flicker #1345)log_view/1andwaterfall/1now contain their absolutely-positioned.lui-sr-onlylabels (position: relative). Without it those labels resolve against the initial containing block, and a scrolled log inflated the DOCUMENT scroll height — the host app grew a second scrollbar that dragged its sidebar and header off screen.Sortable data-table headers now expose
aria-sort. The sort state was carried only by a↑/↓glyph, so a screen-reader user could not tell which column was sorted or in which direction. The<th>— the cell, as ARIA requires, not the inner sort link — now carriesaria-sort="ascending","descending", or"none", derived from the component's existing sort state. Non-sortable columns carry noaria-sortat all.<.button navigate={...} disabled>is now actually disabled. An<a>has nodisabledattribute, so the link branch rendered adata-disabledthat nothing consumed: the control stayed clickable, focusable, and announced as a perfectly ordinary link. It now also carriesaria-disabled="true"andtabindex="-1", and.lui-btn[data-disabled]gets the same dimming pluspointer-events: noneas.lui-btn[disabled].Sheet dismissal now runs
on_closeJS commands (flicker #1357). Close button, backdrop, Escape, and programmatic dismissal all execute the command.The app shell breadcrumb bar now sits flush under the appbar. Removed extra top padding above the sticky breadcrumb bar when it is the first element of the main column; the bar's own padding is retained.
A menu item can now be a link (flicker #1331).
menu_item/1renders a<button>unless givennavigate,patchorhref, in which case it renders a<.link>. Both keeprole="menuitem"andtabindex="-1", which is what the hooks' roving-focus model selects on, so the keyboard contract is unchanged.disabledon the link variant becomesdata-disabled+aria-disabled, since anchors have nodisabledattribute — the hooks' item query already skips both.breadcrumb_bar/1andapp_shell/1's action slots forwardnavigate/patch/hrefto the folded item. This closes a silent failure: a folded entry renders from the slot ATTRS, not the slot body, and the slot carried no destination — so an action whose inline button navigated became a menu item with no click target. It rendered, it was clickable, and nothing happened. Reported in flicker as "none of the items in the action dropdown are opening"; it affected every navigating entry past:max_inline.
Added
Chat kit components (flicker #1358). Added
avatar/1,message/1, and follow-awaremessage_scroller/1withmessage_scroller_item/1, including token-only styling and theLanternMessageScrollerhook.data_table/1gains alistview (flicker #1202). Third view alongsidetableandcards: a headerless index row for resource lists (projects, apps, databases). Opt-in via a:list_itemslot (mirrors:card); the toolbar, search, filters, sort controls, and pagination stay. Each row places slot content left and optional:row_actiontrailing right without wrapping the row in an anchor so actions stay independently clickable. View toggle shows a list option only when:list_itemis given. Callers without:list_itemrender as before.resource_list/1+resource_list_item/1— simple resource index (flicker #1202). Compound list for pages that show a handful of resources (projects, apps, databases, buckets) without data-table machinery.layoutis:list(full-width rows, hairline separators between items) or:grid(auto-fill card grid). Optionalnavigate/patch/hrefmakes the whole row the link hit target; trailing slot holds badges/meta. Host owns any list/grid toggle; no search, sort, pagination, or JS. No Fluxon equivalent.timeline/1+timeline_item/1— vertical event sequence with a marker rail (flicker #1202). Ordered list of steps: status dot (or optional icon), title, state label, preformattedattimestamp, and optional body.statuspaints the marker viadata-status(done/active/pending/danger/neutral); the statelabelalways renders as text so items stay legible in grayscale. Connector lines between markers hide on the last item via CSS:last-child. Pure presentational CSS, no JS. No Fluxon equivalent. Additive shapes::detailslot wraps title/label/at in a native<details>/<summary>disclosure (openfor initial state);label_position={:leading}(set ontimeline/1, overridable per item) placesatin a fixed-width column left of the rail (--lui-timeline-label-w);:markerslot replaces the dot/icon (e.g. avatar). Unused options leave existing markup unchanged.breadcrumb_bar/1gains an:actionsslot (Linear action bar, flicker #1202). Trail left, actions right-aligned on the same sticky chrome. The first:max_inlineentries (default 2) render as quick buttons; everything after that folds into a More menu built on the existing APGmenu/1(no new JS). The cap is fixed rather than width-based, because a responsive cap has to hide an inline button that is not in the menu, which makes that action unreachable at narrow widths. Without:actions, markup is unchanged.app_shell/1exposes the same region through optional:breadcrumb_actionsso the slot chrome stays consistent withbreadcrumb_bar/1.command/1— command palette (⌘K dialog). Modal combobox over a listbox, modelled on shadcn's Base UIcommandand built on the same dialog runtime asmodal/1(LanternUI.open_dialog/1/close_dialog/1, focus trap, scroll lock, Escape/backdrop dismissal), plus a global Meta/Ctrl+hotkey. Parts:command_group/1,command_item/1(with:icon/:description/:shortcutslots),command_empty/1,command_separator/1,command_shortcut/1. Keyboard: ↑/↓/Home/End move the highlight, Enter activates, Escape closes; focus stays in the input and the highlight is published witharia-activedescendant.Filtering is the consumer's. The component renders exactly the items it is handed and pushes the query upward, so a LiveView can answer from a database or a search index — there are no client-side match knobs.
Safe to mount globally. The
LanternCommandhook pusheson_search(%{"query" => query}, debounced) andon_select(%{"value" => value}) itself, so the palette renders no<form>and carries nophx-change/phx-submit; event names default to thecommand_*namespace. A global component with a genericphx-change="search"makes a host app's ownelement("form")andform[phx-change="search"]test selectors ambiguous — this one cannot. An item that carries its ownphx-clickdoes not also pushon_select, so per-item bindings are never double-fired. No Fluxon equivalent.Skeleton variants + labeled status.
skeleton/1gainsvariant="block|text|circle"(rendered asdata-variant) and an opt-inlabelthat wraps the placeholder in a politerole="status"region with a visually hidden label. Bare<.skeleton />renders as before; reduced motion still disables the pulse. (flicker #1026)scroll_area/1— themed scrollbar wrapper (flicker #1024). Pure CSS over native overflow scrolling (no JS hook):scrollbar-width/scrollbar-colorplus::-webkit-scrollbar, themed via--lantern-scrollbar-thumb/-track/-sizewith token fallbacks.orientationpicks the scroll axis;labelrenders a focusable, keyboard-scrollablerole="region"per the WAI-ARIA guidance for scrollable containers. No Fluxon equivalent.slider/1— single-thumb slider on the WAI-ARIA APG slider pattern (flicker #1023).field/name/valueform surface like the other form controls, with the value in a hidden input sophx-change/submit behave natively. TheLanternSliderhook owns pointer drag plus Arrow/Home/End/ PageUp/PageDown stepping on therole="slider"thumb;value_texttemplatesaria-valuetext(e.g."{value}%"). No Fluxon equivalent.menu/1+menubar/1— the WAI-ARIA APG menu-button and menubar patterns (menu_item,menu_separator,menubar_menu; no Fluxon equivalent). Clean-room from the APG: the component owns the trigger button soaria-haspopup/aria-expanded/aria-controlsare always wired, the popup is a trigger-labelledrole="menu", and theLanternMenu/LanternMenubarhooks run the full keyboard model on the shared overlay primitives — wrapping ArrowUp/Down, Home/End, roving tabindex, Escape back to the trigger, and horizontal ArrowLeft/Right across menubar entries that carries an open submenu along.jsdom-backed hook tests (
npm test).test/js/helpers/dom.mjsmounts a hook frompriv/static/lantern_ui_hooks.jsagainst a real DOM with spies forpushEvent/pushEventTo, so client behaviour can be asserted directly instead of inferred from rendered HTML.test/js/command_hook_test.mjscovers the palette end to end: visibility across LiveView patches, event targeting, the keyboard model, the debounce, and the empty state.mix testandnpm testare both run by CI. jsdom is a devDependency of a privatepackage.json; the Hex package is unchanged and still ships a dependency-free hooks module.
Fixed
command: search and selection were completely broken (stack overflow).LanternCommanddefinedpushtwice. JavaScript keeps only the last definition in an object literal, so thepushEvent/pushEventTorouter added for LiveComponent support was silently unreachable and the surviving method recursed into itself — every keystroke past the debounce threwRangeError: Maximum call stack size exceeded, andon_selectnever reached the server either. The router is nowpushTo/2and the search push ispushSearch/0. A bundle-wide test fails on any duplicated hook method, since nothing else — notnode --check, not the compiler, not a render test — reports one.
Changed
- Breadcrumb + page_header density cut ~3×. Trail is 0.6875rem with 0.75rem
icons and a ~1.35rem sticky bar; page title is 0.9375rem with minimal margins.
app_shellmain drops top padding when a breadcrumb strip is present. Saves vertical real estate without changing the goprint home/chevron structure. - Breadcrumb matches goprint chrome. Leading optional
homelink (house icon), chevron separators by default (textseparatorstill available), compact 0.8125rem labels. Stickybreadcrumb_barno longer uses a negative top margin underapp_shell(that was clipping titles).page_headertitle is body font at 1.125rem with normal casing — not brand-display caps.
Changed
- Breadcrumb chrome is layout-owned and compact.
app_shellgains a:breadcrumbslot that renders a stickylui-app-breadcrumbregion under the appbar (goprint-style density). Newbreadcrumb_bar/1exposes the same chrome for hosts that still own their outer shell;page_header/1is the matching compact title row.<.breadcrumb>now also accepts anitemslist of%{label:, path:}maps (product app chrome) in addition to the existing:itemslots, and the trail defaults to a tighter separator/smtype size.
Added
meter(flicker #1025) — semanticrole="meter"for scalar measurements within a known range (APG Meter pattern), distinct fromprogress(task completion).aria-valuenow/min/max, optionalaria-valuetext, sizes, and optionallow/high/optimumthresholds that color the fill viadata-state(optimal/suboptimal/critical). Pure CSS, no JS. No Fluxon equivalent.nav_itemnow nests (Fluxon-parity expandable nav) via a:subnavslot. Pass a:subnavof nestednav_items and the item becomes a toggle with a chevron over agrid-rows-based slide panel, opened/closed client-side withJS.toggle_attributeondata-expanded— the same expandable pattern Fluxon'snavlinkused, now built intoapp_shell'snav_item.expandedsets the initial open state (e.g.expanded={@in_this_section?}); the subnav is hidden on the collapsed icon rail.
Fixed
app_shell/1gets a real mobile navigation drawer. Below 768px the sidebar used to flatten into a horizontal scrolling strip under the app bar, which is unusable once the nav has more than a handful of items — group labels were dropped and everything else had to be side-scrolled through a ~3-item window. It is now an off-canvas drawer behind a hamburger in the bar, over a scrim, with the full grouped nav and labels. It closes on scrim click, Escape, widening past the breakpoint, and on tapping a nav item — so anavigatelink no longer leaves the drawer covering the page it just went to. The collapsed icon-rail rules are now scoped tomin-width: 769px, so a persisteddata-collapsedno longer strips the labels (or the:subnavchevrons) out of the mobile drawer.app_shell: a full-heightdata_table fillno longer leaves a ~4rem dead band at the bottom..lui-app-main's 4rem bottom gutter is sized for scrolling content; under a.lui-datatable-fillpanel it sat below the table so the pinned pagination stopped ~64px short of the viewport. When main directly holds a fill table, the bottom gutter now matches the sides (1.5rem), so the table reaches the bottom with a balanced inset.
Added
accordion/1+accordion_item/1— a headless-driven, WAI-ARIA APG accordion with the Fluxon 2.3.1 public API. A Fluxon call migrates by changing onlyuse Fluxontouse LanternUI: containermultiple,prevent_all_closed, andanimation_durationoptions; itemexpandedandiconoptions; requiredheader/panelslots with slot classes; optional generated ids; and global attrs are supported. The namespacedLanternAccordionhook owns open/close and the APG keyboard model (Enter/Spacetoggle,ArrowUp/ArrowDown/Home/Endbetween headers). Nested accordions are isolated; client state and focused headers survive generated-id LiveView patches/reconnects; the sole required-open trigger is exposed asaria-disabled. Panels stay in the DOM (hidden) so ARIA idrefs always resolve and collapsed content leaves the tab order + a11y tree. The chevron respects bothanimation_durationandprefers-reduced-motion. Passes the ARIA conformance gate. Implemented clean-room from the public Fluxon API facts and W3C WAI-ARIA APG; no commercial Fluxon or Chelekom expression copied (seedocs/upstreams/chelekom.md, flicker #921).skeleton(flicker #1029) — a standalone CSS-only decorative loading placeholder with class/style/global attribute passthrough, token-driven color and radius, a visible default geometry, and reduced-motion-safe pulse.stat_card/stat_grid(flicker #1009) — standalone, responsive KPI summaries extracted from DataTable's existing overview stat renderer. Cards support label/value plus optional subtitle, host heroicon, navigation target, and merged classes; unlinked cards use non-interactive semantics and long values wrap safely. An empty slot-driven grid emits no wrapper. DataTable now delegates its:statstructure and appearance to the same renderer.
Changed
nav_itemicons accept host heroicons (hero-*), not just lantern's icon set.icon="chart-bar"still renders a lantern inline SVG, buticon="hero-home"now renders as the host's CSS-mask span (the same convention Phoenix apps use for<.icon name="hero-…">) so an app can migrate its hand-rolled sidebar ontoapp_shell+nav_group/nav_itemwhile keeping its own heroicons — lantern doesn't have to own every glyph (it has nohome/cog). The mask span is constrained to lantern's icon size via a marker class that out-specifies the.hero-*size regardless of stylesheet order. Requires the host's heroicons CSS to be loaded (already true in the target apps).app_shellmain is now a bounded, independently-scrolling region on desktop — sodata_table fillworks inside it. Previously.lui-appwasmin-height: 100vh(whole-page scroll) and.lui-app-mainhad no height/flex, so a.lui-datatable-fillchild had no bounded parent to fill: full-height tables with pinned header/overview/pagination silently didn't fill. Now, atmin-width: 769px,.lui-appis viewport-height withoverflow: hiddenand.lui-app-mainis abox-sizing: border-boxflex column of heightcalc(100vh - appbar)withoverflow-y: auto. Normal pages scroll inside main exactly as before (the bar is alreadyposition: fixed); a fill table now fills the height and pins its chrome. Mobile (≤768px, sidebar becomes a strip) keeps natural page scroll. This unblocks migrating apps off hand-rolled shells ontoapp_shellwithout losing full-height tables.
Fixed
data_table fillnow pins the column header. In fill mode the table body is the scroll region, but nothing inlantern_ui.csswas sticky — so on any list taller than the viewport the<thead>scrolled away with the rows and you lost the column labels (and the sort controls) exactly when a long list makes them matter most. The header/chrome/overview/pagination already stayed pinned; only the column header was lost..lui-this nowposition: stickyinside a fill-mode scroll region. Because.lui-tableusesborder-collapse: collapsethe border belongs to the table rather than the cell — a stickyththerefore drops itsborder-bottomon scroll — so the hairline is redrawn as an inset shadow. Scoped to fill mode; default tables are unchanged.lantern_ui_compat.cssnow defines the--lantern-*component tokens. The file's docs said to import it instead oflantern_ui_theme.cssand that it "themes LanternUI for free", but it only defined the Fluxon host tokens — never the--lantern-*values the components actually read. Following the documented path left every--lantern-*undefined, so components rendered with square corners (no--lantern-radius), no surfaces/backgrounds (no--lantern-surface), and broken dropdowns. Every migrating app hit this and had to hand-vendor the bridge (flicker did; enventory/skusync/goprint/foodfeed forgot and shipped broken styling). compat.css now carries the bridge, soimport compatalone themes the components. Backward compatible — an app's own--lantern-*overrides declared after the import still win.
Changed
use LanternUI, only:/except:now accepts Fluxon-stylefunction: aritypairs in addition to component-key atoms — so a host can drop a single colliding function (use LanternUI, except: [icon: 1]) instead of a whole module. Enables clean drop-in over apps that already defineicon, etc.
Added
alert_dialog(flicker #1028) — semantic destructive confirmation composed on the existingLanternModalruntime. Requires title, description, cancel, and action slots; wiresalertdialoglabel/description idrefs, focuses the cancel control first, ignores outside clicks, and retains Escape dismissal, focus trapping, and trigger-focus restoration.progress(flicker) — determinate + indeterminate progress bar / meter (sizes, semantic colors, optional shimmer), pure CSS.color_input— native color picker (Fluxon parity): a real<input type="color">styled as a swatch plus a read-only hex readout, no JS. Standard field chrome (label/sublabel/description/help/errors), FormField-aware,sizexs–xl. Accepts thename/value/labelsurface the playground theme editor already uses.autocomplete(flicker #892, #1027) — static and async typeahead (Fluxon 2.3.1 public API parity): local contains/starts-with/exact matching or debounced LiveViewon_search, loading and custom empty states, nested option groups, rich option/header/footer/affix slots, clear/open-on-focus modes, and active-descendant keyboard/ARIA behavior. FormField-aware over a hidden value input; selected labels and focus survive result patches.LanternAutocompletehook.
Added
navlist/navheading/navlink(flicker #890) — standalone structured nav (Fluxon parity): navlist with optional heading, navlink that renders a link (navigate/patch/href) or button, active state + optional icon.loading(flicker #891) — spinner (Fluxon parity): variantsring/dots-bounce/dots-fade/dots-scale, sizes xs–xl,role="status"+ sr-only label, reduced-motion aware.
Added
sheet(flicker #883) — slide-over/drawer panel mirroring Fluxon'ssheet/1. Shares the dialog runtime withmodal(open_dialog/close_dialog), slides fromleft/right/top/bottomwith enter+exit animation, focus trap, scroll lock, Escape/backdrop dismiss, optional header title + sticky footer slot.LanternSheethook;[hidden]display guard.
Changed
- Fluxon token compatibility (flicker #882): every
--lantern-*color now chains through the matching Fluxon semantic token (--primary,--foreground,--foreground-soft/softer,--foreground-primary,--background-base/accent,--surface,--border-base,--danger/success/warning/info) before its hard fallback. A flicker/Fluxon theme — defined in exactly those names — now flows straight into lantern-ui with no bridge and no component changes; standalone keeps the shadcn monochrome look via the fallbacks. Non-destructive: only the theme file gained the mapping layer. Under a theme,--lantern-primary/--lantern-accentboth pick up the brand--primary(matches flicker's single-brand look).
Changed
toast_groupplacement now supports all six positions —top-left,top-center,top-right,bottom-left,bottom-center,bottom-right(was three). Toasts enter from the nearest screen edge (slide down from the top, up from the bottom) and the newest stays closest to the edge.
[0.7.0] - 2026-07-11
Added
- data_table closure primitives (flicker #874) — five standalone components:
badge— status pill (Fluxon-API: color × variant × size)table/table_head/table_body/table_row— presentational table family (Fluxon-API), the substrate the upcomingdata_tablecomposestabs/tabs_list/tabs_panel— segmented or underline tabs (Fluxon-API), server-driven; tabs givenpatch/navigaterender as links so tab state can live in the URLselect— FormField-aware select (Fluxon-API): rich listbox path (LanternSelecthook — positioning, keyboard nav, type-ahead) over a hidden input, plus anativefallback;searchable/multipleaccepted for compatibility, implemented laterpagination— pager + page-size control (lantern extension; replaces flop_phoenix's pager). Duck-typed againstFlop.Meta's shape, so no flop dependency; all navigation is patch-based
Form.translate_error/1is now public.data_table(flicker #867) — the admin table that replaces the per-appadmin_tablecopies. API mirrors the enventory/skusync baseline (col/ bulk_action/row_action/toolbar/header_action/empty slots, selected_ids, toggle_select / select_all_page / clear_selection events). Flop.Meta duck-typed (no flop dep); sorting + pagination are patch navigation that preserves existing query params (filters); bulk bar, selected-row styling, EmptyState fallback, composed on the new primitives.data_tablechrome (flicker #868): collapsible stat-card overview strip (:statslots, persisted per-id via the new genericLanternCollapsehook), patch-link tabs with count badges mapping to Flop filter presets (:tabslots, active tab detected from current filters), built-in debounced search (search_field) and typed filter selects (:filterslots) that build Flop filter params client-side and patch the URL — zero page-level handlers (LanternTableChromehook rides LiveView's own patch navigation) — and a table ⇄ cards view toggle (:cardslot +viewattr).data_tabletoolbar rework (review): filters moved into a popover ("Filters" button with active-count badge + clear-filters), the toolbar is one right-aligned row (filters → search → actions), the bulk bar gains "Select all N" (emitsselect_all_matchingfor whole-result-set selection), and collapse hooks re-apply their state after LiveView patches (the overview/sidebar collapse no longer resets on re-render).data_tabletitle section:subtitleattr andinfo_modal_id— an ⓘ button beside the title that opens the given modal viaLanternUI.open_dialog(baselineadmin_tableparity).data_tablecard shell + chrome row (design review): the table renders as a contained card (header with title/info, hairline sections, footer pagination); tabs ("quick filters") share one chrome row with everything else — left-to-right: tabs · quick actions (toolbarslot) · search · view toggle · settings popover (rightmost).:filterslots gaintype::select(default),:text, and:range(min/max →>=/<=Flop filters).select:multiple+searchableimplemented (Fluxon parity): multi keeps the listbox open, maintains one hiddenname[]input per pick, shows a count label, honorsmax; search filters options client-side (search_thresholdauto-enables at N options;search_input_placeholder/search_no_results_texthonored).data_table:filterslots acceptmultiple/searchableand emit Flopinfilters withvalue[]lists.
[0.6.0] - 2026-07-08
Changed
Layoutreshaped into an app shell (breaking):sidebar_layout→app_shell— a full-width top bar (:brandcorner +:headerinline context +:actionsright) over a fixed collapsible left sidebar, with the collapse control at the sidebar foot. Mirrors a typical product app layout so a Fluxon layout can migrate onto it.nav_group/nav_itemunchanged.
Added
--lantern-font-brandtoken (brand/heading face; defaults to the body font, override to e.g. Space Grotesk).- Icons: cursor-arrow-rays, sparkles, pencil-square, calendar, check-circle, window, chevron-up-down, inbox, presentation-chart-line, arrow-trending-up, view-columns.
[0.5.0] - 2026-07-08
Added
- App-shell layout (
LanternUI.Components.Layout) — the core Fluxon-style navigation chrome:sidebar_layout(fixed sidebar + main + optional topbar),sidebar_header(logo corner),sidebar_nav/nav_group/nav_item(grouped nav with icons + active state), andsidebar_toggle(collapse to an icon rail, persisted per-id in localStorage via the newLanternSidebarhook). Collapses to a horizontal strip on narrow viewports. - Icons:
bars-3,squares-2x2,chart-bar,circle-stack,cloud.
[0.4.1] - 2026-07-08
Changed
- CSS split into two artifacts (per the embeddable-core architecture):
lantern_ui.cssis now the always-required component styles, and the optional default theme (tokens, light/dark, density) moved tolantern_ui_theme.css. Standalone hosts import both; Fluxon/token-bearing hosts import only the component styles and bridge--lantern-*onto their own tokens (example in the file header). Migration: addlantern_ui_theme.cssnext to your existinglantern_ui.cssimport.
[0.4.0] - 2026-07-08
Added
- Modal (
modal/1+LanternUI.open_dialog/1,2/close_dialog/1,2) — Fluxon-compatible dialog on the shared overlay runtime: focus trap, Escape/outside dismissal,prevent_closing, placement, token-driven fade. - Dropdown menu (
dropdown/1+dropdown_header/separator/link/button/custom) — Fluxon-compatible family with WAI-ARIA menu keyboard navigation. - Checkbox (
checkbox/1) — Fluxon-compatible,FormField-aware, hidden unchecked-value input, label/description/error states. - Breadcrumb (
breadcrumb/1) — path navigation for file/tree UIs (lantern-ui extension). - Empty state (
empty_state/1) — quiet zero states with icon, title, description, and action slot (lantern-ui extension). - Icons:
folder,folder-open,document,arrow-up-tray,arrow-down-tray,arrow-path,trash. - Hooks:
LanternModal,LanternDropdown.
[0.3.3] - 2026-07-08
Fixed
- Picker panel now carries the popover chrome itself (background, border, shadow) — the time pane and footer no longer float transparent below the calendar's box.
- Open panels follow their anchor during scroll/resize instead of staying pinned to the viewport.
- The panel time pane no longer shows its own
∅clear glyph (the footer's Clear covers it).
Added
- Explicit
.lighttoken block, so a subtree can force light mode under a dark OS (mirror of.dark).
[0.3.2] - 2026-07-08
Added
date_time_pickerpanel now includes a time pane — a non-submitting segmented time field under the calendar, kept in two-way sync with the trigger (newlantern:set-timeevent on the field hook). Picking a time with no date yet defaults the date to today.
Fixed
- Segmented field no longer wraps onto multiple lines in narrow hosts
(explicit
flex-wrap: nowrap+ overflow guards on.lui-dtf).
[0.3.1] - 2026-07-07
Fixed
date_picker/date_time_picker/time_pickernow accept aformattribute and forward it to the hidden value input, so the pickers work as editors rendered outside their<form>element (e.g. lantern's table cell editors, which submit viaform="...").
0.3.0 - 2026-07-07
Added
- Component system foundation: full design-token set in
priv/static/lantern_ui.css(semantic colors, shadcn-density scale — 32px controls / 13px text — monochrome primary + coral accent, light/dark,data-lantern-densitymodes), theuse LanternUIimporter with Fluxon-compatible:only/:except,LanternUI.Classhelpers, and a dependency-free JS runtime core (position,trapFocus,onDismiss,LanternOverlay). - Primitives (Fluxon-compatible API,
lui-*namespaced CSS, zero build step):button/button_group,icon(curated inline Heroicons-outline set),form.input/label/error,calendar(WAI-ARIA month grid +LanternCalendarhook with the full APG keyboard model), and the segmenteddatetime_field(typeable/steppable segments to millisecond precision, hidden canonical input, clear-to-null) with theLanternDatetimeFieldhook. - Pickers:
date_picker,date_time_picker(calendar popover with Today/Now · Clear · Done), andtime_picker(segments-only). Canonical valuesYYYY-MM-DD/HH:MM:SS.mmm/YYYY-MM-DDTHH:MM:SS.mmm; empty = null. Accepts Date/Time/NaiveDateTime/DateTime structs or strings.
Fixed
line_chartcrosshair tooltip: size the box to the longest label + value so long series names (e.g. Kubernetes pod names) no longer collide with the right-aligned value.
Added
LanternUI.Charts.line_chart/1— multi-series time-series line chart with a legend and a shared crosshair tooltip (LineHoverhook). Accepts%{label, color, points: [{datetime, number}]}series; 0-based y axis; built for resource/monitoring metrics.- Initial chart set as native Phoenix LiveView function components under
LanternUI.Charts:area_chart/1,sparkline/1,bar_chart/1. LanternUI.Charts.Geometry— pure scaling, "nice" tick, and SVG path helpers.ChartHoverLiveView JS hook (priv/static/lantern_ui_hooks.js) for the crosshair + tooltip onarea_chart.- Optional standalone theme (
priv/static/lantern_ui.css); components otherwise inherit host CSS variables (Fluxon-compatible).