noir 0.0.2
noir: ^0.0.2 copied to clipboard
A Flutter-like reactive terminal UI framework for Dart, powered by OpenTUI.
Changelog #
0.0.2 #
This release changes where Noir lives in its repository, not what it does.
The package moved to packages/noir, next to noir_signals, so the README
and the package metadata now link to the new paths. The library, its public
API, and the bundled native artifacts are unchanged from 0.0.1.
Changed #
- README, example, and notice links point at
packages/noirin the repository. Links on the 0.0.1 pub.dev page that name the old root paths no longer resolve. - The package
repositoryfield namespackages/noir.
0.0.1 #
This initial 0.0.x release adds LayoutBuilder and one framework lifecycle
seam, fixes focus recovery and keyed ListView rows, and moves the opt-in
hooks library to a separate package. The native ABI and the bundled native
artifacts are unchanged from alpha.4.
Added #
-
Mouse pointer shapes for OSC 22 terminals: enabled buttons, checkboxes, switches, and sliders request a hand, text fields request an I-beam, and custom
PointerListenerregions can setmouseCursor. Pointer selection follows render-tree hit testing and updates after layout even when the mouse is stationary. High-level mouse reporting now includes movement by default; explicitenableMovement: falsepreserves click/drag-only reporting. iTerm2 uses legacy OSC 22 shape names and restores its default pointer on exit. Select and selectable ListView rows, including popup menus, show a hand only over valid visible options. TabSelect requests a hand over tab cells while leaving its underline and description unchanged. Unsupported terminals retain their own pointer. -
Terminal output stays synchronous; removing redundant asynchronous stdout flushes prevents immediate mouse setup or cleanup from failing with a bound stream sink.
-
Added
LayoutBuilder, which builds its child from the incomingBoxConstraints. The builder runs during layout and its result is laid out in the same frame, so the constraints it reads are the constraints its child receives. It runs on the first layout, on a constraint change, on a widget update, on an inherited dependency change, and on reassembly; repeated layout at unchanged constraints does not run it. A builder that throws leaves the element marked, so the next layout retries. -
Added
State.deferDisposeandHookState.deferDispose. A host registers a cleanup for a resource it retires while the descendants built by the previous configuration may still read it. The framework releases that resource after the host successfully updates its descendants and the removed descendants finish unmounting, or during unmount beforeState.disposeruns. Batches release descendants before ancestors, registration order is preserved inside one host, and a failed initialization, widget update, or build keeps the resource alive until a later reconciliation succeeds.
Fixed #
- A component rebuilding below
Row,Column,Stack, orWrapnow reconnects a replacement render object in its existing child slot. Local changes to a component's root widget previously removed its visible output until the parent rebuilt. Sibling state and order, flex/position data, and pointer routing remain attached to the correct child. Flexible,Expanded, andPositionednow preserve their layout behavior through non-render component wrappers, including metadata-only local updates. Invalid placement, duplicate metadata on one render edge, and non-positive widget flex values now reject in release mode instead of silently losing their layout behavior.- Plain
ListViewinstances now preserve valid caller scroll positions when mounting, changing controllers, or changing item counts. Switching off selection keeps the current window, and simultaneous controller/selection changes follow the new highlight without first jumping to the old one. - The packaged hot-reload runner now recompiles detected edits whose file timestamps are not newer than the last successful reload, including recovery after a rejected reload. The VM's own timestamp filter skipped such an edit while reporting success, so the app reassembled stale code. Only that reload is forced, which recompiles every source and takes longer; an ordinary save is newer than the last reload and still takes the incremental path. The runner log names the path taken.
FocusManagernow recovers focus after an involuntary loss. Disabling the focused control or removing the region that owns focus previously left the tree with no primary focus, andShortcuts.handleKeyEventroutes from the focused element, so the tree stopped answering every binding. Recovery runs after the synchronous tree updates finish and focuses the first control inside the nearest surviving explicit scope, so bindings mounted inside that scope answer again; the scope itself takes focus only when it holds no control. It then falls back to the first node in traversal order, and leaves focus empty when nothing is eligible. An explicitrequestFocus()and an incomingautofocusboth claim focus first, and an intentionalunfocus()is not recovered.autofocusis now honored when it is enabled after mount.- A layout request raised during layout is now answered in the same frame.
PipelineOwner.flushLayoutused to run one pass and then discard any render object marked while that pass ran, for example by aLayoutBuilderwhose build touched a render object already laid out or still performing layout, and nothing retried it. The flush now runs another full pass while such requests remain, up to three passes, and then throws aStateErrornaming the render objects that kept requesting layout from inside layout. A request followed by that object's layout in the same pass does not cost a second pass. Adopting a child during layout also invalidates its active parent and conservatively retries that parent. Architecture tests now freeze the layout-time build seam toLayoutBuilderand keep the rendering layer free of element-layer imports. - A keyed
ListViewrow now names exactly one element. The row'sLocalKeywas copied onto theSizedBoxwrapping it, so the key named two elements and a strict driver locator reported an ambiguous match. A keyed row that stays in the window still keeps itsStatewhile the window scrolls or the rows reorder, and a row that leaves the window is still disposed. ListViewnow follows its highlight when its own height or item extent changes, so a resized list keeps the selected row on screen while the controller keeps a valid scroll position.- App startup now releases acquired resources if terminal-mode activation or final registration fails, preserving the original error if cleanup also fails. Rendererless headless mouse requests are rejected before mounting.
- Changed terminal pixel measurements now repaint an idle scene and update natural image sizes without an unrelated widget rebuild. Duplicate and invalid measurements still leave an otherwise clean scene unpainted.
- Windows terminal sessions now check for size changes every 100ms and update layout and rendering when dimensions change. Checks stop when the session closes and do not run in headless or non-terminal sessions.
Changed #
- The task-list walkthrough is now a five-lesson tutorial. Lesson 1 stays in
doc/getting-started.md; lessons 2 to 5 live indoc/tutorials/task-list/. Each lesson keeps its exact changes, complete runnable checkpoint, and screenshot, and its earlier checkpoints ship as runnable files underexample/tutorials/task_list/. The shippedexample/task_list.dartis the final checkpoint, so the tutorial and the example cannot drift apart. The old step anchors still resolve from the tutorial's first page. doc/hooks.mdanddoc/signals.mdare reference documents. Their introductions no longer restate the tutorial, and dependency setup points at the package overview.- The core
example/directory now carries the two first-app tutorial checkpoints underexample/tutorials/first_app/. The published documentation captures its terminal frames from those exact files.
Removed #
- Removed
package:noir/hooks.dart. The hook runtime, the built-in resource hooks, and their guide now live in the optional companion packagenoir_signals, published frompackages/noir_signals/in this repository. Replacepackage:noir/hooks.dartwithpackage:noir_signals/noir_signals.dartand addnoir_signalsto the application's dependencies. RenameHookWidgettoSignalWidget,HookBuildertoSignalBuilder, andHookWidgetBuildertoSignalWidgetBuilder. Every hook function, return type, and lifecycle rule is unchanged:useStatestill returns aValueNotifieranduseTextEditingControllerstill returns aTextEditingController.
0.0.1-alpha.4 #
This release corrects the package archive and moves checkout-only code to its owning directories. Noir's public API shapes and runtime behavior, native ABI, and bundled native artifacts are unchanged from alpha.3.
Changed #
- Large example implementations now live under
example/src/, while every runnable entry point remains directly underexample/. Shared example chrome has one owner underexample/src/shared/. - Package and contributor guidance now distinguish supported library surfaces, publishable examples, repository-only scripts, and website sources.
- Checkout-only entry points
bin/patch_manager.dartandbin/snapshot_scenes.dartnow live underscripts/, and the Patch Manager implementation now lives underscripts/patch_manager/. Both entry points were already excluded from the published alpha.3 archive.
Fixed #
- The archive now includes
third_party/opentui-v0.5.1/LICENSE-YOGAand names Yoga v3.2.1 inTHIRD_PARTY_NOTICES.md. The published0.0.1-alpha.3archive omitted this MIT license notice even though Yoga is compiled into the bundled OpenTUI libraries.
0.0.1-alpha.3 #
This release includes the work from the unpublished alpha.2 candidate; no
0.0.1-alpha.2 package was published.
Post-release notice: The published
0.0.1-alpha.3archive omitted the Yoga v3.2.1 MIT license notice for the Yoga sources compiled into the bundled OpenTUI native libraries. The repository correction will ship in the next package version; the published alpha.3 archive cannot be changed.
Added #
- Added
Theme/ThemeDataand the first themed component tier:ListView,Checkbox,Switch,Button,Divider,ProgressBar,Spinner,Badge, andDataTable. Existing inputs, selects, and scroll views also resolve omitted colors through the nearest theme. - Added the opt-in
package:noir/hooks.dartlibrary withHookWidget, state and effect primitives, async and listenable observation, and hooks for Noir controllers and animations. - Added whole-cell
Stack/PositionedandWrap; horizontalTabSelect; horizontal and verticalSlider; seven originalAsciiFonttreatments; and rich, selectableTextTablegrids. - Added selectable
CodeView, unified and splitDiffView, and GitHub-flavouredMarkdownViewwith grapheme-safe selection, OSC52 copy, async highlighting, GFM tables, and semantic terminal hyperlinks. - Added
TerminalImageand the statefulImagewidget for PNG, JPEG, WebP, first-frame GIF, and raw RGBA sources. They negotiate Kitty, Sixel, or block rendering and support measured pixel sizing, fit modes, cancellation, and explicit ownership. - Added the root
OverlayPortal/OverlayPortalControllersurface and launcher-anchoredMenuAnchor/MenuControllermenus. This prerelease API does not claim Flutter's nested overlay, transform, animation, or cascade behavior. - Added
Icons, a width-audited catalog of 119 single-cell glyph strings. Terminal icons remain plain text (Text(Icons.check)), not anIconwidget orIconDatahierarchy. - Added the packaged
dart run noir:rundevelopment command. It watches Dart sources, performs VM reload plus Noir reassembly, preserves the last good app after rejected edits, forwards arguments and exit status, and records diagnostics in.dart_tool/noir/run.log. - Added headless drive mode, with a repository-only Dart client and CLI,
parser-backed key and pointer input, exact key/type/text locators, focus
snapshots, and production hit-test-path clicks.
scripts/noir_drive.dartacceptskey space. - Added six guarded raw scissor- and opacity-stack operations to
package:noir/noir_ffi.dart. These expose pinned OpenTUI availability only: its opacity stack does not fade ordinary text and cannot underpin anOpacitywidget. - Added the live Pub search example with injected test data, stale-response suppression, paging, completion suggestions, anchored sort/filter menus, responsive package details, and rendered health and advisory documents.
- Added opt-in chronological tail following to
ScrollControllerthroughfollowTailandisFollowingTail. An opted-in controller starts at the trailing extent, follows content growth and reflow, detaches when scrolled above the end while preserving the top-row offset, and reattaches on return. - Added
TextArea.maxHeightfor bounded visual-row growth,softWrapfor grapheme- and cell-aware wrapping, andsubmitOnEnterso Enter submits while a distinguishable Ctrl+J inserts a newline and multiline paste stays intact. - Added
DiffView.canRequestFocus, so a read-only embedded preview stays out of the enclosing surface's keyboard traversal, andMarkdownView.tableCellPaddingX, which defaults to0to match the pinned OpenTUI Markdown renderer and leaves the choice to the application. - Added the agent chat example: a product-neutral transcript whose
presentation consumes a semantic backend and never learns which agent
produced an event. A deterministic offline replay backend is the default,
and an opt-in bounded Claude CLI backend behind
--claude=/absolute/pathdrives the same reducer and widget tree. The adapter startsclaude -pwithout a PTY in safe mode, keeps tools, slash commands, MCP servers, and session persistence off, and exposes no permission channel.
Changed #
- Simplified startup to
runTuiApp(const MyApp(), enableMouse: true).runTuiAppowns hot-reload registration and terminal dimensions;TuiApp.exit(context)now performs in-tree disposal and exit-code handling. - Added
BuildContext.mountedandState.reassemble(), hardened the State teardown window, made exact-type inherited lookups truly exact, and made framework-owned focus and text-controller replacement transactional. - Focus nodes can move between inactive elements in the same focus manager without losing focus. Focused lists, selects, and tables use the selected theme color and mute that highlight when focus moves elsewhere.
- Standardized example chrome around terminal-native titled regions and moved
Patch Manager's tracked-file presentation to
DiffViewwithout changing its staging and action model. DiffViewfills an added or removed row to the viewport edge, and a split row fills each half up to its own edge, which matches the pinned OpenTUI diff renderer. Earlier rows painted their background only under the text.MarkdownViewrenders-for an unordered list item, which matches the pinned OpenTUI Markdown renderer. Earlier lists used a bullet glyph.
Fixed #
- Restored legacy raw Ctrl+A selection and Ctrl+C copy in document views while
preserving enhanced key reports with associated text. Interactive sessions
request xterm
modifyOtherKeysmode 2 so Ctrl+C remains application input through tmux, then restore xterm and Kitty keyboard modes independently. - Tightened renderer and terminal lifecycle cleanup: late resizes are ignored, closed sessions release renderer references, borrowed native buffer views are invalidated after render attempts, stale native draw stacks are cleared, and best-effort finalization cannot leak a cleanup exception.
- Fixed flex overflow, decorated-child, and clipped-buffer painting at viewport
boundaries; image replacement and cancellation ownership; one-ticker
Spinnerbehavior; swappedListViewviewport anchoring; andSelectscroll indicators under tighter layout constraints. - Pub search completion starts fresh hosted requests for each eligible prefix, keeps either successful endpoint when the other fails, and does not retain a stale completion corpus.
ImageProtocol.autouses block cells under tmux. Forced Kitty graphics through tmux remain unsupported for alpha.3 because placement may stay displaced until a resize.TextAreaandTextInputpaint each grapheme cluster whole. The earlier per-cell path kept only a cluster's first scalar, so an extended cluster such as a zero-width-joiner emoji lost its tail.ScrollBoxclips a descendant editor's terminal cursor to the same viewport as its painted cells, so an editor scrolled out of view no longer positions a stray cursor outside the viewport.
Removed #
- Breaking prerelease change: removed the unused
ColorSupport,TerminalCapabilities,TerminalSize, andCapabilitiesDetectionhelpers without compatibility shims.
0.0.1-alpha.1 #
- The native-asset build hook now declares
native_manifest.jsonand the selected bundled library as file-system dependencies. Dart can therefore invalidate cached hook output, repeat SHA-256 verification, and regenerate the derived macOS bundle copy when either input changes. - Added focused regression coverage for the hook dependency declarations and documented how to suppress routine build-hook status without skipping native asset verification.
- Added the Like Reactor example, demonstrating deterministic heart particles, animation-driven morphing, and overlapping keyboard and mouse activation.
0.0.1-alpha.0 #
- First public alpha of Noir's Flutter-like reactive widget framework for terminal applications.
- Declarative stateless and stateful widgets,
setState, layout, text styling, focus, keyboard and mouse input, text editing, scrolling, and animation. Alignpositions naturally sized children within bounded boxes while still filling bounded axes and shrink-wrapping unbounded axes.- Hot reload support:
TuiApp.reassemble()rebuilds every mounted element and forces a full layout and paint pass, preservingState, focus, scroll, and animation state and recreating no terminal or native resource. - Opt-in
registerHotReloadExtension(app)publishes theext.noir.reassembleVM service extension so a development driver can rebuild a running app afterreloadSources. - The Flutter-inspired counter example composes a flat blue app bar, centered
body, and solid square-style clickable action surface. Up/Down,
+/-, Enter, Space, and left click drive its state; an unconsumed Ctrl+C key follows the terminal session's cleanup and interrupt exit path while higher-priority handlers can override it. - Terminal shutdown restores inherited line and echo modes before cancelling
the stdin subscription, avoiding Dart/macOS
EBADFerrors during Escape or other normal disposal paths. - Terminal input preserves modifiers from xterm
modifyOtherKeysreports, maps Home/Insert/End and function-key reports to pinned OpenTUI semantics, and retains press/repeat/release metadata from Kitty functional and tilde reports. The multiline examples use Ctrl+D as a portable submit key whileTextAreastill accepts Ctrl+Enter when the terminal reports that chord. - Bundled, SHA-256-verified OpenTUI native libraries for macOS, Linux, and Windows on x64 and arm64.
- Canonical OpenTUI v0.5.1 source and unchanged official release assets.
- macOS bundles require macOS 13.0 or later.
- See Known Limitations for current platform and rendering constraints.