Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit d1794d7c891f4e04740dcccec7fb5000170ad48f
parent da4a6589d70a8f220e1051834156cc809dc76296
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 16:21:31 -0400

common: the filter snapshot stores notr (Transcripts unticked) and lc (Live chat ticked)

Read, compared and written beside nop, each only off its default, so a
profile saved before the row reads transcripts and posts as it always did.
parseSnapshot is exported for the round-trip test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Acommon/components/exportFilterStorage.test.ts | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/components/exportFilterStorage.ts | 19++++++++++++++++---
2 files changed, 99 insertions(+), 3 deletions(-)

diff --git a/common/components/exportFilterStorage.test.ts b/common/components/exportFilterStorage.test.ts @@ -0,0 +1,83 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + emptySnapshot, + emptyStoredState, + loadStoredState, + parseSnapshot, + saveStoredState, + snapshotsEqual, +} from "./exportFilterStorage"; + +// The "Search in" row's two new keys (release 16): `notr` (Transcripts +// unticked) and `lc` (Live chat ticked), stored beside `nop` only when off the +// default, in the working snapshot and in every profile. + +test("notr and lc survive a JSON round trip through the parser", () => { + const snap = { ...emptySnapshot(), notr: true, nop: true, lc: true }; + const back = parseSnapshot(JSON.parse(JSON.stringify(snap))); + assert.ok(back); + assert.equal(back.notr, true); + assert.equal(back.nop, true); + assert.equal(back.lc, true); + assert.ok(snapshotsEqual(snap, back)); +}); + +test("absent reads as the default, and the default is not written", () => { + const back = parseSnapshot({ channels: { included: [], excluded: [] } }); + assert.ok(back); + assert.equal("notr" in back, false); + assert.equal("lc" in back, false); + // A profile saved before the row existed equals one that spells the + // defaults out. + assert.ok(snapshotsEqual(back, { ...emptySnapshot(), notr: false, lc: false })); +}); + +test("a value that is not a boolean is dropped, not coerced", () => { + const back = parseSnapshot({ + channels: { included: [], excluded: [] }, + notr: "yes", + lc: 1, + }); + assert.ok(back); + assert.equal(back.notr, undefined); + assert.equal(back.lc, undefined); +}); + +test("snapshotsEqual tells each of the row's keys apart", () => { + const base = emptySnapshot(); + assert.ok(!snapshotsEqual(base, { ...base, notr: true })); + assert.ok(!snapshotsEqual(base, { ...base, lc: true })); + assert.ok(!snapshotsEqual({ ...base, notr: true }, { ...base, lc: true })); +}); + +test("the working snapshot and a profile keep the row through storage", () => { + const store = new Map<string, string>(); + const g = globalThis as { window?: unknown }; + const before = g.window; + g.window = { + localStorage: { + getItem: (k: string) => store.get(k) ?? null, + setItem: (k: string, v: string) => void store.set(k, v), + removeItem: (k: string) => void store.delete(k), + }, + }; + try { + const state = emptyStoredState(); + state.working = { ...emptySnapshot(), lc: true }; + state.profiles = { chatOnly: { ...emptySnapshot(), notr: true, nop: true, lc: true } }; + state.activeProfileName = "chatOnly"; + saveStoredState(state); + const loaded = loadStoredState(); + assert.ok(loaded); + assert.equal(loaded.working.lc, true); + assert.equal(loaded.working.notr, undefined); + assert.deepEqual( + [loaded.profiles.chatOnly.notr, loaded.profiles.chatOnly.nop, loaded.profiles.chatOnly.lc], + [true, true, true], + ); + assert.equal(loaded.activeProfileName, "chatOnly"); + } finally { + g.window = before; + } +}); diff --git a/common/components/exportFilterStorage.ts b/common/components/exportFilterStorage.ts @@ -26,9 +26,17 @@ const VERSION = 1; export type FilterSnapshot = { channels: { included: string[]; excluded: string[] }; nov?: boolean; - // Exclude the social-post corpus — the third media kind beside videos and - // livestreams (see SearchSessionContext.passesFilter). + // The "Search in" row (release 16): what a plain query — a "transcripts" + // leaf — reads. Each is stored only off its default, so a profile saved + // before the row existed reads transcripts and posts, as it always did. + // notr — Transcripts unticked (the transcript cues are not read) + // nop — Posts unticked (the social-post corpus; before the row this was + // the Posts box in the Type row, and it still keeps the posts + // corpus out of the scope a "Posts" leaf reads) + // lc — Live chat ticked (the live_chat track is read; off by default) + notr?: boolean; nop?: boolean; + lc?: boolean; nol?: boolean; naa?: boolean; nar?: boolean; @@ -124,7 +132,8 @@ function isStringArray(v: unknown): v is string[] { return Array.isArray(v) && v.every((x) => typeof x === "string"); } -function parseSnapshot(raw: unknown): FilterSnapshot | null { +// Exported for the round-trip test; the app reads through loadStoredState. +export function parseSnapshot(raw: unknown): FilterSnapshot | null { if (!raw || typeof raw !== "object") return null; const r = raw as Record<string, unknown>; const ch = r.channels as Record<string, unknown> | undefined; @@ -140,6 +149,8 @@ function parseSnapshot(raw: unknown): FilterSnapshot | null { // `nop` and `nu` were written by committedSnapshot but never read back here, // so both were silently dropped on every reload. if (typeof r.nop === "boolean") snap.nop = r.nop; + if (typeof r.notr === "boolean") snap.notr = r.notr; + if (typeof r.lc === "boolean") snap.lc = r.lc; if (typeof r.nol === "boolean") snap.nol = r.nol; if (typeof r.naa === "boolean") snap.naa = r.naa; if (typeof r.nar === "boolean") snap.nar = r.nar; @@ -257,6 +268,8 @@ export function snapshotsEqual(a: FilterSnapshot, b: FilterSnapshot): boolean { } if ((a.nov ?? false) !== (b.nov ?? false)) return false; if ((a.nop ?? false) !== (b.nop ?? false)) return false; + if ((a.notr ?? false) !== (b.notr ?? false)) return false; + if ((a.lc ?? false) !== (b.lc ?? false)) return false; if ((a.nol ?? false) !== (b.nol ?? false)) return false; if ((a.naa ?? false) !== (b.naa ?? false)) return false; if ((a.nar ?? false) !== (b.nar ?? false)) return false;