Repository navigation
merge: bring upstream main (b63e4a0, delta updates) onto main - #16
Merged
Merged
Conversation
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.
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
marked this pull request as ready for review
November 14, 2026 09:29
This was referenced Nov 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Merges upstream
vivaui/vivamainat b63e4a0 into the fork'smain(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.3from upstream is pushed to the fork as well.Conflicts and how they were resolved:
components/tooltip.go: keeps the fork's themefontSizeandpadX. Wraps with upstream's scratch-buffercanvas.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'sHorizontalTrack, which starts attrackXafter 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:setWindowTitlenow passes_trafficLightScaletoviva_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 withgo 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.ApplyPatchreturns a directory namedviva-payload-*, andInstallcopied the payload under that name.stagedBundleaccepts only a.appentry, so a delta update on macOS always failed with "the archive holds no application bundle". The copy now takes the running bundle's name.TestInstallStagesAnAppliedPatchfails 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.modin Lithe pins the head of this branch, 0312f3d, throughreplace github.com/vivaui/viva => github.com/Lithe-IDEA/viva. Merge this PR with a merge commit (or a fast-forward) so that 0312f3d stays onmain. A squash would leave the pinned commit reachable only from this branch. Keepsync/upstream-b63e4a0andfix/update-darwin-payload-nameuntil the merge lands.How it was verified
go build ./...go vet ./...go test ./... -count=1:components,ui,update,internal/platform/darwin,tools/..., and thecmd/vivarecipe and feed tests pass.cmd/vivapackaging 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.