Public Access
A client two versions behind was offered the older of the two, installed it,
restarted, and was told at once that another update was waiting. Two downloads,
two installs and two restarts to move one step.
The feed was not the problem and could not have been. The nightly release is
deleted and remade on every push to main, so it holds exactly one build: a client
can only ever be offered the newest thing there is. The wrong version came from
this application's own state.
◆ THE PASS STOPPED ASKING, WHICH IS NOT WHAT IT WAS MEANT TO STOP DOING.
if (!IsAutomatic || State is UpdateState.Downloading or UpdateState.Ready)
Ready means something is downloaded and waiting. The intent behind excluding it is
right — there is no point fetching what has already been fetched — and what it
actually did was skip the check. So from the first thing any process downloaded,
its banner was pinned to that build for the rest of its life, however many newer
ones were published afterwards. On a rolling channel that is the normal case
rather than a corner.
Not re-fetching is still the rule and now lives in FetchAsync, which compares
versions rather than refusing to look: the same build is left alone, a newer one
replaces what was pending. Which is also why FetchAsync answers with what is
*pending* rather than with what the check returned — a client on 1.0 holding 1.1
is told 1.1 by every later check, because 1.1 is still the newest thing on the
feed, so "the check found something" and "there is something new" are different
questions and only the second one is worth a download.
A build already fetched is never discarded, including when a check answers null.
Null means the feed's newest is no newer than the build that is *running*, which
says nothing about the one on disk — that one is still newer, and withdrawing an
offer somebody has already been shown would be the worse answer.
◆ AND A LAST LOOK BEFORE THE PROCESS IS REPLACED, WHICH IS THE HALF THE TIMER
CANNOT REACH.
The loop asks every six hours. The build that caused this was published minutes
before RESTART NOW was pressed, so no interval anybody would accept would have
caught it. The press now asks once more.
It costs one small request in the ordinary case, because the feed answers with the
build already on disk and nothing is fetched. It costs a download only when there
genuinely is something newer, which is the case where the alternative was two
downloads anyway.
Bounded at ten seconds, and the wait rather than the request: VelopackUpdateChannel
records that Velopack's check takes no token, so cancelling would be observed only
after the call had already returned. Ten seconds bounds a small JSON document
against a network that drops packets rather than refusing them; a refused
connection fails in milliseconds and never reaches it.
Nothing here refuses a restart. An unreachable forge, a look that ran out of time,
a fetch of the newer build that failed halfway — all leave the build on disk
untouched and still newer than the one running, so the restart goes ahead with it.
Hence swallowed rather than reported: Failed is for something a person is waiting
on an answer to, and this person is waiting on a restart they are about to get.
IsRestarting keeps the banner up through it. Everything the strip draws keys off
IsReady and the look moves the state off that, so without it pressing RESTART NOW
made the banner vanish with nothing on screen saying why, for as long as a 120 MB
package takes. It says what it is doing instead, percentage and all.
◆ THE CHANNEL HAD TO LEARN WHICH BUILD IT FETCHED, AS AGAINST WHICH IT LAST FOUND.
Required by both halves above rather than tidied along with them. Matched()
resolved an AvailableUpdate only against `found`, which a check overwrites — and
both changes make this application check while a build is already downloaded. A
check that lands a newer answer and then fails to fetch it, or is given up on
halfway, leaves the shell restarting with the build it already has and Matched()
throwing at it: the offer real, the package on disk real, and the only thing wrong
being that the channel had forgotten which one it was. A slow forge became a
refused restart.
So `fetched` is recorded after a download returns, tried before `found`, and
survives every later check.
Settings → General claimed checks start "a couple of minutes after launch", which
has been untrue since the first pass moved to launch. It now also says the loop
keeps asking while an update is waiting, because that is the behaviour somebody
reading that row would want to know about.
UpdateFlowTests goes from 21 to 28: the pass asks again and fetches once, the
pending build is replaced when the feed moves on, a superseded offer reappears
through a dismissal, the press installs what the last look finds, and the three
ways the last look can fail all still install what was already on disk. Check
16.7a is the manual half, needing two real releases.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
115 lines
6.8 KiB
XML
115 lines
6.8 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
|
x:Class="DodoSSH.Client.App.Views.SettingsGeneralPage"
|
|
x:DataType="vm:MainWindowViewModel">
|
|
|
|
<!--
|
|
v5c: General, against Settings-General.dc.html.
|
|
|
|
The design's page has three cards — STARTUP, APPEARANCE, UPDATES — and this one has one. STARTUP
|
|
(launch at login, reopen tabs) and APPEARANCE (theme, language) are refused outright: nothing tracks
|
|
whether Windows starts this application, nothing remembers a tab list across a launch, only the one dark
|
|
theme exists, and there is no i18n anywhere in the client. Building either card would be two rows of
|
|
controls that toggle a field nothing reads. See design-notes/v5c-fidelity-notes.md.
|
|
|
|
UPDATES is the whole page's real content, moved here from the old PreferencesScreen.axaml verbatim —
|
|
same UpdateViewModel, same states, same warnings, restyled into this page's row idiom. Its own
|
|
update-channel switcher is refused the same way STARTUP and APPEARANCE are: which channel a copy follows
|
|
is fixed when it is built, so this screen cannot offer to switch it.
|
|
-->
|
|
|
|
<ScrollViewer>
|
|
<StackPanel MaxWidth="1100" Margin="40" HorizontalAlignment="Stretch">
|
|
|
|
<TextBlock Classes="settingstitle" Text="General" />
|
|
|
|
<TextBlock Classes="settingssection" Text="UPDATES" />
|
|
|
|
<Border Classes="settingscard">
|
|
<StackPanel>
|
|
|
|
<Border Classes="settingsrow">
|
|
<Grid ColumnDefinitions="*,Auto">
|
|
<StackPanel Grid.Column="0" Spacing="6" Margin="0,0,16,0">
|
|
<TextBlock Classes="settingsrowtitle" Text="Version" />
|
|
<TextBlock Classes="mono" FontSize="12" Foreground="{StaticResource TextFaint}"
|
|
TextTrimming="CharacterEllipsis" Text="{Binding Updates.CurrentVersion}" />
|
|
</StackPanel>
|
|
<Button Grid.Column="1" Classes="ghost" Height="38" Content="CHECK NOW"
|
|
Command="{Binding Updates.CheckNowCommand}"
|
|
IsEnabled="{Binding Updates.CanCheckNow}" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Border Classes="settingsrow">
|
|
<Grid ColumnDefinitions="*,Auto">
|
|
<StackPanel Grid.Column="0" Spacing="6" Margin="0,0,16,0">
|
|
<TextBlock Classes="settingsrowtitle" Text="Check on its own" />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Every six hours while DodoSSH is running, starting at launch. It keeps checking while the keychain is locked — where builds come from has nothing to do with your vault — and while an update is already waiting, so what is offered is the newest build rather than the first one this run happened to see." />
|
|
</StackPanel>
|
|
<CheckBox Grid.Column="1" VerticalAlignment="Top"
|
|
IsChecked="{Binding Updates.IsAutomatic}"
|
|
IsEnabled="{Binding Updates.IsSupported}" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Border Classes="settingsrow" IsVisible="{Binding Updates.IsDownloading}">
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Classes="settingsrowtitle" Text="Downloading…" />
|
|
<ProgressBar Height="4" Minimum="0" Maximum="100"
|
|
Value="{Binding Updates.DownloadPercent}"
|
|
Foreground="{StaticResource Accent}" Background="{StaticResource Chip}" />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<Border Classes="settingsrow" IsVisible="{Binding Updates.IsReady}">
|
|
<Grid ColumnDefinitions="*,Auto">
|
|
<StackPanel Grid.Column="0" Spacing="6" Margin="0,0,16,0">
|
|
<TextBlock Classes="settingsrowtitle" Text="{Binding Updates.ReadyHeadline}" TextWrapping="Wrap" />
|
|
<TextBlock Classes="settingsrowcaption" Text="{Binding Updates.RestartWarning}" />
|
|
</StackPanel>
|
|
<Button Grid.Column="1" Classes="accent" Height="38" Content="RESTART NOW"
|
|
Command="{Binding Updates.RestartNowCommand}" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<Border Classes="settingsrow last">
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Builds come from the project's own release page, and never from the server you sign in to. Whoever hands you the client can hand you a client that copies your passphrase, and the operator of a DodoSSH deployment is the party the trust model is about. A deployment may tell you where to get it. It is not where it comes from." />
|
|
<TextBlock Classes="settingsrowcaption" Foreground="{StaticResource TextFaint}"
|
|
Text="{Binding Updates.Status}"
|
|
IsVisible="{Binding Updates.Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="This copy of DodoSSH cannot replace itself, so nothing above does anything. That is what a build run from a source checkout looks like, and also what a copy somebody unzipped by hand looks like — it is the installer that registers the update path."
|
|
IsVisible="{Binding Updates.IsUnsupported}" />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<TextBlock Classes="settingssection" Text="NOT BUILT YET" />
|
|
|
|
<Border Classes="settingscard">
|
|
<StackPanel Margin="24,20" Spacing="10">
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="These are on the design and have nothing behind them here. They are listed rather than left out, so what this page does not do is as legible as what it does. The full list, and what each would take, is in docs/design-import-gaps.md." />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Update channel — there is a nightly as well as a release, but which one a copy follows is fixed when it is built, so moving between them means installing the other one." />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Launch at login — nothing registers this application with Windows' own startup list." />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Reopen tabs from last session — nothing remembers which tabs were open across a launch, and every connection would need to authenticate again regardless." />
|
|
<TextBlock Classes="settingsrowcaption"
|
|
Text="Theme and language — only the one dark theme exists, and there is no translation anywhere in this client." />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
</UserControl>
|