Skip to content

nsh v0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Nov 12:23
· 15 commits to main since this release
e626a8d

Native Shell 0.4.0 fills out the unstyled component foundation with form controls, rich text and more chart marks; adds shader effects and transitions; runs web apps on the browser's own JavaScript engine; and gives the APIs apps use one consistent shape.

Upgrading from 0.3.x? This release includes breaking API changes. See the migration notes below before rebuilding your app.

Highlights

  • Shaders across the UI. Draw a view's box with WGSL using Shader, draw views and the whole screen through a program with ShaderEffect, ShaderTransition, ScreenEffect and ScreenTransition, and let pages and dialogs come and go through one with transitionShader. All of it draws on macOS, iOS, Android, Windows, Linux and the web; the GPUI backend draws Shader fills. (#517, #682, #763, #764, #765, #766, #767, #768, #769, #770)
  • A full set of form controls. Build forms and toolbars with Select, Combobox, Slider, NumberInput, OtpInput, DatePicker, Calendar, TimeField, ColorPicker, Toggle / ToggleGroup, Toolbar, Link, Pagination, Avatar / AvatarGroup and Accordion. They provide behavior, state and accessibility; your app supplies their look. (#536, #585, #510, #534, #535, #598, #577, #621, #702, #624, #690, #706, #610)
  • Rich content and navigation. Edit documents with RichEditor, a ProseMirror-style editor with tables; add a command palette with Command, nested lists with Tree, slides with Carousel, hover previews with HoverCard and scroll bars in your own look with Scrollbar. TextView typesets formulas out of the box. (#494, #518, #785, #725, #722, #697, #579, #841)
  • More charts from nsh/base/plot. Compose Bar, Pie, Arc, Sankey, Treemap, Dot, Cell, Rule, RadialLine and RadialGrid marks with PlotAxis and PlotLabel, color marks by category, plot against a second y scale, and move a plot's hover with the keyboard and screen readers. (#505, #501, #601, #867, #829, #861, #819, #596, #549, #599, #592, #839, #521)
  • Web apps on the browser's own engine. Every web app now runs on the browser's JavaScript engine, in development with hot updates too. The runtime loads its parts on demand, which takes the gallery's first load from 13.03 MB to 4.35 MB (4.01 MB to 1.41 MB gzipped), and published sites carry Brotli and gzip copies of their files. (#541, #603, #853)
  • More of the system and the toolchain. Follow whether the app is in use with lifecycle(), recognize speech with Speech.start on macOS and iOS, read shipped files with fetch on asset:/ URLs, and use WOFF and WOFF2 fonts with fallback along fontFamily lists. Rust apps gain file dialogs, system paths, window and menu APIs and svg(); an app's crate can register native components in its generated hosts; and nsh publish <package> signs an existing package again with another release key. (#747, 0520080, #746, #745, #773, #629, #674, #683, #886)

Performance and platform improvements

  • Layout rounds to device pixels, frames that stay on their pixels stay unsent, and a subtree that moves by whole pixels skips its frame walk. Animation graphs evaluate each node once a frame and find interpolation segments by bisection. (#557, #617, #637, #588, #589, #590)
  • Images load through one bounded pipeline on the desktops, taking peak memory from about 1.3 GB to about 370 MB when scrolling 1,000 remote photos as thumbnails on Linux. Pasting 474 KB of unbroken text into a multiline Input takes 58 ms where it took 12 s on macOS, and DataTable, Editor, Select, Calendar and the input controls do less work as they scroll and take keys. (#674, #627, #622, #604, #638, #656, #817, #672)
  • iOS keeps focused inputs above the keyboard and launches on iOS 15 to 17, adds leading and SF Symbol bar buttons, draws backdrop blur behind a view's background, and draws system alerts and the TabBar in the app's theme colors. (#821, #888, #809, #866, #742, #741)
  • Android fixes wss:// WebSockets, follows display density changes while the app runs, keeps its runtime classes in minified embedding apps, strips release libraries, and improves keyboard and input handling. (#530, #431, #639, #538, #714, #715, #717, #836)
  • macOS draws fades without redrawing their views, taking one app's memory footprint from 92 MB to 27 MB, and adds trusted web view input with sendInput and web view downloads. Linux draws images at device pixels on fractional scales, Windows speeds up plots and gives the wheel to native components, and GPUI delivers files dragged in from other applications and adds see-through windows. (#877, #895, #743, #758, #834, #879, #712, #591, #876)
  • The web applies state styles, platform props and scroll views' contentStyle and contentClass, and passes every event payload field to the app. Selections stay intact across virtual list rows on every platform, arrow keys follow right-to-left layouts, and parts style with aria-expanded: and data-* attributes. nsh test compares values as Jest does. (#865, #885, #883, #892, #703, #797, #556, #815, #711)

Migrating from 0.3.x

0.4.0 gives each kind of API one shape: styles in CSS's properties, one unit per quantity, one object per event, objects as return values, on(event, listener) for listeners, and one way to report failures. The old forms are removed. (#495, #524, #533, #558, #593, #612, #651)

Move styling props into style and use CSS's shadows. Classes reach all of them too:

- <View style={{ shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4 }} />
+ <View style={{ boxShadow: "0 2px 4px rgb(0 0 0 / 0.2)" }} />
- <Text style={{ textShadowColor: "#000", textShadowOffset: { width: 0, height: 1 }, textShadowRadius: 2 }}>…</Text>
+ <Text style={{ textShadow: "0 1px 2px #000" }}>…</Text>
- <Text numberOfLines={2} ellipsizeMode="middle" selectable>…</Text>
+ <Text style={{ numberOfLines: 2, textOverflow: "ellipsis-middle", userSelect: "text" }}>…</Text>
- <View pointerEvents="box-none" />
+ <View style={{ pointerEvents: "box-none" }} />
- <Image resizeMode="contain" />
+ <Image style={{ objectFit: "contain" }} />

Every event handler takes one object, and mouse events carry the DOM's names:

- <Checkbox onChange={(checked) => …} />
+ <Checkbox onChange={({ checked }) => …} />
- <Input onChange={(text) => …} />
+ <Input onChange={({ value }) => …} />
- <Popover onOpenChange={(open, reason) => …} />
+ <Popover onOpenChange={({ open, reason }) => …} />
- onLayout={(bounds) => bounds.size.width}
+ onLayout={({ bounds }) => bounds.size.width}
- onScrollEnd={(offset) => …}                 // and the drag and momentum events
+ onScrollEnd={({ offset }) => …}
- onLoad={(size) => …}                        // Image
+ onLoad={({ size }) => …}
- onContentSizeChange={(size) => …}
+ onContentSizeChange={({ size }) => …}
- e.translationX, e.translationY, e.velocityX, e.velocityY, e.valueX, e.valueY    // PanEvent
+ e.translation.x, e.translation.y, e.velocity.x, e.velocity.y, e.value?.x, e.value?.y
- keymap.bind("mod+k", (keystroke) => …)
+ keymap.bind("mod+k", ({ keystroke }) => …)
- onHoverIn={…} onHoverOut={…} onHoverMove={(e: HoverEvent) => …}
+ onMouseEnter={…} onMouseLeave={…} onMouseMove={(e: MouseEvent) => …}

Functions return objects, options come as one object, and each element has a ref type of its own (ViewRef, InputRef, ScrollRef, ListRef, WebViewRef, VideoRef, NativeComponentRef):

- const finished = await value.timing(1);     // and spring, decay, keyframes, play, start
+ const { finished } = await value.timing(1);
- const last = await value.stop();
+ const { value: last } = await value.stop();
- const id = await AlertDialog.show({ actions: [{ label: "OK" }] });
+ const { action } = await AlertDialog.show({ actions: [{ id: "ok", label: "OK" }] });
- const opened = await openUrl(url);
+ const { opened } = await openUrl(url);
- const count = await editor.replaceAll(text);
+ const { count } = await editor.replaceAll(text);
- if (await Image.prefetch(src)) …
+ await Image.prefetch(src);                  // rejects on failure
- Window.setTitleBar("hidden", 40);
+ Window.setTitleBar({ style: "hidden", height: 40 });
- net.connect(host, port);
+ net.connect({ host, port });
- let node: YNode;
+ let node: ViewRef;
- BackHandler.onBackPress(listener);
+ BackHandler.on("press", listener);
- const sub = host.on(name, listener); sub.remove();
+ const stop = host.on(name, listener); stop();

Update these units and names:

- gestureState.vx                             // points per millisecond, also vy
+ gestureState.vx                             // points per second, also vy
- keyboardFrame().duration                    // seconds
+ keyboardFrame().duration                    // milliseconds
- viewAreaCoveragePercentThreshold: 50, itemVisiblePercentThreshold: 50
+ viewAreaCoverageThreshold: 0.5, itemVisibleThreshold: 0.5
- ImageSize
+ PixelSize

Use the websocket module's own connect, which ends reads cleanly:

- import { WebSocket } from "websocket"; const c = await WebSocket.connect(url);
+ import { connect } from "websocket"; const c = await connect(url);
- await c.read()                              // rejected on a clean close
+ await c.read()                              // null after a clean close

Also update these APIs:

  • Handle failures by rejection and code: every function returning a promise rejects, a denied capability is an Error with code: "ERR_ACCESS_DENIED", permission and resource (fetch keeps a TypeError with the same fields), and platforms and quotas report ERR_FEATURE_UNAVAILABLE_ON_PLATFORM and ERR_NSH_QUOTA_EXCEEDED. Clipboard and Picker reject where they are not granted, and new WebSocket to a URL not granted fires error, then close (1006). (#651)
  • Read signal beside code in process.run's output; code is null when a signal ended the child. (#651)
  • Handle Clipboard.readImage rejecting over 48 MB and on Android, and Clipboard.writeImage rejecting on Android. (#701)
  • Add flexDirection: "column" to a view that sets display: "flex" and stacks its children: that display lays them out in a row, as in CSS. (#495)
  • Use start, end, marginStart, paddingEnd and their relatives for sides that follow the direction: left, right, paddingLeft, textAlign: "left" and their relatives keep their side right to left, as in a browser. (#495)
  • Give Window frames the window's top-left corner, y down from the primary screen's top, on every platform (macOS used the bottom-left corner), and drop anchorTop. (#698)
  • Check platform.desktop or platform.idiom === "desktop" for the desktop idiom: idiom is "desktop" exactly where platform.desktop is true, and platform.catalyst is true under Mac Catalyst in every idiom. (#705)
  • Leave focusable unset on views that are not Tab stops: focusable={true} makes any view one. (#503)
  • Expect arrow keys in Tabs and RadioGroup: only the group's stop is focusable, and Tab's onPress and Radio's onChange also fire when the keys move the focus. (#537)
  • Accept (error, reset) in every function given as ErrorBoundary's fallback: each one is called, whatever parameters it declares. (#685)
  • Keep ios={{…}} and desktop={{…}} to their typed keys: an unknown key fails the type check. (#667)
  • Size elements in a component's slots yourself where they relied on stretching: a slot takes its element's own box, laid out as the component's item. (#606)
  • Set your theme's primary to choose TabBar's selected color, which now follows that token. (#741)
  • Use nsh run --port <port> or --port 0 to run apps side by side: a plain nsh run replaces the NSH session on its port. (#428)
  • Add --abi arm64-v8a,armeabi-v7a,x86_64 to nsh package --target android when you ship x86_64: the default is arm64-v8a and armeabi-v7a, and --run still adds x86_64 on an x86_64 emulator. (#539)
  • On the web, call await player.runModule(entryURL, { appId, capabilities, chunks }) in place of player.run(bytecode, …) on custom pages, and import List, Image, Canvas, WebView and Video from nsh in place of their former module paths. App code runs in the page's realm, so browser APIs follow the browser's CORS, permission and secure-context rules. (#541, #603, #700)
  • In nsh/test, import PickRequest in place of Pick, allow for app.clock being null, write text: before literal text shaped like a selector prefix, handle ERR_NSH_TEST_UNSUPPORTED for open options a --target cannot honor, expect Jest's toBe and toEqual, and pass network: "live" for real sockets and WebSockets. (#684, #708, #710, #711, #719)

In Rust, update these as well:

- text(s).ellipsize_mode(EllipsizeMode::Middle).selectable(true)
+ text(s).text_overflow(EllipsizeMode::Middle).select_text()
- image(src).resize_mode(ResizeMode::Contain)
+ image(src).object_fit(ResizeMode::Contain)
- .on_hover_in(|_: &()| ..) .on_hover_out(|_: &()| ..) .on_hover_move(|e: &HoverEvent| ..)
+ .on_mouse_enter(|e: &MouseEvent| ..) .on_mouse_leave(|e: &MouseEvent| ..) .on_mouse_move(|e: &MouseEvent| ..)
- .action("OK") .cancel("Cancel") .destructive("Delete")
+ .action("ok", "OK") .cancel("cancel", "Cancel") .destructive("delete", "Delete")
- area().y0(0.0)
+ area().y0(AreaY0::from(0.0))
- Scale::Point, Scale::Linear { domain }
+ Scale::Point { reverse: false }, Scale::Linear { domain, reverse: false }
- nsh_core::element::ambient::watch_scroll
+ nsh_core::element::watch_scroll
- nsh-core = "0.3"
+ nsh-core = { version = "0.4", features = ["declarations"] }    # where you read declarations
  • Take Size<DevicePixels> in image().on_load, and set Viewability's view_area_coverage_threshold and item_visible_threshold as Percentage. (#524)
  • Pass line_height a LineHeight, and handle Prefetch's Result<(), String>. (#495, #593)
  • Forward a generic helper's listener to on_press as IntoListener<PressEvent, M>. (#629)
  • Read GridProps' x and y as Prop<Option<Vec<f64>>>, and end full Environment struct literals with ..Default::default(). (#839, #747)
  • Build nsh-platform-web with its browser feature, and drop Bundle::with_harden_bytecode and harden_bytecode. (#603)

The compatibility baseline is still being prepared; 0.4.0 is not a frozen compatibility release.

Downloads

Prebuilt toolchains are available below for macOS Apple Silicon, Linux x86_64 and Windows x86_64, alongside the iOS and Android embedding SDKs. Each archive has a .sha256 checksum file.

Documentation and live demos · Full changelog

All merged pull requests

What's Changed

  • website: Fix links to the merged nsh page and the Chinese anchors by @huacnlee in #498
  • core: Add the listbox and option accessibility roles by @madcodelife in #487
  • macos: Open a context menu with no place where the pointer last was by @madcodelife in #491
  • core: Let a view be a keyboard focus stop without taking presses by @madcodelife in #503
  • cli: Open nsh test --target macos's windows off the main screen by @madcodelife in #492
  • examples: Import InputProps from nsh/base, where #488 moved it by @madcodelife in #502
  • core: Keep a component's bindings on an app's slot live by @madcodelife in #506
  • ios: Apply stack changes that arrive while a modal opens or closes by @Cooooooker in #507
  • web: Let a field in a draggable view take focus when pressed by @Cooooooker in #508
  • base: Add Pie and Arc to nsh/base/plot by @madcodelife in #501
  • base: Add RichEditor, a ProseMirror-style rich text editor by @huacnlee in #494
  • cli: Preserve Wasm import keys when publishing JS snippets by @honsunrise in #500
  • base: Add Bar with band scales, stacks and grouped bars to nsh/base/plot by @madcodelife in #505
  • base: Add Slider, a value or a range picked along a track by @madcodelife in #510
  • js: Keep a component's root condition from making the component again by @huacnlee in #514
  • base: Rename the rich editor's TextStyle extension to RichTextStyle by @huacnlee in #515
  • base: Fix RichEditor's list commands and add a bubble menu by @huacnlee in #516
  • core: Measure a list's rows once the app's drawing of them arrives by @madcodelife in #504
  • core: Let a Rust view's content of a nested component read its context by @madcodelife in #509
  • ios: Let VoiceOver step elements with increment and decrement actions by @madcodelife in #493
  • core: Style with CSS's properties in native forms, typed below TSX by @sunli829 in #495
  • core: Add Interactive::project_focused for a control's parts by @madcodelife in #512
  • core: Add Accessible::accessibility_elements_hidden for Rust elements by @madcodelife in #513
  • core: Let a ref's focus() focus a pressable view by @madcodelife in #489
  • base: Report a popup's open change before it opens or closes by @madcodelife in #511
  • ios: Dismiss the keyboard of an input in a modal by @Cooooooker in #522
  • base: Pick RichEditor's Mac or PC keys by the platform it runs on by @sunli829 in #523
  • core: Give each quantity one unit across the APIs apps use by @sunli829 in #524
  • core: Add Shader, a view whose box a WGSL shader draws by @huacnlee in #517
  • gpui: Hold text typed ahead until the engine handles the key before it by @mwatts in #399
  • web: Let a nested text's link take its press by @Cooooooker in #526
  • cli: Build an app's development web runtime incrementally by @dXnSix in #429
  • cli: Replace the previous development session on its port by @dXnSix in #428
  • core: Repeat repeating gradients on macOS, iOS and Android by @madcodelife in #531
  • core: Keep a JS-drawn row in its own cell when rows before it go by @madcodelife in #532
  • docs: Check what canvases draw in compat scripts with drawing steps by @madcodelife in #490
  • cli: Package only phones' ABIs into APKs, or the ones --abi names by @mengyinghu-sys in #539
  • web: Keep a link's characters in their run by @Cooooooker in #528
  • gallery: Make the Charts page a showcase built from plot parts by @madcodelife in #525
  • core: Share object field decoders to reduce the web runtime by @huacnlee in #520
  • android: Follow display density changes while the app runs by @dXnSix in #431
  • base: Move between Tabs and RadioGroup items with the arrow keys by @madcodelife in #537
  • docs: Take nsh-base components' features from GPUI Kit, not only names by @madcodelife in #540
  • macos: Fire the app's key bindings for keys nsh test gives its window by @madcodelife in #527
  • web: Run published apps in the browser JavaScript engine by @huacnlee in #541
  • web: Load syntaxes on first use and serve small apps a lite runtime by @BeADre in #542
  • gallery: Draw the checkbox's check and dash with one bold pen by @madcodelife in #544
  • core: Name the mouse events as the DOM does, with one move event by @sunli829 in #533
  • mobile: let a page take the system tab bar away by @Heyana in #441
  • core: Stop the listeners of a region's replaced run from running by @vicanso in #465
  • base: Add NumberInput, a number typed in a field or stepped by @madcodelife in #534
  • base: Give RichEditor Apple's keys in a browser on a Mac by @Cooooooker in #547
  • android: Strip release libnsh.so before it goes into APKs by @mengyinghu-sys in #538
  • base: Add tables to RichEditor, with merged cells, resizing and reordering by @huacnlee in #518
  • base: Keep an input method's composition out of RichEditor's document until it commits by @Cooooooker in #543
  • macos: Let nsh test's steps end before a scroll indicator fades by @madcodelife in #554
  • base: Let Area's y0 read a value of each row, making a band by @madcodelife in #550
  • base: Add OtpInput, a one-time code typed into one native field by @madcodelife in #535
  • base: Keep text typed again after RichEditor's caret moves by @Cooooooker in #555
  • base: Add Select, a choice from a list of items by @madcodelife in #536
  • base: Round Pie and Arc corners and sweep them to new angles by @madcodelife in #551
  • web: Send the mouse and drop events in the browser JavaScript engine by @madcodelife in #560
  • gallery: Rebuild the shell on the approved design, a demo per element by @huacnlee in #545
  • core: Round layout to device pixels instead of whole points by @mengyinghu-sys in #557
  • web: Let embedding pages show dialogs over the whole window by @BeADre in #546
  • core: Style a popup's trigger while it is open with aria-expanded: by @huacnlee in #556
  • base: Add PlotAxis to label a plot's edges with its scale's ticks by @madcodelife in #549
  • web: Launch the browser once before the viewport dialog tests by @huacnlee in #566
  • web: Move the caret past spaces typed at the end of a line by @Cooooooker in #565
  • android: Fix wss:// WebSockets, which had no TLS crypto provider by @jt-wang in #530
  • gallery: Count the RichEditor list test's numbers against the screen before by @madcodelife in #564
  • base: Glide a plot's linear domains with motion.update by @madcodelife in #553
  • base: Show RichEditor's mentions as their text instead of a blank by @Cooooooker in #571
  • core: Pass every event one object by @sunli829 in #558
  • base: Move a plot's hover with the keyboard and screen readers by @madcodelife in #521
  • base: Type the text parts of Select and OtpInput with text styles by @madcodelife in #583
  • base: Group RichEditor's undo steps by time on the web by @Cooooooker in #568
  • base: Keep a paste over the whole document under a character limit by @Cooooooker in #569
  • web: Leave keys a shortcut takes out of an input's onKeyPress by @Cooooooker in #567
  • cli: Paste in nsh test --target web by @Cooooooker in #573
  • testing: Read a span's style with app.spanStyle on real hosts by @Cooooooker in #576
  • test: Correct shifted keys and web keyboard events by @Cooooooker in #572
  • examples: Keep the demos' panels their content's height in a narrow window by @madcodelife in #586
  • base: Add Combobox, one value or several from a searchable list by @madcodelife in #585
  • web: Pass a video's onLoad its size in the browser JavaScript engine by @BeADre in #584
  • base: Move a Pie or Arc on from where it is drawn when changed again by @madcodelife in #594
  • base: Label a Pie's slices outside the ring with leader lines by @madcodelife in #595
  • base: Add reverse to the plot's scales, for bars from the top or right by @madcodelife in #587
  • base: Scroll Editor rows into the views already made by @huacnlee in #604
  • core: Return objects, and give each element a ref type of its own by @sunli829 in #593
  • base: Add Scrollbar for scroll bars in the app's own look by @huacnlee in #579
  • base: Color plot marks by category with an ordinal colorScale by @madcodelife in #592
  • base: Add DatePicker, Calendar and TimeField, as gpui-base's by @madcodelife in #598
  • core: Fix text selection seams and a panic on disposed held props by @huacnlee in #615
  • docs: Give the Chinese Scrollbar page the #built-in-bars anchor by @madcodelife in #616
  • desktop: Transition a text's color with transition-colors by @madcodelife in #602
  • core: Lay a slot's content out in its component's layout by @madcodelife in #606
  • core: Add group, region, tree roles, a pressed state and aria-pressed: by @madcodelife in #609
  • testing: Never let a looping animation hold a test's steps by @madcodelife in #613
  • base: Add ColorPicker with an area, channel sliders, fields and swatches by @huacnlee in #577
  • ci: Pass clippy 1.99 on the Android and Windows hosts by @jt-wang in #529
  • mobile: Transition a text's color on iOS, Android and the web by @madcodelife in #608
  • core: Keep frames that stay on their pixels from being sent again by @mengyinghu-sys in #617
  • gallery: Make the sidebar's demos one stop of Tab, moved with Up and Down by @madcodelife in #605
  • base: Add RadialLine and RadialGrid marks for radar charts by @madcodelife in #596
  • gallery: Animate the controls' state changes, in richer demos by @madcodelife in #611
  • web: Pass input autofill hints to the browser by @burdygin in #570
  • core: Find derived animated nodes by a hash of what made them by @dXnSix in #588
  • core: Evaluate each animation node once a frame by @dXnSix in #589
  • core: Find an interpolation's segment by bisection by @dXnSix in #590
  • gallery: Make the popups' demos real scenarios, with motion by @madcodelife in #614
  • core: Make every listener on(event, l) returning a stop function by @sunli829 in #612
  • base: Add PlotLabel to draw texts at data points by @madcodelife in #599
  • web: Keep calls into the runtime fast however many values an app holds by @huacnlee in #636
  • core: Skip the frames of a subtree that moved by whole pixels by @huacnlee in #637
  • base: Keep the Editor's search compiled while typing, and caret moves cheap by @huacnlee in #638
  • website: Add App Stories at /apps from the nsh-showcases catalog by @huacnlee in #618
  • android: Keep the runtime's classes when an embedding app is minified by @mengyinghu-sys in #639
  • base: Move a huge DataTable as far as the finger on touch screens by @mengyinghu-sys in #628
  • android: Lay text out at the pixel width layout gave it, within float noise by @mengyinghu-sys in #620
  • base: Let popups leave with the app's exit motion, and say where they went by @madcodelife in #631
  • base: Move DataTable's window row by row as it scrolls by @mengyinghu-sys in #622
  • core: Give Rust apps file dialogs, system paths, window and menu APIs by @huacnlee in #629
  • web: Run every web app on the browser's own JavaScript engine by @huacnlee in #603
  • gallery: Make the Button and Input demos real scenarios, with motion by @madcodelife in #635
  • gallery: List components alphabetically, after the primitives by @madcodelife in #600
  • gallery: Make the pickers' demos real scenarios, with motion by @madcodelife in #633
  • text: Measure long unbroken runs in multiline inputs in linear time by @sunfuze in #627
  • web: Draw canvases and animated values again only when they change by @huacnlee in #640
  • base: Add Toggle and ToggleGroup, buttons that stay pressed by @madcodelife in #621
  • base: Draw DataTable's scroll bar above its rows by @huacnlee in #646
  • web: Show dialogs over the page at the app's scale, see-through in dark by @huacnlee in #643
  • web: Run a listener whose owner is gone, as QuickJS does by @huacnlee in #644
  • core: Run a component's method called before its view is made, after by @huacnlee in #645
  • examples: Draw the demos' check marks as SVG, not a "✓" glyph by @huacnlee in #647
  • base: Hide RichEditor's placeholder where an input method composes by @BeADre in #642
  • website: Show the Dialog demo's page sheet as tall as its content by @huacnlee in #650
  • base: Add the Sankey mark to nsh/base/plot by @madcodelife in #601
  • base: Pick a date in one tap on phones, and span the sheet with the calendar by @madcodelife in #653
  • gallery: Reach the sidebar's demos from its search in the test by @madcodelife in #641
  • base: Let touches and the wheel on the Editor's text reach its scroll views by @huacnlee in #652
  • docs: Link the Chinese Toggle page's keyboard note to its own section by @sunli829 in #655
  • base: Reveal a Collapsible as it opens and closes, and add Accordion by @madcodelife in #610
  • base: Name a rich editor's document JSON getJSON, as Tiptap does by @madcodelife in #658
  • base: Keep the input controls' typing and keys cheap by @madcodelife in #656
  • web: Show the keyboard focus only as the app draws it by @huacnlee in #661
  • web: Give the calendars local time's offset so date pickers open by @huacnlee in #660
  • base: Keep a plot's hover and a slider's thumb under the pointer by @madcodelife in #654
  • base: Take no presses anywhere in a hidden Scrollbar by @madcodelife in #657
  • base: Say a disabled ColorPicker's parts are disabled by @madcodelife in #659
  • base: Add Link, which opens its href or lets the app route it by @madcodelife in #624
  • docs: Let the unions apps read gain members, as apps handle the rest by @sunli829 in #664
  • website: Give the storage test the player's helpers its imports name by @sunli829 in #668
  • website: Darken the light theme's borders so cards and controls read by @huacnlee in #673
  • web: Read local time's offset only when the calendars ask by @huacnlee in #663
  • website: Draw the home hero as a galaxy whose stars presses steer by @huacnlee in #665
  • website: State the documentation's license in the footer by @huacnlee in #666
  • gallery: Make the gallery's and the website's controls flat by @huacnlee in #648
  • core: Animate a reveal's layout only where it moves frames by @madcodelife in #669
  • base: Remember finished plot appears in a PlotAppearScope by @madcodelife in #607
  • base: Turn a calendar's months without making its days again by @madcodelife in #672
  • gallery: Sweep the indeterminate bar natively, report tab spans cheaply by @madcodelife in #670
  • core: Report every failure of what JS calls one way, with a stable code by @sunli829 in #651
  • js: Type the props apps put in ios={{…}} and desktop={{…}} by @sunli829 in #667
  • core: Load images and SVGs through one bounded pipeline; add svg() by @huacnlee in #674
  • ios: Let test steps settle while a focused input's caret blinks by @dXnSix in #677
  • android: Recolor texts without boxing their color by @madcodelife in #671
  • core: Let a Rust component's element take a test id, as TSX's testID by @sureshkrishnan-ai in #561
  • rich_editor: Show compositions in the line, with steady candidates by @huacnlee in #686
  • base: Place a tab list's indicator natively on the selected tab by @madcodelife in #687
  • base: Let menu items be checked, with the app's mark before them by @madcodelife in #688
  • web: Let a test's manual clock move without freezing the page by @sunli829 in #689
  • android: Send test touches to the shown dialog's window by @dXnSix in #675
  • ios: Type each key in tests once the one before it landed by @dXnSix in #676
  • js: Make the generated declarations type-check with skipLibCheck off by @sunli829 in #684
  • js: Call a function ErrorBoundary fallback whatever parameters it declares by @sunli829 in #685
  • base: Add Pagination, pages to move between with no look of its own by @madcodelife in #690
  • docs: Show website demos at their real size, as tall as their content by @huacnlee in #691
  • linux: Keep text selections across a virtual list's rows by @huacnlee in #692
  • core: Give a Rust-filled slot's listeners to the element it holds by @madcodelife in #696
  • desktop: Place a window by its top-left corner on macOS too by @sunli829 in #698
  • base: Add Toolbar, one stop of Tab whose arrows move between its items by @madcodelife in #702
  • web: Stop serving the old alias modules, and name nsh's modules when one is missing by @sunli829 in #700
  • runtime: Promise clipboard images only as URLs, and reject what cannot be read by @sunli829 in #701
  • runtime: Export websocket's connect, and end reads cleanly with the close code by @sunli829 in #707
  • ios: Scroll in tests with a quicker finger by @dXnSix in #678
  • android: Build again: pass over the design tokens command by @dXnSix in #681
  • macos: Say text and canvases draw into content layers, not drawRect: by @ohdarling in #694
  • engine: Read the memory footprint with task_info, public on iOS by @ohdarling in #695
  • selection: Keep selections across virtual list rows on every platform by @huacnlee in #703
  • docs: Link the Chinese toolbar page to its design guide section by @sunli829 in #720
  • avatar: Add Avatar and AvatarGroup, a picture with its fallback by @madcodelife in #706
  • testing: Refuse a selector prefix nsh/test does not know by @sunli829 in #708
  • core: Give "desktop" one meaning, with platform.desktop by @sunli829 in #705
  • testing: Keep a test's sockets and WebSockets off the network by @sunli829 in #719
  • testing: Let waits move a manual clock, refuse what a --target cannot honor, and give clock null by @sunli829 in #710
  • testing: Make expect compare values as Jest does by @sunli829 in #711
  • android: Read a selection's rectangles six numbers at a time, as clippy asks by @dXnSix in #727
  • android: Set a view's camera distance only when its perspective moves by @mengyinghu-sys in #721
  • gpui: Show a canvas's latest frame when it changes on every frame by @dXnSix in #679
  • web: Keep a component in an expression child when what it reads changes by @Cooooooker in #737
  • cli: Find web.host when the entry is named without a directory by @Cooooooker in #736
  • android: Hide the keyboard on a tap in a scroll view when the field is outside it by @jt-wang in #717
  • list: Stop making rows again when items are added before them by @Cooooooker in #740
  • android: Slide stack screens by whole pixels so their shadows stay cached by @mengyinghu-sys in #732
  • macos: Cast a canvas's shadows the way their offsets point by @dXnSix in #680
  • editor: Add gutter and overview ruler marks to decorations by @huacnlee in #693
  • core: Add ShaderEffect, ShaderTransition, ScreenEffect and ScreenTransition on Shader by @dXnSix in #682
  • text_view: Use the automatic underline position for links by @lurenjia534 in #713
  • android: Keep an input's text field the size of its content box when the input shrinks by @jt-wang in #715
  • android: Let a scroll view inside a pannable view scroll the drags it can take by @jt-wang in #716
  • android: A readonly input keeps its selection by @jt-wang in #718
  • android: Hide the keyboard on Keyboard.dismiss() after the field that showed it is gone by @jt-wang in #714
  • core: Keep a text's rounded width from falling under its measured width by @LeeDashan in #724
  • windows: Give the wheel to native components that want it by @WillisXue in #712
  • popover: Keep a popover's backdrop under its popup on touch screens by @daydoing in #729
  • android: Drop a node's events once its view is destroyed by @mengyinghu-sys in #738
  • macos: Hide the hidden title bar's toolbar in full screen by @wheregmis in #755
  • mobile: Color the TabBar's selected tab with the theme's primary by @jt-wang in #741
  • mobile: Draw system alerts and action sheets in the app's theme colors by @jt-wang in #742
  • js: Show a component given as a tooltip in development bundles by @WillisXue in #739
  • web: Stop calling an engine that trapped, so its panic stays on screen by @daydoing in #735
  • testing: Settle steps on a manual clock without waiting for quiet by @LeeDashan in #723
  • gallery: Keep the charts' labels clear of each other and the grid by @madcodelife in #704
  • carousel: Add Carousel, slides on a native scroll view that can loop by @madcodelife in #722
  • diff_view: Report the scroll position and where each change is by @Mr-1311 in #730
  • android: Stamp the test agent's drag events with the time they are due by @dXnSix in #753
  • core: Preserve diagonal resize cursor directions by @WillisXue in #749
  • tree: Add Tree, nested items in a virtual list with one Tab stop by @madcodelife in #725
  • windows: Apply cursor changes while the mouse is still by @WillisXue in #751
  • docs: Fix the Tree page's Chinese anchor and list Tree after Tooltip by @huacnlee in #772
  • hover_card: Add HoverCard, a card that opens while the pointer rests by @madcodelife in #697
  • testing: Start scrolling a view into view where nothing takes the drag by @dXnSix in #752
  • gallery: Type-check the Carousel and HoverCard tests with a nullable clock by @madcodelife in #778
  • hover_card: Keep the card out of toolbars and accept endless delays by @madcodelife in #780
  • tooltip: Accept a delay of Infinity instead of panicking by @madcodelife in #781
  • web: Hide a field that shows nothing while an input method composes in it by @Cooooooker in #776
  • gallery: Page with Previous and Next on phones, numbered pages on desktops by @huacnlee in #792
  • macos: Draw a border that turns visible after it was transparent by @madcodelife in #783
  • command: Add Command, a palette whose items filter natively by @madcodelife in #785
  • carousel: Settle slides in tree order, ignore stale rests, keep focus by @madcodelife in #790
  • toolbar: Keep the demo's overflow menu in reach and test arrow order by @madcodelife in #789
  • tree: Act on the row that shows for keys, focus and screen readers by @madcodelife in #786
  • core: Re-sort order watches only for moves among their elements by @madcodelife in #788
  • menu: Open a menu bar's menus on hover and keep popups clear of their trigger by @huacnlee in #791
  • avatar: Give each src an image of its own and accept endless delays by @madcodelife in #782
  • text: Keep color emoji upright in italic text by @Cooooooker in #779
  • core: Retire Rust component views with their creating run by @yuezk in #762
  • data_table: Let Rust apps make a DataTable's rows by @sureshkrishnan-ai in #562
  • test: Run the compat checks again, the scripts caught up with main by @madcodelife in #662
  • test: Check every Sankey prop and the plot scope's memory of appears by @madcodelife in #796
  • roving: Swap the arrow keys in right-to-left layouts by @madcodelife in #797
  • gallery: Open the menu bar's hover tests in a desktop-wide page by @madcodelife in #795
  • core: Add after_answer, run once the app has answered a component's events by @madcodelife in #798
  • core: Make focus-within follow any focus, popups included by @madcodelife in #801
  • slider: Show the app's answer to a drag's last move when the drag ends by @madcodelife in #799
  • web: Measure emoji as wide as the page draws them on high-density screens by @Cooooooker in #775
  • core: Let a Button or other component in a slot take the slot's presses by @Cooooooker in #777
  • ios: Size header items with Auto Layout, and add leading and SF Symbol bar buttons by @huacnlee in #809
  • tree: Report onAction on Return over a leaf and on a double press by @madcodelife in #805
  • web: Measure figures with the kerning of their character pairs by @Cooooooker in #800
  • combobox: Type into the trigger and open lists the trigger's width by @huacnlee in #793
  • docs: Update README to simplify installation instructions by @huacnlee in #811
  • text: Grow a line for an inline view taller than the line height on Apple by @huacnlee in #810
  • hover_card: Stay open while the keyboard focus is in the trigger or card by @madcodelife in #802
  • testing: Run a desktop platform's apps as desktops on --target web by @madcodelife in #808
  • core: Tell apps whether they are in use by @dXnSix in #747
  • text: Center inline chips on the text and keep line spacing even on Apple by @huacnlee in #816
  • cli: Let an app's Rust crate register native components in its hosts by @WillisXue in #683
  • data_table: Retarget the scroll anchor when a drag ends at the top by @akongG in #814
  • linux: Place inline attachments on device pixels and align them to the text by @huacnlee in #812
  • compat: Catch the Combobox steps up with typing into the trigger by @madcodelife in #818
  • engine: Let fetch read the files the app ships by @dXnSix in #746
  • text: Read WOFF and WOFF2 fonts by @dXnSix in #745
  • text: Fall back along fontFamily lists by @dXnSix in #773
  • core: Place popups against a TSX trigger's own box, as a Rust one's by @madcodelife in #807
  • core: Style parts by data-* attributes components give them by @madcodelife in #815
  • engine: Give the asset tests' engines the speech field they now need by @dXnSix in #822
  • select: Draw list, segment and calendar items once, their states as classes by @madcodelife in #817
  • macos: Save web view downloads to the app's data directory by @wheregmis in #758
  • text: Parse fontFamily lists with font_families on every platform by @huacnlee in #820
  • macos: Press pressable views from AXPress, as Return does by @0xbentang in #794
  • core: Make three tests' path fixtures pass on Windows by @WillisXue in #787
  • plot: Add per-row Bar bases and the Rule mark by @madcodelife in #819
  • testing: Keep scrolling into view after a gesture that moves nothing by @dXnSix in #754
  • carousel: Spring back a swipe the app refuses, and count focus in the slides by @madcodelife in #824
  • select: Name the search field for screen readers with searchAccessibilityLabel by @madcodelife in #825
  • command: Name the search field apart from the list with searchAccessibilityLabel by @madcodelife in #826
  • macos: Report the hidden title bar's height right after a resize by @madcodelife in #830
  • text: Refuse WOFF fonts whose tables declare more than a font may hold by @dXnSix in #823
  • linux: Draw images at device pixels on fractional scales by @huacnlee in #834
  • plot: Add the Dot mark for scatter and bubble charts by @madcodelife in #829
  • compat: Find the Command row by its label now that parked cells are skipped by @madcodelife in #832
  • ios: Keep focused inputs above the keyboard and move with it by @huacnlee in #821
  • otp_input: Mark filled cells with data-filled by @madcodelife in #837
  • button: Add hitSlop, so a small button takes presses near it by @madcodelife in #838
  • diff_view: Place file headers below wrapped rows by @huacnlee in #827
  • android: Hide the keyboard with a page the app pops or a dialog it closes by @huacnlee in #836
  • web: Draw underlines unbroken and clear of the text by @huacnlee in #850
  • examples: Open the gallery at one demo and section with cargo run -- by @huacnlee in #851
  • bundler: Warn about an ungranted host only when the app calls it by @huacnlee in #852
  • plot: Add a second y scale for dual-axis charts by @madcodelife in #839
  • gallery: Hover the risk-return plot again after changing its period by @madcodelife in #840
  • link: Add hitSlop, so a small link takes presses near it by @madcodelife in #845
  • switch: Add hitSlop, so a small switch takes presses near it by @madcodelife in #846
  • checkbox: Add hitSlop, so a small checkbox takes presses near it by @madcodelife in #847
  • radio: Add hitSlop, so a small radio takes presses near it by @madcodelife in #848
  • toggle: Add hitSlop, so a small toggle takes presses near it by @madcodelife in #849
  • compat: Go back to the gallery's index between its demos by @madcodelife in #854
  • color_picker: Mirror the area and horizontal sliders right to left, keys too by @madcodelife in #855
  • tree: Swap Left and Right in a right-to-left layout by @madcodelife in #856
  • docs: Keep the developer docs to what contributors work with by @huacnlee in #859
  • web: Load the runtime's parts on demand, split into side modules by @huacnlee in #853
  • website: Publish every release's documentation beside main's by @huacnlee in #862
  • plot: Add the Cell mark for heatmaps by @madcodelife in #861
  • macos: Let web views take trusted input with sendInput by @wheregmis in #743
  • desktop: Show the pointer over links in selectable text by @huacnlee in #842
  • desktop: Keep a nested scroll view's bar in the visible part by @huacnlee in #858
  • linux: Hide the placeholder and caret while an input method composes by @huacnlee in #844
  • js: Keep list separators alive across data updates by @huacnlee in #828
  • web: Let the size check and clippy pass with the runtime's side modules by @huacnlee in #863
  • plot: Add the Treemap mark for market maps and allocations by @madcodelife in #867
  • gallery: Keep the drawdown's low label clear of its line by @madcodelife in #868
  • ios: Draw backdrop blur behind a view's background, gradient and shadows by @twosugar in #866
  • diff_view: Give wide characters two columns and line numbers their full width by @huacnlee in #864
  • linux: Keep window controls centered in custom title bars by @yuezk in #857
  • macos: Reduce app memory by not redrawing views on fades by @huacnlee in #877
  • windows: Fix plot performance on Windows. by @huacnlee in #879
  • web: Package apps whose Rust crates call JS through wasm-bindgen by @LVtomatoJ in #870
  • web: Apply state styles and accept page header styles by @chaxus in #865
  • ios: Launch on iOS 15 to 17 by defining isEditable on the text view by @madcodelife in #888
  • text_view: Typeset formulas when the app gives no renderMath by @huacnlee in #841
  • gallery: Make the editor demo's hover card a slim editor-style panel by @huacnlee in #878
  • core: Let pages and dialogs come and go through a shader by @dXnSix in #763
  • gallery: Show shader effects and transitions on the Shaders page by @dXnSix in #771
  • macos: Draw shader effects, transitions and page shaders by @dXnSix in #764
  • ios: Draw shader effects, transitions and page shaders by @dXnSix in #765
  • android: Draw shader effects, transitions and page shaders by @dXnSix in #766
  • web: Draw shader effects, transitions and page shaders by @dXnSix in #767
  • windows: Draw shader effects, transitions and page shaders by @dXnSix in #768
  • web: Apply the web's and the desktop's platform props by @chaxus in #885
  • linux: Draw shader effects, transitions and page shaders by @dXnSix in #769
  • gpui: Draw only shader fills: effects show their children, transitions change at once by @dXnSix in #770
  • ios: Keep a view's backdrop blur below its background when it clips by @dXnSix in #882
  • input: Hide the placeholder while composing and fit the caret to the text by @huacnlee in #889
  • gpui: Add AppOptions::transparent for see-through windows by @sureshkrishnan-ai in #876
  • web: Show an app's own component given as a tooltip under nsh run by @chaxus in #887
  • gpui: Stop blinking the caret in hidden windows and after ten seconds on Linux by @sureshkrishnan-ai in #875
  • gpui: Resize the window on Window.setFrame by @sureshkrishnan-ai in #872
  • cli: Let nsh publish sign a package again with another release key by @ohdarling in #886
  • gpui: Deliver files dragged in from other applications by @UncertaintyDeterminesYou4ndMe in #591
  • web: Apply a scroll view's contentStyle and contentClass by @chaxus in #883
  • macos: Stop checking every out-of-sight view on each frame by @madcodelife in #895
  • input: Apply lineHeight to multiline text on Linux by @huacnlee in #891
  • web: Pass every event payload field to the app by @madcodelife in #892
  • core: Give text inside an inline view the color of the text around it by @madcodelife in #894
  • text: Grow a line for an inline view taller than the line height everywhere by @madcodelife in #893
  • release: Prepare version 0.4.0 by @huacnlee in #884

New Contributors

Full Changelog: v0.3.0...v0.4.0