Skip to content

Commit d9206ea

Browse files
committed
refactor(preferences): replace defaultMods and preferredTileset with mods and tileset
- Rename APIs and properties for clarity: `defaultMods` -> `mods`, `preferredTileset` -> `tileset`. - Update localStorage keys to `cbn-guide:mods` and `cbn-guide:tileset`. - Refactor persistence methods and related tests. - Update ADR-008 to reflect key renaming.
1 parent c1286ad commit d9206ea

File tree

7 files changed

+113
-131
lines changed

7 files changed

+113
-131
lines changed

docs/adr/ADR-008_mod_preset_persistence.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
- **Status:** Accepted
44
- **Context:** Users repeatedly configure the same modset. The URL is the source of truth (ADR-002), but bare URLs have no mods.
5-
- **Decision:** `localStorage` stores a default mod preset under `cbn-guide:default-mods`. On bootstrap, bare URLs (no `mods` param) get the saved preset injected via `replaceState`. Applying mods in the ModSelector automatically saves them. Saving an empty modset clears the preset.
5+
- **Decision:** `localStorage` stores a default mod preset under `cbn-guide:mods`. On bootstrap, bare URLs (no `mods` param) get the saved preset injected via `replaceState`. Applying mods in the ModSelector automatically saves them. Saving an empty modset clears the preset.
66
- **Consequences:**
77
- Positive: users configure once, bare URLs auto-load their preset.
88
- Negative: `localStorage` adds a persistence dependency; clearing browser data loses the preset.

src/navigation.svelte.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { resolveTileset } from "./tile-data";
22
import {
33
initializePreferences,
44
preferences,
5-
setDefaultMods,
6-
setPreferredTileset,
5+
setMods,
6+
setTileset,
77
} from "./preferences.svelte";
88
import {
99
buildURL,
@@ -51,10 +51,7 @@ export const navigation: NavigationContext = {
5151
return page.route.localeParam ?? DEFAULT_LOCALE;
5252
},
5353
get tileset(): string {
54-
return resolveTileset(
55-
preferences.preferredTileset,
56-
page.route.tilesetParam,
57-
);
54+
return resolveTileset(preferences.tileset, page.route.tilesetParam);
5855
},
5956
get mods(): string[] {
6057
return page.route.modsParam;
@@ -106,7 +103,7 @@ export function updateSearchRoute(
106103
*
107104
*/
108105
export function changeTileset(tileset: string): void {
109-
if (setPreferredTileset(tileset)) {
106+
if (setTileset(tileset)) {
110107
navigateToURL(
111108
buildURL(
112109
navigation.buildRequestedVersion,
@@ -149,7 +146,7 @@ export function changeVersion(buildVersion: string): void {
149146
}
150147

151148
export function changeMods(mods: string[]): void {
152-
setDefaultMods(mods);
149+
setMods(mods);
153150
location.href = buildURL(
154151
navigation.buildRequestedVersion,
155152
navigation.target,
@@ -184,14 +181,12 @@ function buildCanonicalBootstrapURL(
184181
const canonicalPath = resolveVersionedPath(currentURL.pathname, builds);
185182

186183
const canonicalMods =
187-
page.route.modsParam.length > 0
188-
? page.route.modsParam
189-
: (preferences.defaultMods ?? []);
184+
page.route.modsParam.length > 0 ? page.route.modsParam : preferences.mods;
190185

191186
const canonicalLocale = page.route.localeParam;
192187

193188
const canonicalTileset = resolveTileset(
194-
preferences.preferredTileset,
189+
preferences.tileset,
195190
page.route.tilesetParam,
196191
);
197192

src/navigation.test.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
_resetPreferences,
1717
initializePreferences,
1818
preferences,
19-
setPreferredTileset,
19+
setTileset,
2020
} from "./preferences.svelte";
2121
import {
2222
bootstrapApplication,
@@ -48,7 +48,7 @@ describe("navigation", () => {
4848
_resetPreferences();
4949
_resetBuildsState();
5050
localStorage.removeItem?.("cbn-guide:tileset");
51-
localStorage.removeItem?.("cbn-guide:default-mods");
51+
localStorage.removeItem?.("cbn-guide:mods");
5252
global.fetch = defaultFetchMock;
5353
});
5454

@@ -67,7 +67,7 @@ describe("navigation", () => {
6767

6868
test("derives the effective tileset from preferences when the URL omits t", async () => {
6969
initializePreferences();
70-
setPreferredTileset("retrodays");
70+
setTileset("retrodays");
7171
await initializeBuildsState();
7272

7373
expect(navigation).toMatchObject({
@@ -78,7 +78,7 @@ describe("navigation", () => {
7878
});
7979

8080
test("uses a valid URL tileset override transiently without persisting it", async () => {
81-
setPreferredTileset("retrodays");
81+
setTileset("retrodays");
8282
setWindowLocation("stable/item/rock", "?t=ultica");
8383
_resetRouting();
8484
await initializeBuildsState();
@@ -87,8 +87,8 @@ describe("navigation", () => {
8787
tileset: "ultica",
8888
});
8989
expect(preferences).toEqual({
90-
preferredTileset: "retrodays",
91-
defaultMods: null,
90+
tileset: "retrodays",
91+
mods: [],
9292
});
9393
expect(buildLinkTo({ kind: "home" })).toBe("/stable/?t=ultica");
9494
});
@@ -191,8 +191,8 @@ describe("navigation", () => {
191191

192192
expect(navigation.tileset).toBe("retrodays");
193193
expect(preferences).toEqual({
194-
preferredTileset: "retrodays",
195-
defaultMods: null,
194+
tileset: "retrodays",
195+
mods: [],
196196
});
197197
expect(window.location.search).toContain("t=retrodays");
198198
expect(replaceStateSpy).toHaveBeenCalledOnce();
@@ -230,10 +230,7 @@ describe("navigation", () => {
230230
});
231231

232232
test("bootstrap injects saved mods into bare URL", async () => {
233-
localStorage.setItem(
234-
"cbn-guide:default-mods",
235-
JSON.stringify(["aftershock"]),
236-
);
233+
localStorage.setItem("cbn-guide:mods", JSON.stringify(["aftershock"]));
237234
setWindowLocation("stable/");
238235
_resetRouting();
239236
const replaceStateSpy = vi
@@ -256,10 +253,7 @@ describe("navigation", () => {
256253
});
257254

258255
test("bootstrap does not inject when URL already has mods", async () => {
259-
localStorage.setItem(
260-
"cbn-guide:default-mods",
261-
JSON.stringify(["magiclysm"]),
262-
);
256+
localStorage.setItem("cbn-guide:mods", JSON.stringify(["magiclysm"]));
263257
setWindowLocation("stable/", "?mods=aftershock");
264258
_resetRouting();
265259
const replaceStateSpy = vi.spyOn(history, "replaceState");
@@ -275,7 +269,7 @@ describe("navigation", () => {
275269
});
276270

277271
test("bootstrap does not inject when no preset saved", async () => {
278-
localStorage.removeItem("cbn-guide:default-mods");
272+
localStorage.removeItem("cbn-guide:mods");
279273
setWindowLocation("stable/");
280274
_resetRouting();
281275
const replaceStateSpy = vi.spyOn(history, "replaceState");
@@ -292,10 +286,7 @@ describe("navigation", () => {
292286

293287
test("bootstrap canonicalizes version and saved preferences with one replaceState", async () => {
294288
localStorage.setItem("cbn-guide:tileset", "retrodays");
295-
localStorage.setItem(
296-
"cbn-guide:default-mods",
297-
JSON.stringify(["aftershock"]),
298-
);
289+
localStorage.setItem("cbn-guide:mods", JSON.stringify(["aftershock"]));
299290
setWindowLocation("bogus/item/rock");
300291
_resetRouting();
301292
const replaceStateSpy = vi

src/preferences.svelte.test.ts

Lines changed: 55 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
66
import {
77
_resetPreferences,
8-
clearSavedDefaultMods,
8+
clearSavedMods,
99
initializePreferences,
10+
MODS_STORAGE_KEY,
1011
preferences,
11-
setDefaultMods,
12-
setPreferredTileset,
12+
setMods,
13+
setTileset,
14+
TILESET_STORAGE_KEY,
1315
} from "./preferences.svelte";
1416

15-
const STORAGE_KEY = "cbn-guide:tileset";
16-
const DEFAULT_MODS_STORAGE_KEY = "cbn-guide:default-mods";
1717
const DEFAULT_TILESET = "undead_people";
1818

1919
function installMockStorage() {
@@ -44,46 +44,46 @@ function installMockStorage() {
4444
describe("preferences", () => {
4545
beforeEach(() => {
4646
installMockStorage();
47-
localStorage.removeItem?.(STORAGE_KEY);
47+
localStorage.removeItem?.(TILESET_STORAGE_KEY);
4848
_resetPreferences();
4949
});
5050

5151
afterEach(() => {
5252
vi.restoreAllMocks();
53-
localStorage.removeItem?.(STORAGE_KEY);
53+
localStorage.removeItem?.(TILESET_STORAGE_KEY);
5454
_resetPreferences();
5555
});
5656

5757
test("loads the persisted tileset preference when the route omits it", () => {
58-
localStorage.setItem(STORAGE_KEY, "retrodays");
58+
localStorage.setItem(TILESET_STORAGE_KEY, "retrodays");
5959

6060
expect(initializePreferences()).toEqual({
61-
preferredTileset: "retrodays",
62-
defaultMods: null,
61+
tileset: "retrodays",
62+
mods: [],
6363
});
6464
expect(preferences).toEqual({
65-
preferredTileset: "retrodays",
66-
defaultMods: null,
65+
tileset: "retrodays",
66+
mods: [],
6767
});
6868
});
6969

7070
test("falls back to the default tileset when storage contains an invalid value", () => {
71-
localStorage.setItem(STORAGE_KEY, "not-a-real-tileset");
71+
localStorage.setItem(TILESET_STORAGE_KEY, "not-a-real-tileset");
7272

7373
expect(initializePreferences()).toEqual({
74-
preferredTileset: DEFAULT_TILESET,
75-
defaultMods: null,
74+
tileset: DEFAULT_TILESET,
75+
mods: [],
7676
});
77-
expect(localStorage.getItem(STORAGE_KEY)).toBe(DEFAULT_TILESET);
77+
expect(localStorage.getItem(TILESET_STORAGE_KEY)).toBe(DEFAULT_TILESET);
7878
});
7979

8080
test("setPreferredTileset rejects invalid values without mutating the preference", () => {
81-
expect(setPreferredTileset("bad-input")).toBe(false);
81+
expect(setTileset("bad-input")).toBe(false);
8282
expect(preferences).toEqual({
83-
preferredTileset: DEFAULT_TILESET,
84-
defaultMods: null,
83+
tileset: DEFAULT_TILESET,
84+
mods: [],
8585
});
86-
expect(localStorage.getItem(STORAGE_KEY)).toBeNull();
86+
expect(localStorage.getItem(TILESET_STORAGE_KEY)).toBeNull();
8787
});
8888

8989
test("swallows storage failures during initialization and writes", () => {
@@ -95,95 +95,86 @@ describe("preferences", () => {
9595
});
9696

9797
expect(initializePreferences()).toEqual({
98-
preferredTileset: DEFAULT_TILESET,
99-
defaultMods: null,
98+
tileset: DEFAULT_TILESET,
99+
mods: [],
100100
});
101101

102-
expect(() => setPreferredTileset("retrodays")).not.toThrow();
102+
expect(() => setTileset("retrodays")).not.toThrow();
103103
expect(preferences).toEqual({
104-
preferredTileset: "retrodays",
105-
defaultMods: null,
104+
tileset: "retrodays",
105+
mods: [],
106106
});
107107
});
108108
});
109109

110-
describe("defaultMods", () => {
110+
describe("mods", () => {
111111
beforeEach(() => {
112112
installMockStorage();
113-
localStorage.removeItem?.(DEFAULT_MODS_STORAGE_KEY);
113+
localStorage.removeItem?.(MODS_STORAGE_KEY);
114114
_resetPreferences();
115115
});
116116

117117
afterEach(() => {
118118
vi.restoreAllMocks();
119-
localStorage.removeItem?.(DEFAULT_MODS_STORAGE_KEY);
119+
localStorage.removeItem?.(MODS_STORAGE_KEY);
120120
_resetPreferences();
121121
});
122122

123123
test("loads saved modset from localStorage", () => {
124124
localStorage.setItem(
125-
DEFAULT_MODS_STORAGE_KEY,
125+
MODS_STORAGE_KEY,
126126
JSON.stringify(["aftershock", "magiclysm"]),
127127
);
128128

129-
expect(initializePreferences().defaultMods).toEqual([
130-
"aftershock",
131-
"magiclysm",
132-
]);
133-
expect(preferences.defaultMods).toEqual(["aftershock", "magiclysm"]);
129+
expect(initializePreferences().mods).toEqual(["aftershock", "magiclysm"]);
130+
expect(preferences.mods).toEqual(["aftershock", "magiclysm"]);
134131
});
135132

136133
test("returns null when no key exists", () => {
137-
expect(initializePreferences().defaultMods).toBeNull();
138-
expect(preferences.defaultMods).toBeNull();
134+
expect(initializePreferences().mods).toEqual([]);
135+
expect(preferences.mods).toEqual([]);
139136
});
140137

141138
test("discards malformed JSON gracefully", () => {
142-
localStorage.setItem(DEFAULT_MODS_STORAGE_KEY, "{ bad json }");
139+
localStorage.setItem(MODS_STORAGE_KEY, "{ bad json }");
143140

144-
expect(initializePreferences().defaultMods).toBeNull();
145-
expect(preferences.defaultMods).toBeNull();
141+
expect(initializePreferences().mods).toEqual([]);
142+
expect(preferences.mods).toEqual([]);
146143
});
147144

148145
test("discards non-array JSON gracefully", () => {
149146
localStorage.setItem(
150-
DEFAULT_MODS_STORAGE_KEY,
147+
MODS_STORAGE_KEY,
151148
JSON.stringify({ mods: ["aftershock"] }),
152149
);
153150

154-
expect(initializePreferences().defaultMods).toBeNull();
155-
expect(preferences.defaultMods).toBeNull();
151+
expect(initializePreferences().mods).toEqual([]);
152+
expect(preferences.mods).toEqual([]);
156153
});
157154

158155
test("setDefaultMods persists and updates state", () => {
159-
setDefaultMods(["aftershock"]);
156+
setMods(["aftershock"]);
160157

161-
expect(preferences.defaultMods).toEqual(["aftershock"]);
162-
expect(localStorage.getItem(DEFAULT_MODS_STORAGE_KEY)).toBe(
158+
expect(preferences.mods).toEqual(["aftershock"]);
159+
expect(localStorage.getItem(MODS_STORAGE_KEY)).toBe(
163160
JSON.stringify(["aftershock"]),
164161
);
165162
});
166163

167164
test("setDefaultMods([]) clears the preset", () => {
168-
localStorage.setItem(
169-
DEFAULT_MODS_STORAGE_KEY,
170-
JSON.stringify(["aftershock"]),
171-
);
172-
setDefaultMods([]);
165+
localStorage.setItem(MODS_STORAGE_KEY, JSON.stringify(["aftershock"]));
166+
setMods([]);
173167

174-
expect(preferences.defaultMods).toBeNull();
175-
expect(localStorage.getItem(DEFAULT_MODS_STORAGE_KEY)).toBeNull();
168+
expect(preferences.mods).toEqual([]);
169+
expect(localStorage.getItem(MODS_STORAGE_KEY)).toBeNull();
176170
});
177171

178172
test("clearSavedDefaultMods removes key and state", () => {
179-
localStorage.setItem(
180-
DEFAULT_MODS_STORAGE_KEY,
181-
JSON.stringify(["aftershock"]),
182-
);
183-
clearSavedDefaultMods();
173+
localStorage.setItem(MODS_STORAGE_KEY, JSON.stringify(["aftershock"]));
174+
clearSavedMods();
184175

185-
expect(preferences.defaultMods).toBeNull();
186-
expect(localStorage.getItem(DEFAULT_MODS_STORAGE_KEY)).toBeNull();
176+
expect(preferences.mods).toEqual([]);
177+
expect(localStorage.getItem(MODS_STORAGE_KEY)).toBeNull();
187178
});
188179

189180
test("swallows storage failures silently", () => {
@@ -197,12 +188,12 @@ describe("defaultMods", () => {
197188
throw new Error("storage denied");
198189
});
199190

200-
expect(initializePreferences().defaultMods).toBeNull();
191+
expect(initializePreferences().mods).toEqual([]);
201192

202-
expect(() => setDefaultMods(["aftershock"])).not.toThrow();
203-
expect(preferences.defaultMods).toEqual(["aftershock"]);
193+
expect(() => setMods(["aftershock"])).not.toThrow();
194+
expect(preferences.mods).toEqual(["aftershock"]);
204195

205-
expect(() => clearSavedDefaultMods()).not.toThrow();
206-
expect(preferences.defaultMods).toBeNull();
196+
expect(() => clearSavedMods()).not.toThrow();
197+
expect(preferences.mods).toEqual([]);
207198
});
208199
});

0 commit comments

Comments
 (0)