refactor(shell): retire the Jobs destination

Nothing it carried is gone -- the two commits before this put all of it
somewhere the work is already being done.

A retired destination is the one shape #25's storage decision does not
make free. A visibility entry is a map key and an unknown key is
dropped on load; a launch page is a *value*, and an unknown one fails
validation -- which on the load path means the app refuses to start for
whoever had Jobs selected. `RetiredViews` is that list, read by
`ApplyDefaults`, which treats a retired name as a zero value. An
unknown-but-not-retired name still errors, because that is a typo and
saying so is the useful answer.
This commit is contained in:
2026-08-19 20:27:40 -04:00
parent 12af6ec1f7
commit 8efed2dd2b
6 changed files with 84 additions and 580 deletions
+10
View File
@@ -57,7 +57,17 @@ type GeneralConfig struct {
}
// ApplyDefaults fills zero-value fields with sensible defaults.
//
// A launch page naming a *retired* view is treated as a zero value
// rather than as an error, because the alternative is an app that will
// not start for anyone who had that page selected when it was removed.
// An unknown-but-not-retired name still fails Validate: that is a typo,
// and telling someone about it is the useful answer.
func (c *GeneralConfig) ApplyDefaults() {
if _, retired := RetiredViews[c.DefaultPage]; retired {
c.DefaultPage = ""
}
if c.DefaultPage == "" {
c.DefaultPage = DefaultDefaultPage
}
+16 -2
View File
@@ -24,10 +24,25 @@ const (
ViewExplore View = "explore"
ViewDownloads View = "downloads"
ViewAutotag View = "autotag"
ViewJobs View = "jobs"
ViewSettings View = "settings"
)
// RetiredViews are destinations that used to exist and no longer do.
//
// A *visibility* entry for a removed view needs no such list: it is a
// key in a map, and an unknown key is dropped on load. A `DefaultPage`
// is a **value**, and an unknown one fails validation -- which on the
// load path means the app refuses to start rather than a setting being
// ignored. So the one shape that cannot be retired for free is named
// here and reset to the default instead.
//
// `jobs` was folded into Settings by #27: library scans under
// Libraries, index work under Search Index, downloads under the
// download clients, and the autotag apply into the Autotag view.
var RetiredViews = map[View]struct{}{
"jobs": {},
}
// ViewSpec is what the backend knows about a destination. The label and
// the icon are deliberately absent: those are presentation, they live
// beside the rest of the app's icon vocabulary in
@@ -73,7 +88,6 @@ var Views = []ViewSpec{
{ID: ViewExplore, VisibleByDefault: true, Hideable: true, CanLaunch: true},
{ID: ViewDownloads, VisibleByDefault: true, Hideable: true, CanLaunch: true},
{ID: ViewAutotag, VisibleByDefault: false, Hideable: true, CanLaunch: true},
{ID: ViewJobs, VisibleByDefault: true, Hideable: true, CanLaunch: true},
{ID: ViewSettings, VisibleByDefault: true, Hideable: false, CanLaunch: false},
}
+58 -6
View File
@@ -66,7 +66,7 @@ func TestViewVisibilityStoredWins(t *testing.T) {
general := &GeneralConfig{
ViewVisibility: map[string]bool{
string(ViewAutotag): true,
string(ViewJobs): false,
string(ViewExplore): false,
},
}
general.ApplyDefaults()
@@ -77,8 +77,8 @@ func TestViewVisibilityStoredWins(t *testing.T) {
t.Error("autotag was switched on and should be visible")
}
if resolved[string(ViewJobs)] {
t.Error("jobs was switched off and should be hidden")
if resolved[string(ViewExplore)] {
t.Error("explore was switched off and should be hidden")
}
}
@@ -136,6 +136,58 @@ func TestValidateRevealsAHiddenLaunchPage(t *testing.T) {
}
}
// A launch page naming a view that no longer exists resets to the
// default instead of failing validation, which on the load path would
// mean the app refusing to start for whoever had it selected.
//
// This is the one shape #25's storage decision does *not* make free: a
// visibility entry is a key and an unknown key is dropped, but a launch
// page is a value.
func TestARetiredLaunchPageFallsBackToTheDefault(t *testing.T) {
t.Parallel()
general := &GeneralConfig{DefaultPage: "jobs"}
if err := general.Validate(); err != nil {
t.Fatalf("Validate() error: %v", err)
}
if general.DefaultPage != DefaultDefaultPage {
t.Errorf("DefaultPage = %q, want %q", general.DefaultPage, DefaultDefaultPage)
}
}
// A name that is merely wrong is still an error: that is a typo, and
// saying so is more useful than ignoring it.
func TestAnUnknownLaunchPageIsStillAnError(t *testing.T) {
t.Parallel()
general := &GeneralConfig{DefaultPage: "nonsense"}
if err := general.Validate(); !errors.Is(err, errUnknownDefaultPage) {
t.Fatalf("Validate() error = %v, want errUnknownDefaultPage", err)
}
}
// A retired view is not a view, so nothing offers it and nothing
// resolves it -- the visibility map included.
func TestARetiredViewIsGone(t *testing.T) {
t.Parallel()
for id := range RetiredViews {
if _, ok := LookupView(string(id)); ok {
t.Errorf("%s is retired but still in Views", id)
}
general := &GeneralConfig{}
general.ApplyDefaults()
if _, ok := general.ResolvedViewVisibility()[string(id)]; ok {
t.Errorf("%s is retired but still resolves a visibility", id)
}
}
}
// Settings may not be the launch page, which is the shape the old
// DefaultPage enum had and is now read off the same table.
func TestValidateRejectsAnUnlaunchablePage(t *testing.T) {
@@ -213,7 +265,7 @@ func TestViewVisibilityRoundTrips(t *testing.T) {
t.Fatalf("SetViewVisible() error: %v", err)
}
if err := original.SetViewVisible(string(ViewJobs), false); err != nil {
if err := original.SetViewVisible(string(ViewExplore), false); err != nil {
t.Fatalf("SetViewVisible() error: %v", err)
}
@@ -228,8 +280,8 @@ func TestViewVisibilityRoundTrips(t *testing.T) {
t.Error("autotag should have loaded as visible")
}
if resolved[string(ViewJobs)] {
t.Error("jobs should have loaded as hidden")
if resolved[string(ViewExplore)] {
t.Error("explore should have loaded as hidden")
}
}