mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add snippet-library search, sort, empty states, storage monitor (M6, §02/§09D)
This commit is contained in:
@@ -2,8 +2,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
loadPanelLayout,
|
||||
loadPaneVisibility,
|
||||
loadSnippetSort,
|
||||
savePanelLayout,
|
||||
savePaneVisibility,
|
||||
saveSnippetSort,
|
||||
} from './ux-prefs';
|
||||
|
||||
const KEY = 'astrolabe:ux-prefs';
|
||||
@@ -98,3 +100,33 @@ describe('ux-prefs · paneVisibility', () => {
|
||||
expect(stored.paneVisibility.editor).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ux-prefs · snippetSort', () => {
|
||||
beforeEach(() => vi.stubGlobal('localStorage', makeStorageStub()));
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it('defaults to Modified, descending when nothing is stored', () => {
|
||||
expect(loadSnippetSort()).toEqual({ by: 'modified', order: 'desc' });
|
||||
});
|
||||
|
||||
it('round-trips a stored sort', () => {
|
||||
saveSnippetSort({ by: 'name', order: 'asc' });
|
||||
expect(loadSnippetSort()).toEqual({ by: 'name', order: 'asc' });
|
||||
});
|
||||
|
||||
it('falls back to defaults for invalid stored values', () => {
|
||||
localStorage.setItem(KEY, JSON.stringify({ snippetSort: { by: 'bogus', order: 'sideways' } }));
|
||||
expect(loadSnippetSort()).toEqual({ by: 'modified', order: 'desc' });
|
||||
});
|
||||
|
||||
it('preserves panelLayout when writing the sort (separate keys)', () => {
|
||||
savePanelLayout({ libraryWidth: 280 });
|
||||
saveSnippetSort({ by: 'size', order: 'desc' });
|
||||
const stored = JSON.parse(localStorage.getItem(KEY)!) as {
|
||||
panelLayout: { libraryWidth: number };
|
||||
snippetSort: { by: string; order: string };
|
||||
};
|
||||
expect(stored.panelLayout.libraryWidth).toBe(280);
|
||||
expect(stored.snippetSort).toEqual({ by: 'size', order: 'desc' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* These preferences persist **separately** from UserSettings so they can change
|
||||
* frequently (a drag emits many width updates) without rewriting the settings
|
||||
* record. Its own key, `astrolabe:ux-prefs`, holds the snippet sort preference
|
||||
* (lands with M5/M6) and the panel layout (per-pane widths + visibility).
|
||||
* (spec §02 → Sort) and the panel layout (per-pane widths + visibility).
|
||||
*
|
||||
* This slice persists the panel **widths** and per-pane **visibility** (spec §01A).
|
||||
* Read-with-fallback + write-through merge, the same contract as the settings
|
||||
@@ -15,6 +15,13 @@
|
||||
* `localStorage`; everything else goes through these typed functions.
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_SORT_BY,
|
||||
DEFAULT_SORT_ORDER,
|
||||
type SortBy,
|
||||
type SortOrder,
|
||||
} from '@core/snippet-sort';
|
||||
|
||||
const KEY = 'astrolabe:ux-prefs';
|
||||
|
||||
/** Per-pane widths (px). Optional — a missing field falls back to its default. */
|
||||
@@ -30,9 +37,16 @@ export interface PaneVisibilityPref {
|
||||
preview?: boolean;
|
||||
}
|
||||
|
||||
/** The persisted library sort (spec §02 → Sort; persists across sessions). */
|
||||
export interface SnippetSortPref {
|
||||
by: SortBy;
|
||||
order: SortOrder;
|
||||
}
|
||||
|
||||
interface StoredPrefs {
|
||||
panelLayout?: PanelLayout;
|
||||
paneVisibility?: PaneVisibilityPref;
|
||||
snippetSort?: Partial<SnippetSortPref>;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -106,3 +120,29 @@ export function savePaneVisibility(visibility: PaneVisibilityPref): void {
|
||||
const current = readRaw();
|
||||
writeRaw({ ...current, paneVisibility: { ...current.paneVisibility, ...visibility } });
|
||||
}
|
||||
|
||||
/** Guards against junk in storage — only the known sort fields survive. */
|
||||
function sortBy(v: unknown): SortBy | undefined {
|
||||
return v === 'modified' || v === 'created' || v === 'name' || v === 'size' ? v : undefined;
|
||||
}
|
||||
function sortOrder(v: unknown): SortOrder | undefined {
|
||||
return v === 'asc' || v === 'desc' ? v : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The persisted library sort, falling back to the spec default (Modified, desc)
|
||||
* for anything missing or invalid in storage.
|
||||
*/
|
||||
export function loadSnippetSort(): SnippetSortPref {
|
||||
const stored = readRaw().snippetSort ?? {};
|
||||
return {
|
||||
by: sortBy(stored.by) ?? DEFAULT_SORT_BY,
|
||||
order: sortOrder(stored.order) ?? DEFAULT_SORT_ORDER,
|
||||
};
|
||||
}
|
||||
|
||||
/** Persist the library sort, preserving every other key already in the record. */
|
||||
export function saveSnippetSort(sort: SnippetSortPref): void {
|
||||
const current = readRaw();
|
||||
writeRaw({ ...current, snippetSort: { ...current.snippetSort, ...sort } });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user