Public Access
8e61fc72b4a75a3254e52963439642a23de4c358
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dec2cf164a |
Let the connecting card's host name and address be taken away
The card that fills the terminal's rectangle while a connection is being made prints two lines saying which machine and at what address, and both were TextBlocks — which in Avalonia is a drawing of a string rather than a string. It is the one place the shell says what it is dialling *before* anything has answered, and on a refusal it is the only copy of that on screen: there is no prompt yet and there may never be one. That is exactly when the value is wanted somewhere else — in a ticket, in a message to whoever owns the host, in the ssh command being written in the window beside this one — and retyping it off the screen is how a digit goes missing from an address that is mostly digits. SelectableTextBlock, the same change the session column's own address got in "Let the session address be selected, and copied whole" and for the same reason. Nothing else about either line moves: same font, size, weight, colour, trimming and centring. The refusal underneath them was already selectable, which made the pair above it the odd ones out. Both lines rather than only the name. They are two lines of one identifier, and a pair where the first can be taken and the second cannot is a worse answer than either. Tooltips are new, and they are what makes the trimming safe rather than a trap. A drag reaches only the glyphs that were drawn, so a name or an address wearing an ellipsis has to be taken with select-all — which takes the whole value, because the ellipsis is a drawing and not a shorter string. The hover says what the whole value is, so nobody copies half of one without having been told there is a half. Same argument, same shape, as the sidebar's. The test asserts that half rather than the easy one, on values long enough to be trimmed inside the card's 460 pixels; values that fit would pass whether the trimming were render-only or not. Confirmed it bites by putting the two TextBlocks back and watching it fail. The phone draws the same two lines in the same state — TerminalScreen.axaml's IsConnectingShowing block — and is deliberately left alone. Its captions are styled by TextBlock.title and TextBlock.detail selectors in Phone.axaml, so the same substitution there silently drops the styling until those rules gain SelectableTextBlock siblings, which is a theme change rather than this one. Worth doing; not worth smuggling in here. Also: WindowDecorations on the splash instead of SystemDecorations, which Avalonia 12 has deprecated in favour of it. That warning was mine, added two commits ago; the main window's own is older and is not this change's to move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b8c94ecb73 |
Put a window on screen while it starts, and refuse the second one
Two things a desktop application is expected to do and this one did not. ◆ THREE SECONDS OF NOTHING. Measured on a debug build against a cold profile: the main window arrives 3.1 seconds after the executable does. Nothing was on screen for any of it — the icon had been double-clicked and the desktop looked unchanged. The main window cannot come sooner. It needs a view model, and the view model needs composition to have opened the SQLite cache, bound the terminal's loopback socket, asked the platform about a TPM and read the install layout off disk. Its own IsStarting card already narrates what happens after it appears; this is the part before, which no arrangement of that window can cover. So SplashWindow, and it is measurably the point: 668 ms to the splash, 3.1 s to the window that replaces it. Two mechanics are load-bearing rather than incidental. The splash *is* desktop.MainWindow for a moment. The lifetime shows whatever that property holds once OnFrameworkInitializationCompleted returns; a window merely Show()n there would have no lifetime attached, and under ShutdownMode.OnLastWindowClose the first close of anything would end the process. Handing the property over is one assignment and Avalonia reads it lazily. And composition is posted at DispatcherPriority.Background rather than called. It is synchronous and slow — which is the entire argument for a splash — so running it inline would block the very dispatcher turn that has to paint the thing. The window is SizeToContent="Height", which is about the failure and not the splash: CouldNotStart carries an exception's own message, and inside a fixed height a long one would wrap and be clipped at exactly the moment that text is all the window is for. A composition that throws is caught rather than left to the dispatcher, for the same reason — before this there was nothing on screen to abandon, and now there is, so an escaping exception would leave a window saying "Starting…" over a process that had stopped. Escape closes it, and only then. ◆ AND ONE INSTANCE. Claimed in Main before Avalonia is configured, so a second launch starts nothing at all: no toolkit, no window, no splash flashing up before it thinks better of it. Exclusion is an exclusive handle on instance.lock inside the profile directory, and that location settles two questions at once. A nightly and a release build do not exclude each other — separate profiles, ADR 0013 — and two people signed in to one machine each get their own instance. A global mutex would have had to re-derive both from somewhere. A file handle rather than a named mutex because the kernel releases it however the process ends. There is no such thing as a stale lock here, which is a state a named mutex makes both platforms model differently and neither model simply. The second launch is not silent. Somebody who double-clicks an icon and sees nothing has been told the application is broken, so it rings a named pipe — named from a digest of the profile path, so it is scoped exactly as the lock is — and the running instance un-minimises and activates. Activate is a request and not a guarantee: Windows refuses the foreground to a process that did not receive the input, and the flashing taskbar button that results is still an answer. Three seconds of retrying when the lock is held by something that does not answer its pipe, and that window has one job. Velopack's apply ends this process and its updater starts the new build; the two are ordered, but a lock held a moment too long would make the successor refuse itself and vanish — an update that looked like an uninstall. The doorbell is disposed before Shutdown for the same reason, so the pipe name is free before the process is. ◆ VERIFIED BY RUNNING IT, WHICH IS THE ONLY WAY EITHER OF THESE COULD BE. Two launches of the built executable: the first stayed up with instance.lock in the profile, the second exited. Window sizes over time are where the two timings above come from — 420x160 then 1196x776. SingleInstanceTests covers what a single process can reach: the lock, its release by disposal, the doorbell answering more than once, and two profiles not excluding each other. SplashCardTests measures the one thing the splash can get wrong, which is a failure message long enough to need the height the window is willing to give it. Checks 1.12 and 16.11 are what needs a second executable and a real screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |