Skip to content

merge: bring upstream main (b63e4a0, delta updates) onto main - #16

Merged
xiaoyumuxi merged 39 commits into
mainfrom
sync/upstream-b63e4a0
Nov 14, 2026
Merged

xiaoyumuxi merged 39 commits into
mainfrom
sync/upstream-b63e4a0

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 14, 2026 •

Copy link
Copy Markdown

What this changes

Merges upstream vivaui/viva main at b63e4a0 into the fork's main (96a5af5). That brings in 36 upstream commits, including 27429b5, "feat(update): apply signed delta updates to the installed payload". Lithe needs it for delta updates in the desktop client.

The merge commit is 67313e5. Tag v0.6.3 from upstream is pushed to the fork as well.

Conflicts and how they were resolved:

  • components/tooltip.go: keeps the fork's theme fontSize and padX. Wraps with upstream's scratch-buffer canvas.WrapLines(scratch[:0], label, fontSize, gfx.WeightRegular, maxWidth-2*padX, int(tooltip.viewport.Height)), plus upstream's comment about keeping unbroken tokens whole.
  • components/scroll_area.go: takes upstream's HorizontalTrack, which starts at trackX after the frozen band. Keeps the fork's thumb arguments: scrollAreaThumb(geometry.HorizontalTrack, false, horizontalViewport, contentWidth-frozen, offsetX, maximumX, area.barInset(), area.minThumb()).
  • internal/platform/darwin/native.m: setWindowTitle now passes _trafficLightScale to viva_place_traffic_lights. Upstream added that argument, and the fork's title-change re-placement call predates it.
  • internal/platform/darwin/testdata/traffic_lights.m: keeps both the fork's title-change assertions and upstream's scale assertion.
  • cmd/viva/templates/{scroll_area,tooltip,tree}.go.txt: regenerated with go run ./tools/componentgen.

Also merged: the macOS payload install fix (vivaui#269)

fix/update-darwin-payload-name (bf0a714, on top of upstream b63e4a0) is merged into this branch as 0312f3d. It is the same branch as the upstream PR vivaui#269.

ApplyPatch returns a directory named viva-payload-*, and Install copied the payload under that name. stagedBundle accepts only a .app entry, so a delta update on macOS always failed with "the archive holds no application bundle". The copy now takes the running bundle's name. TestInstallStagesAnAppliedPatch fails without the change and passes with it.

When upstream merges vivaui#269, the next sync merges it again with no conflict, since the same commit is already here.

Merge with a merge commit, not a squash

desktop/go.mod in Lithe pins the head of this branch, 0312f3d, through replace github.com/vivaui/viva => github.com/Lithe-IDEA/viva. Merge this PR with a merge commit (or a fast-forward) so that 0312f3d stays on main. A squash would leave the pinned commit reachable only from this branch. Keep sync/upstream-b63e4a0 and fix/update-darwin-payload-name until the merge lands.

How it was verified

  • go build ./...
  • go vet ./...
  • go test ./... -count=1: components, ui, update, internal/platform/darwin, tools/..., and the cmd/viva recipe and feed tests pass.
    • 14 cmd/viva packaging tests fail with "built for macOS 13.0 or later but [mac] min_system says 12.0". A clean upstream checkout at b63e4a0 fails the same way on this machine (Xcode 27 SDK), so the merge did not cause it.
  • a real window: the desktop client is checked against this commit in the Lithe PR.

t8y2 and others added 30 commits November 13, 2026 21:52
The runtime now records the hovered hit's role, and a menu row only draws its current-item tint when the pointer is not over a non-row control inside the menu. This removes Motty's paint-time focus workaround for the tab colour row.
The rule a Box Drawing or Block Element stroke is drawn with was a fixed
5 percent of the font size, rounded down to whole device pixels. At a 13 px
font on a 2x display that is one device pixel and a heavy stroke two, half
of what the font itself draws: opencode's prompt border (U+2503) turned
into a hairline, and the stroke got relatively thinner as the display
scale rose.

The text engines now report the face's underline thickness
(MonoCellMetrics.Underline), the source Ghostty takes its box thickness
from, and a rule is that thickness at the line's scale rounded up, with a
heavy stroke still two rules. Geometry then weighs what the font's own
glyphs weigh at any size and scale; a canvas without an engine keeps the
reference thickness (internal/textmetrics.MonoUnderline).

Refs: t8y2/motty#822
The test reads the thickness DirectWrite reports for a fixed-pitch face,
which only a Windows runner can execute: the hosted job's name filter
must list it or the COM path is compiled and never run.

Refs: t8y2/motty#822
Co-Authored-By: Kimi <noreply@moonshot.ai>
Co-Authored-By: Claude Code <noreply@anthropic.com>
A ui.Table whose explicit column widths overflow the room it was given now
scrolls horizontally, its header riding along with its rows. TableState.Scroll
reports the offsets, geometry and viewports of both axes, the wheel moves the
columns horizontally or with Shift held (as a Sideways ScrollArea takes it),
and TableSpec.HorizontalScrollbar names a bar below the body that drags like
the vertical one. ScrollbarGutter now reserves space for either bar.

The default layouts are unchanged: a table whose last column has no width still
lets that column take whatever is left and never scrolls across, and a table
inside a ScrollArea leaves its width to the area, sideways or not. Bounds are
clamped to MaxX/MaxY on every paint, and a warm wide table still paints with
zero allocations (BenchmarkWideTable and the existing table benchmarks at
0 allocs/op).

Closes the horizontal half of the datagrid gap registered in dbxui: wide grids
can now be panned, and reveal/freeze features have a viewport to target.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Add PathStyle.Dashed and Canvas.DashedRoundedOutline: shared tessellation
splits a flattened contour into a 6:4 dash rhythm along its arc length,
fitting a whole number of periods to a closed outline's perimeter so the
pattern closes where it began. Translucent dashes go through the seam-free
stroke decomposition, and a warm outline allocates nothing.

Co-Authored-By: Claude Code <noreply@anthropic.com>
gfx.PointerCursorGrabbing closes the hand while something is dragged:
closedHandCursor on macOS, grabbing on Wayland and X11 cursor themes, the
CSS grabbing keyword on the web, and a cached 16x16 cursor Win32 has no
stock preset for (destroyed with the last window).

Co-Authored-By: Claude Code <noreply@anthropic.com>
InputSnapshot.Alignment places a field's line inside its content box; the
zero value keeps the direction-dependent start alignment fields had. The
value drives the painted line, caret, selection (including the clip
re-paint), composition and the geometry the platform editor is told, so a
right-aligned number field stays right-aligned while focused. ui.Field and
ui.Page pass the declaration to editors that leave their own unset.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Box.Dashed() and Look.Dashed(bool) take an element's border in and out of
the dash pattern, painted over the fill like a translucent border is;
DrawContext.DashedBorder paints a dashed frame with the element's fade.
The generated guide names all three, and the component templates are
regenerated (a stale color-swatch template from main is caught up with it).

Co-Authored-By: Claude Code <noreply@anthropic.com>
The area a self-scrolling Table declared for its bars took X and Width from
its outer frame but Y and Height from the body after the gutter was cut off
it: the vertical bar landed in the gutter it reserved, while the horizontal
bar's track landed on the deepest visible row instead of the strip below the
body — covering the row's hits and taking the right-click on its text, and
leaving the reserved strip empty.

The bars now own the whole frame below the header, the body plus both
reserved gutters, so each bar lands in its own gutter symmetrically with the
other and the corner between two bars stays clear. A gutter also reserves
the component's viewport beside the bars (ReserveScrollbars), matching the
strip the columns and rows have already given up, so a drag maps the body
it scrolls exactly; without a gutter the bars keep riding the table's edges
over its rows, as before — a table with no bar IDs paints none and keeps
the single whole-grid offset the wheel moves.
SetClipboardHTML(html, alt) puts a rich flavour beside a plain one in a
single write: NSPasteboard public.html beside public.utf8-plain-text,
Windows' registered HTML Format (CF_HTML, header of byte offsets)
beside CF_UNICODETEXT, and the CLIPBOARD selection's text/html target
beside UTF8_STRING. The GTK shell serves the same pair through
gtk_clipboard_set_with_data; an older GTK without it reports ErrUnsupported
without disabling plain text. The browser answers through a Promise, so
the synchronous call returns ErrAsyncRequired and SetClipboardHTMLAsync
writes one ClipboardItem with both MIME flavours.

An empty html writes alt with SetClipboardText's exact shape, so the
degradation is the copy the caller already spelled out. The headless
driver stores the pair beside its private clipboard text and clears it
with every other copy, as a real clipboard does.
Wrap tooltip labels to the available viewport instead of applying the
shared eight-line, ellipsizing overlay budget. Unbroken filenames retain
all their text so Motty can identify truncated files without a local
popup implementation.

Refs: #828
Let a table omit its un-owned vertical hover overlay when the application paints its own vertical bar beside it. Keep scrolling, gutter geometry, the horizontal hover bar, and explicitly identified scrollbar interactions unchanged.
CodeMirror's highlightActiveLine paints the row the caret is on even when
the document is empty, so an empty editor's first line has a row background
exactly like any populated caret line. The placeholder guard around
currentLine suppressed that tint whenever Value was empty, leaving an empty
document's first line the only row in the widget without a background.

Keep Placeholder text styling as-is and stop treating an empty document as
if it had no caret line.

Co-Authored-By: Kimi <noreply@moonshot.cn>
ui.Box.TextEnd() aligned a Text but did nothing to an Input, a TextField
or an InputBase: paintInput never read n.textEnd, so a field declared
with .TextEnd() kept the ordinary start alignment. paintTextarea's rows
follow the textarea's own row layout (TextareaSnapshot has no
Alignment), so it was left alone.

paintInput now carries the element's TextEnd() into the snapshot as
AlignEnd when the snapshot left its alignment at the zero value. The
overwrite happens where the snapshot is styled from the element, in the
same direction as FontSize, Weight, Color and Mono: an explicit snapshot
alignment (AlignCenter, AlignRight, AlignEnd or a AlignStart that came
in as AlignLeft, which components treats as the default) is kept, so a
caller that declared its own alignment is never written over. Through
InputSnapshot's own a7345fa paths, the one value drives the painted line,
the caret, the selection, the composition and the text geometry the hit
declares, which focusedTextField, painter.syncTextInput and the Fields /
Page editor fallback chains hand to the platform editor: a focused
number field stays right-aligned.
AF_UNIX exists only from Windows 10 1803 and Windows Server 2019. On every
Windows below that (Windows 10 1607 and 1709, Windows Server 2016)
net.Listen("unix") failed, the dial that followed failed with the same
address-family error, and Claim reported only "no process is listening": the
platform's reason was dropped, so a system without the address looked like an
application that was not running.

Claim at a per-user named pipe on Windows instead. The pipe namespace is
machine-wide, so the name carries a digest of the user's SID; the pipe rejects
remote clients and its ACL admits only its creator. A pipe also needs no
directory, no permission check and no stale-file clean-up. macOS and Linux keep
the Unix socket unchanged.

Claim now names the listener error next to "nobody listens", so a future failure
reports what the platform refused. Motty's accompanying commit stops the fatal
message from naming a log that a run failing before logging never opened.

A copy from a build before this change claims at the socket, so it meets this
build only after every running copy has exited; the in-app updater restarts that
way. Tests ran on macOS and on the physical Windows 11 machine, including the
two-process hand-off; Claim+Close on Windows costs 23 us and 18 allocations.
Splitting the package into per-platform claim files left instance.go and its
shared test untagged while claimAddress lives only in the darwin/linux and
windows files, so GOOS=js GOARCH=wasm go vet failed with "undefined:
claimAddress".

Tag the package for the platforms that have a claim, as app/singleinstance
already is, and narrow owner_unix.go to darwin and linux so no file is left
for the browser target.
Wrapping the label moved from the overlay's own scratch to
canvas.WrapLines(nil, ...), so every warm tooltip paint allocated the line
slice again: the gallery and component demo allocation checks counted four
allocations per paint.

Pass a stack scratch, as the wrapping API asks callers to. The regenerated
tooltip recipe carries the same change.
The empty-document caret tint changed the CodeEditor component without
regenerating its installation recipe, so the generator checks failed.
t8y2 and others added 9 commits November 14, 2026 13:12
Motty hit-tests a detached SFTP window while a file is dragged outside the source window. Window.Frame already documents finding another window by comparing frames, but ContentWindow hid the window it wraps. This exposes the same content rectangle and minimize state, and a test covers the frame, the minimize flag and a closed window.

Companion to the Motty change that copies files between servers.
A cell glyph's stroke came from the face's underline thickness, doubled for
a heavy line, the source Ghostty takes its box thickness from. An underline
is not the weight of a box drawing line: Cascadia Mono draws its heavy stem
at 4.24 times its underline, so opencode's prompt border (U+2503) at 16 px
and a 175 percent display was 4 device pixels where the font's own glyph is
6. Consolas sits at 2.08, which is why the earlier fix held there.

The text engines now report the ink width of the face's own light and heavy
box drawing glyphs (MonoCellMetrics.Rule and HeavyRule): CoreText's glyph
bounds, DirectWrite's design glyph metrics, and Pango's ink extents with
font fallback off, so only the chosen face's glyph is measured. A cell glyph
takes both weights, rounded to the nearest device pixel and never thinner
than one; a face without the glyph leaves the reference 0.05 em.

Refs: t8y2/motty#833
viva package --format pkgbuild writes PKGBUILD and .SRCINFO for a binary
package that installs the released tar.gz under /opt and links it into
/usr/bin, with its menu entry, icon and MIME types. [linux] arch_depends
names the Arch packages the application needs beyond Vulkan, XCB and Cairo.
The feed may list a patch per asset between an earlier version and this
one. viva feed --patch-from makes it: a gzip tar with patch.json and one
bsdiff per changed file, against the installed .app on macOS or the
executable on Linux. DownloadPatch verifies the bytes the signed feed
declares; ReadPatch/PatchReader.Apply verify every file it writes; ApplyPatch
stages the result; Install replaces the application from the staged payload.
A patch that does not fit falls back to the full archive. Windows has no
patch: the NSIS installer owns the installation.
components.SelectOptions and SelectItem have had a leading icon slot all
along, but ui.Select passed only the labels down, so a page could not
give its options a flag or a colour swatch without painting around the
control. ui.SelectIcon is Select with one further function, icon func(T)
gfx.Icon: the chosen option's icon is painted before the value on the
trigger and every option's before its own label in the open list, exactly
as the component the select has always been painted by does with the
same icons.

A select that declares no icons — Select, or a nil icon function —
paints what it always painted: the icon table is built in the page's
scratch only when a select has one, so warm select frames allocate
nothing, icons or not. Box.Icons is the same plumbing at the Frame's
layer, where a SelectBase keeps the icons for its caller and paints none
of them, as it does the rest of the look.
…ions

Session.Now, Advance, Frame and Transitioning give a test its own clock.
Access, AccessByRole and AccessByName read the last frame's accessibility
tree as app.AccessNode, and Notifications returns what the application sent:
a Driver records notifications instead of showing them.
Brings upstream b63e4a0, which includes signed delta updates (27429b5),
onto the fork's patches.

Conflicts and how they were resolved:
- components/tooltip.go: keep the fork's theme font size and padding, and
  take upstream's wrapping that keeps a long token whole and bounds the
  lines by the viewport height.
- components/scroll_area.go: take upstream's horizontal track that starts
  after the frozen band, and keep the fork's bar inset and minimum thumb.
- internal/platform/darwin/testdata/traffic_lights.m: keep both the fork's
  title-change assertions and upstream's scale assertion.
- internal/platform/darwin/native.m: setWindowTitle passes the traffic
  light scale upstream added to viva_place_traffic_lights.
- cmd/viva/templates: regenerated with tools/componentgen.
…'s name

ApplyPatch rebuilds the bundle in a temporary directory named viva-payload-*, and Install copied the payload into its staging directory under that name. stagedBundle only accepts an entry ending in .app, so installing the documented ApplyPatch result failed with "the archive holds no application bundle". The copy now takes the running bundle's directory name, which the helper moves into place under anyway.
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 09:29
@xiaoyumuxi
xiaoyumuxi merged commit 609dd47 into main Nov 14, 2026
12 of 20 checks passed
xiaoyumuxi added a commit that referenced this pull request Nov 14, 2026
Upstream's wmSetSize and the fork's wmSetTitle (#1) both took 0x8052, so the
window procedure's switch has a duplicate case once both are on main (#16) and
the Windows platform package does not compile. wmSetTitle moves to 0x8053, the
next number no private message uses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants