The Settings switch snaps back to the saved state when clicked and
follows it once the PIN action succeeds, so a cancelled or refused PIN
no longer leaves it showing the opposite state. A failed switch write is
undone to the value read after the settings retry instead of the
hard-coded inverse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Xtream and M3U management dialogs offer lock toggles, and the
Stalker lock dialog opens, only once the lock store has been read. A
draft built from the empty fail-closed snapshot would otherwise replace
the real locks with nothing on Save if storage recovered in between.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The groups view captures the playlist before the PIN prompt and drops
the management dialog's hidden and locked group lists once another
playlist is open, so they cannot be saved under that playlist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The enforcement service now checks the active M3U channel whenever it
changes while locked, so numeric zapping, next/previous and remote
commands, which select from the full channel list, cannot start a
channel of a locked group. Numeric zapping also skips such a channel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A locked session can no longer change the relock timeout: the Settings
selector is disabled until the PIN is entered and the service refuses
the change while locked. An M3U right-click lock toggle now captures its
playlist before the PIN prompt and is saved only if that playlist is
still open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A rejected lock-state sync to the SQLite worker makes the locked
session withhold everything until a later sync succeeds.
- The Stalker enforcement chunk is preloaded when a Stalker route
opens; a relock runs it synchronously, or leaves the route at once
while it is not loaded, instead of awaiting the chunk.
- Xtream "Manage categories" captures playlist, provider and section
before the PIN prompt and re-checks them after it and after the
dialog import.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The VOD multi-source host keys its discovery session to the parental
lock version: a lock change drops the discovered sources, retires
discoveries and switches in flight, and rediscovers through the
worker's new lock state. Stale-index entries of a write still stamping
are no longer retried by a concurrent reconcile, which could re-stamp
from a store the write had not committed yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Catalog title matching and multi-source discovery query the SQLite
worker directly; they now return nothing while the parental lock
withholds everything (unreadable store or a bridge without the worker
filter). The Stalker lock dialog captures its playlist, provider and
section before the PIN prompt and re-checks them after it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the lock store cannot be read every portal category is withheld
but no locked ids are known, so the rail showed no "Enter PIN to show"
row. It now shows the row without a count in that state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an Xtream lock edit's re-stamp fails and the rollback store write
fails too, memory now goes back to the previous locks and a pending
rewrite persists them on the next store access before the index is
re-stamped, so the failed edit cannot take effect through that re-stamp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lock removal is now authorized right before its first write is issued;
a relock that lands after that is ordered after the write, which
completes. This drops the post-write rollback, whose own failure could
leave the persisted store diverged from memory across a restart. The
Stalker search closes a detail without a genre on relock while every
category is withheld.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When writing the previous store back after a relock-refused removal
fails, the lock store now keeps a pending rewrite, is not readable (the
locked session withholds everything) and rewrites the persisted store
from memory on the next access, so a restart cannot load the removal.
A failed "Remove all playlists" clear shares the same retry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lock removal that passed the unlocked check before its write is asked
again right after the store write and, for Xtream, after the index
stamps. A relock in between writes the previous store back or rolls the
stamps back before anything is published. The stale-index bookkeeping,
index stamping and store merge move into helpers to keep the lock store
within the file size limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new runtime capability requires the lock-state and index-stamping IPC.
When Electron reads Xtream through the SQLite worker without it (a
partial or older preload), the locked session withholds every category
instead of trusting a worker that never learned the lock state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The synchronous relock step now clears a selected Xtream item whose
category the visible category list cannot place (a manually hidden
category opened through search), instead of leaving it usable until the
awaited reloads and lookup finish.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lock edits that take a lock away now commit only while the session is
unlocked, checked inside the write queue at commit time, so an editor
save still in flight (or queued) when the app relocks cannot remove
locks. Adding locks stays allowed. The Xtream category dialog drops its
lock draft after a relock, and a backup restore re-asks the PIN only
when it would remove a lock. Clearing a playlist's last lock keeps its
index stale until the store write has landed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A backup merge now asks for the PIN again right before it replaces a
playlist's locks when the app relocked during the import, instead of
relying on the answer given at the start. The wrong-PIN count and the
30-second pause move from the dialog into the lock service, so
dismissing and reopening the prompt no longer resets them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload issued for playlist A no longer publishes into the shared Xtream
store after the user opened playlist B: the store's reloads guard on the
playlist they read for, and the enforcement apply retires its search
refresh and selection checks on a playlist switch as on a newer lock
version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A backup carrying lock lists replaces the matching playlists' locks,
possibly with an emptier set; the import now asks for the PIN (after the
file was chosen) and aborts when it is refused.
- While a write re-stamps the SQLite index the playlist counts as stale,
so a relock inside that window reloads fail-closed instead of through
the old stamps. The internal store write now needs only a readable
store, so a rollback can still land.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- On relock the synchronous fail-closed steps (M3U channel, Stalker
selection, the locked Xtream detail, catalog lists, stored search) run
immediately instead of queueing behind an earlier apply that may still
wait on a slow or hung read.
- The post-reload Xtream checks decide by the lock store through the
unfiltered category rows rather than by absence from the reloaded list,
which also omits merely hidden categories; unreadable rows fail closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A backup restore now writes the parental locks last, so a failed Xtream
merge leaves the playlist's previous locks in place instead of the
backup's possibly smaller set.
- The Stalker lock dialog snapshots the category list before its lazy
import and opens only if the route is unchanged, so another portal's
categories can never be saved under this playlist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Electron a relock that overtook the initial content hydration waited
for the category reload before the content reload set the deferral flag;
the older unlocked hydration could publish its streams in that window.
withholdCatalog() now sets the flag itself, before anything is awaited.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rejected chunk (e.g. a stale PWA page after a deployment) escaped
applyStalker(), so a locked Stalker selection kept playing after a relock
and the Xtream step was skipped. The step now leaves the Stalker route on
a load failure, which clears the selection and stops playback, and the
Xtream step still runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With every group locked the filtered channel list is empty, so the
container showed its generic empty state and the groups rail's
"N locked · Enter PIN to show" row never appeared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Removing a playlist's last lock clears the SQLite index first; if the
clear or the store write then fails, the index is re-stamped from the
previous locks at once. Title matching and multi-source discovery query
the worker directly and trust the index, so the stale flag alone did not
protect them.
- A rollback publishes its store revision only after every type is
re-stamped, so a reload cannot read a later type through the attempted
stamps.
- docs: restore the index rules the earlier surfaces rewrite dropped from
the contract, now in the Lock store lifetime section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Master (#1712) moved the UI component barrel and the Stalker data layer out
of main.js and tightened the initial budget to 2 MB. The parental-lock
prompt imported the PIN dialog through the ui/components barrel and the
enforcement service injected the Stalker store at startup, which pulled
both back in (2.55 MB, over budget). The PIN dialog now loads through a
local lazy file on the first prompt, and the Stalker step loads only while
a Stalker route is open: initial total 1.65 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A relock during a page-1 Stalker search now filters the rows already on
screen at once, so old unlocked results are not clickable while the
replacement page is pending.
- When every M3U group is locked the groups rail still renders, with its
"N locked · Enter PIN to show" row, instead of the plain empty state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The idle relock no longer interrupts a playing radio station: playing
<audio> counts as activity, like video.
- A restore retries a failed lock-store read before checking a reused id
for stale locks, and aborts while the store stays unreadable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lock-store clear that failed after "Remove all playlists" only logged,
so a later restore reusing a playlist id could inherit the deleted
playlist's locks. The in-memory store now empties at once and the
persisted clear is retried on the next access; a restore that creates a
playlist starts it from empty locks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The M3U groups rail now renders the same "N locked · Enter PIN to show"
row as the portal category rail, so locked groups no longer vanish
without an in-context unlock.
- A failed relock-timeout write rolls back to the value read after the
settings retry, not to the pre-retry default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
updateSettings writes the whole settings object, which after a failed
startup read is the defaults; enabling the lock or changing the relock
timeout then replaced the user's persisted preferences. The read is
retried first and the write refused while settings stay unreadable. The
settings writes move to parental-lock-settings-writer.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The right-click Lock/Unlock (portal categories and M3U groups) built the
new list before entering the queue, so two quick toggles shared one
snapshot and the second dropped the first. Lock writes now accept an edit
of the current list, evaluated inside the queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Lock-store mutations run through one write queue: each rewrites the
whole persisted store, so overlapping edits could otherwise snapshot
the same store and the later write would drop the earlier edit.
- Deleting a playlist removes its locks through the PLAYLIST_DELETE_CLEANUP
hook; "Remove all playlists" clears the lock store once the deletion
has succeeded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stalker's dedicated lock button becomes the same "Manage categories"
(tune) button the Xtream rail has; it opens the lock-only dialog, so
every portal type shares one entry point and the rail header keeps
three actions.
- Right-clicking a category (Xtream, Stalker) or an M3U group offers a
single-row Lock / Unlock through the shared CategoryLockMenuComponent,
behind the same PIN gate and lock store as the dialog.
- The settings hint explains where locks are set; group lock strings added
to all locales (ru/de translated).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The embedded player defers selecting the channel until its stream
resolves, so the enforcement service's cleared selection could not retire
the request; it now carries the lock version it was issued under and is
dropped when a relock happened meanwhile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- clearSearchResults() advances the search request version, so a search
issued under the previous lock state cannot republish what a relock
just cleared.
- A lock write publishes its store revision only once every touched type
is stamped, so a reload triggered by it cannot read a later type
through its old stamps.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The warm-route hydration reads the cache directly; it now returns nothing
while the lock store withholds everything, like the live reads.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- A hidden-category lookup that lands after a later playback (same
provider id, another playlist) no longer overwrites the newer channel's
category; resolutions are generation- and playlist-checked.
- Withheld Stalker rows are keyed by id, stream_id, movie_id, series_id
or the row's cmd/name, so id-less rows no longer collapse onto one key
and stall paging past locked pages.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The workspace route resolver awaits ParentalLockService.initialize()
next to the settings load, so no route or catalog activates before the
PIN and lock store are known.
- Every lock write re-reads a failed store before building its edit, so a
recovered store is edited rather than overwritten.
- The deferred hydration reload runs under the publish guard of the
request that deferred it.
- Backup import validates every parental lock entry and rejects a damaged
list instead of erasing the persisted locks on restore.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Xtream store stays populated after leaving that portal, so its reload
runs on every apply; a locked M3U channel no longer keeps playing behind a
slow database or provider read.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The Xtream live layout resolves a playing channel's category through
the unfiltered rows when the visible list lacks it (search can play a
hidden category's channel); until that lookup lands the category is
unknown and a relock stops the channel.
- A relock that overtakes the initial hydration now withholds the
category publications too and reloads categories with the content.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The Xtream category guard no longer runs the item check on category-only
routes (Number(null) is 0), which prompted for the PIN on every unlocked
VOD and series category while the lock was active.
- A lock change during the initial Xtream hydration withholds the rows the
hydration publishes and runs the filtered reload once it has settled,
on every path that marks the content initialized.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- ElectronXtreamDataSource serves no categories, content or search hits
while the lock store withholds everything; its SQLite index may still
carry a stale stamp.
- setupPin decides whether to persist the switch from the settings value
before the PIN is stored, since enabled follows hasPin while the switch
is unknown.
- A lock store recovered by a later read marks its playlists stale so the
index is re-derived, a persisted entry must carry all three lists, and a
stale Stalker search page is dropped before touching the withheld-id
bookkeeping.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- A write that removes a playlist's last lock clears the SQLite index
first and drops the store key afterwards, so an interruption between the
two can only leave a state the startup reconcile repairs toward locked.
- A PIN hash read failure is distinct from an absent PIN: the session stays
locked and every PIN-protected step re-reads it first.
- Backup export awaits parental lock initialization and refuses to run
while the lock store is not readable, since an absent lock field means
"no opinion" on restore.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- A relock now fails closed immediately: the selected detail is stepped
off against the lock store, the catalog lists and stored search results
are emptied, and the filtered reloads publish only while the captured
lock version is still current.
- Backups carry M3U lock titles verbatim (exact dedup), since the locks
match group titles exactly.
- A relock-timeout write that fails reverts the in-memory value and shows
the settings save-failure snackbar.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The Electron mirror of the feature switch is awaited; a mirror that
cannot be written undoes the settings write, so a reload never starts
from a mirror that disagrees with the persisted switch.
- A failed multi-type re-stamp rolls the store back AND re-stamps every
touched type from it, since earlier types may already carry the new
locks; a failed rollback keeps the playlist stale (fail-closed).
- A persisted lock store whose nested entries are not what writeLocks
produces is a failed read, not an empty store.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The lock store is readable only once the SQLite index has been re-derived
from it, and a re-stamp that keeps failing keeps the session fail-closed,
so catalog reads can never serve rows stamped unlocked by a stale index.
- While everything is withheld, Stalker rows without a genre are withheld
as well (the store filter and the renderer predicate).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- The window before the initial lock store read settles now withholds
everything, like an unreadable store: settings can report the feature as
on before the locks are known.
- The store commits before the SQLite index re-stamp; a failed re-stamp
now rolls the store back, a failed rollback re-stamps on the next
access, and every launch re-derives the index from the store.
- Xtream category/content reloads fail closed: a rejected reload empties
the affected lists (content types drop back to idle) instead of keeping
rows read under the previous lock state.
- Enabling/disabling the feature persists through one guarded path that
undoes the in-memory switch and skips the Electron mirror on a failed
settings write.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>