Implement fit-mode rendering contract with container sizing and pane re-fit

This commit is contained in:
2026-06-05 10:45:41 +03:00
parent f4253f50ca
commit 411bfbc6c2
13 changed files with 552 additions and 73 deletions
+27 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { loadUiTheme, saveUiTheme } from './settings-store';
import { loadPreviewFitMode, loadUiTheme, savePreviewFitMode, saveUiTheme } from './settings-store';
const KEY = 'astrolabe:settings';
@@ -84,3 +84,29 @@ describe('settings-store · ui.theme', () => {
});
});
});
describe('settings-store · preview.fitMode', () => {
beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
afterEach(() => vi.unstubAllGlobals());
it('defaults to Original (default) when nothing is stored', () => {
expect(loadPreviewFitMode()).toBe('default');
});
it('returns a stored valid fit mode', () => {
localStorage.setItem(KEY, JSON.stringify({ preview: { fitMode: 'full' } }));
expect(loadPreviewFitMode()).toBe('full');
});
it('falls back to default for an unrecognized value', () => {
localStorage.setItem(KEY, JSON.stringify({ preview: { fitMode: 'cover' } }));
expect(loadPreviewFitMode()).toBe('default');
});
it('round-trips through load and preserves the theme slice', () => {
saveUiTheme('dark');
savePreviewFitMode('height');
expect(loadPreviewFitMode()).toBe('height');
expect(loadUiTheme()).toBe('dark'); // the other slice survives the merge
});
});
+21
View File
@@ -13,6 +13,7 @@
* `localStorage`; everything else goes through these typed functions.
*/
import type { FitMode } from '@core/rendering';
import type { UiTheme } from '@core/theme';
const KEY = 'astrolabe:settings';
@@ -20,9 +21,13 @@ const KEY = 'astrolabe:settings';
/** Spec §07 Appearance default. */
const DEFAULT_THEME: UiTheme = 'light';
/** Spec §04 — the Fit control defaults to Original. */
const DEFAULT_FIT_MODE: FitMode = 'default';
/** Loose view of the stored record — M5 will give this its full typed shape. */
interface StoredSettings {
ui?: { theme?: unknown; [k: string]: unknown };
preview?: { fitMode?: unknown; [k: string]: unknown };
[k: string]: unknown;
}
@@ -61,6 +66,10 @@ function isUiTheme(v: unknown): v is UiTheme {
return v === 'light' || v === 'dark';
}
function isFitMode(v: unknown): v is FitMode {
return v === 'default' || v === 'width' || v === 'height' || v === 'full';
}
/** The persisted UI theme, or the default — unknown/legacy values fall back. */
export function loadUiTheme(): UiTheme {
const stored = readRaw().ui?.theme;
@@ -74,3 +83,15 @@ export function saveUiTheme(theme: UiTheme): void {
const current = readRaw();
writeRaw({ ...current, ui: { ...current.ui, theme } });
}
/** The persisted preview fit mode, or the default — unknown values fall back. */
export function loadPreviewFitMode(): FitMode {
const stored = readRaw().preview?.fitMode;
return isFitMode(stored) ? stored : DEFAULT_FIT_MODE;
}
/** Persist the preview fit mode, preserving every other key already in the record. */
export function savePreviewFitMode(fitMode: FitMode): void {
const current = readRaw();
writeRaw({ ...current, preview: { ...current.preview, fitMode } });
}