Pair the existing journal_mode=WAL with the companion pragmas it needs to
actually pay off, and reduce read latency on hot query paths.
- synchronous=NORMAL: ~2-10x faster writes when paired with WAL. Safe — only
risks losing the last committed txn on power loss; DB stays consistent.
- cache_size=-64000: 64MB page cache (default is 2MB).
- temp_store=MEMORY: keep sort/group temp tables in RAM.
- mmap_size=268435456: 256MB memory-mapped I/O for reads.
Applied to both connection sites: the main read-write/read-only connection
in libs/shared/database and the EPG worker connection in electron-backend.
The per-connection pragmas (cache_size, temp_store, mmap_size) apply to the
read-only agent-backend connection too.
Inspired by matracey/iptvnator@4ad8f88; ported manually since the file has
diverged significantly and the worker connection didn't exist in that fork.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3d6901072049
Use Map and Set lookups for M3U favorite aggregation, dashboard global favorite mapping, and channel-list favorite/group resolution.
Remove the duplicate NgxIndexedDB provider registration and add regression coverage for duplicate URL and earliest-match behavior.
Entire-Checkpoint: c2cea9c530e6
The two component specs in this lib had been carrying 7 long-standing
failures rooted in three stale-mock issues, all unrelated to public
behaviour but enough to make the suite red on every run.
Header spec (5 failures, NG0303 on app-playlist-switcher):
- MockPlaylistSwitcherComponent was missing canRefreshActivePlaylist /
isRefreshingActivePlaylist inputs and refreshPlaylistRequested
output. The real PlaylistSwitcherComponent grew them when refresh
was added to the header; the mock never caught up. Re-aligned.
Workspace-shell component spec (2 failures):
- NG0301 on #dropZone="playlistDropZone" because overrideComponent's
set.imports list dropped PlaylistDropZoneDirective and the drop
overlay component. Added MockPlaylistDropZoneDirective and
MockPlaylistDropOverlayComponent.
- After the recent option-D refactor that lifted the import overlay
into its own child component, the spec also needed
MockWorkspaceShellImportOverlayComponent so the parent template
could resolve <app-workspace-shell-import-overlay />.
- MockWorkspaceShellHeaderComponent gained hasNoPlaylists input and
MockWorkspaceShellContextSidebarComponent gained hasPlaylists input
to match the real components.
- The "renders type-aware xtream import progress copy" test asserted
on inline overlay markup that no longer lives in the parent
template. Replaced with a structural test that the overlay child
renders/unmounts based on facade.showXtreamImportOverlay().
New focused spec for WorkspaceShellImportOverlayComponent (6 tests)
covers the moved progress-copy assertion at the right level by mocking
the WorkspaceShellXtreamImportService directly: title, progress copy,
determinate vs indeterminate progress bar, badge tone, and cancel
forwarding.
Final state: full feature lib suite is 66/66 (was 53/60 with 7
chronic failures). pnpm nx test web, pnpm nx lint
workspace-shell-feature, pnpm nx build web --configuration=production,
and the basic + settings web-e2e suites all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
Continues the workspace-shell facade decomposition by relocating two
clusters from the parent template into the components that own them.
Xtream import overlay:
- New WorkspaceShellImportOverlayComponent injects
WorkspaceShellXtreamImportService directly. The 70-line inline
overlay block in workspace-shell.component.html collapses to a
single <app-workspace-shell-import-overlay /> element, gated by
facade.showXtreamImportOverlay().
- Overlay-specific styles move from workspace-shell.component.scss
into the child component (~125 lines relocated).
- Facade drops 14 import re-export lines plus the cancelXtreamImport
delegation. The two facade tests that exercised label generation
now resolve the service via TestBed.inject and assert against it
directly.
Header bulk action:
- WorkspaceShellHeaderComponent gains headerBulkAction input and
headerBulkActionRequested output, plus a button that renders the
action when present (next to the existing headerShortcut button).
- The parent template wires facade.headerBulkAction() →
facade.runHeaderBulkAction(). The facade keeps the cross-cutting
computed and the multi-store dispatch method since both span
multiple stores.
Verification:
- workspace-shell.facade.spec.ts: 20/20 passing (two assertions
retargeted at WorkspaceShellXtreamImportService).
- Full feature lib: 53/60 passing — same 7 pre-existing failures.
- pnpm nx build web --configuration=production: success.
- pnpm nx lint workspace-shell-feature: clean.
- pnpm nx run web-e2e:e2e-ci--src/basic.e2e.ts: 3/3 across browsers.
- pnpm nx run web-e2e:e2e-ci--src/settings.e2e.ts: 12/12 across
browsers.
Facade: 917 → 897 lines. The bulk-action button now actually renders
in the header — closing a latent gap where the computed existed on
the facade but had no template binding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
Production build (stricter than test/dev) flagged the helper signature
as requiring a mutable array while WorkspaceViewCommandService.commands
returns readonly. The body uses spread, so widening the parameter to
readonly is the correct fix and unblocks `nx build web` plus E2E.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
The WorkspaceShellFacade had grown to 1666 lines, well past the 300/400
LOC target in CLAUDE.md. The component template binds to 54 facade
members directly, so the split preserves the public API by reference
re-export and keeps the template untouched.
Phase 1 — extract pure helpers (no DI, no behavior change):
- helpers/workspace-shell-constants.ts: i18n keys, RAIL_TOOLTIP_KEYS,
XtreamImportPhaseTone, WorkspaceHeaderBulkAction
- helpers/workspace-shell-route-utils.ts: toQueryString,
getRouteQueryParam, syncSearchQueryParam, bumpRefreshQueryParam,
getProviderFromPlaylist
- helpers/workspace-shell-search-labels.ts: resolveSearchPlaceholderKey,
resolveSearchScopeLabel, resolveActiveCategoryLabel, getRailTooltipKey,
translateRailLinks (each takes TranslateFn + snapshots as params)
- helpers/workspace-shell-import-labels.ts: build* helpers for the 14
Xtream import overlay labels and formatLocalizedNumber
- helpers/workspace-shell-command-builders.ts: buildCommandPaletteItems
plus the three command-definition builders, navigation command
factory, resolveCommand, comparePaletteCommands, getCommandGroupOrder
Phase 2 — extract the two component-scoped sub-services with the
cleanest boundaries:
- WorkspaceShellXtreamImportService: read-only labels, cancel action,
isImportRunning. Reads only XtreamStore + TranslateService.
- WorkspaceShellCommandPaletteService: MatDialog lifecycle, recent
commands integration, eager WorkspacePlayerCommandsContributor
bootstrap. Receives a typed CommandBuilderContext via function
argument from the parent — no circular DI.
The parent facade re-exposes every member by reference assignment so
signal identity is preserved for change detection. workspace-shell.
component.html is unchanged. workspace-shell.component.ts gains only
two provider entries for the new sub-services.
Verification:
- workspace-shell.facade.spec.ts: 20/20 passing
- pnpm nx lint workspace-shell-feature: clean
- workspace-shell-feature suite: 53/60 passing — same 7 pre-existing
failures (header & shell component specs hit unrelated NG0301
on playlistDropZone export); no regressions introduced
- CDP smoke: command palette opens with Recently Used + Global
Actions groups, settings nav disables search bar correctly
Facade reduces from 1666 to 917 lines. Each new helper/service is
under 250 lines except command-builders.ts (462) which is mostly
declarative command definitions. The parent facade is still over the
400-line hard max because cross-cutting orchestration
(searchCapability, runHeaderBulkAction, rail link computeds) reads
from multiple clusters; further reduction would require moving logic
into child components, which is deferred per the plan.
Plan: ~/.claude/plans/workspace-shell-facade-ts-is-now-1668-crispy-planet.md
(also copied locally to .plans/2026-05-01-workspace-shell-facade-split.md;
the working-tree .gitignore change excludes .plans from tracking).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
Adds a "Recently used" section at the top of the command palette (capped at 5,
persisted via StorageMap) and five "Switch player to ..." commands for one-step
player switching from anywhere. MPV/VLC are gated to Electron; the active player
entry is shown disabled. Switching applies to the next playback session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
- Added `XtreamCachedContentScope` type to manage content loading states.
- Updated live stream layout to show a loading skeleton when content is being fetched.
- Introduced styles for loading content in the live stream layout.
- Enhanced unit tests to cover loading states and caching behavior.
- Modified `XtreamWorkspaceRouteSession` to handle cached content loading and state management.
- Improved handling of optional connection values in playlist data.
- Updated workspace context panel to reflect import states and loading conditions.
Entire-Checkpoint: c2cea9c530e6
The "channel name + close" bar above the right-pane player was redundant
with the active row in the left list (which already shows the title and
the selection chrome) and was inconsistent with the rest of the app's
live-tv views, which never had it. Closing playback was already
achievable by selecting another channel; the X added cognitive load
without earning its space (ux-similarity-consistency,
ux-cognitive-load-reduce).
Also swap the collection-header border-bottom from
--mat-sys-outline-variant to --app-separator so the divider under
"Favorites" / "Recently viewed" actually carries enough contrast to
read in dark mode (matching the live-epg-panel summary-bar pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2cea9c530e6
Two issues kept the scrollbar invisible on the global Favorites and
Recently viewed live-tv lists:
1. The wrong files. The visible Favorites/Recent pages render through
<app-global-favorites-list> from portal/shared/ui (and the
workspace-shell variant), not the channel-list-container's tabs.
Apply .app-scrollbar to both of those components.
2. Specificity. Component CSS gets an Angular [_ngcontent-xxx]
attribute selector, so `.nav-list { overflow-y: auto }` resolves to
(0,2,0) — same as the doubled `.app-scrollbar.app-scrollbar` and
wins on source order (component CSS loads after globals). Mark
overflow-y: scroll !important on the utility so the contract holds:
applying .app-scrollbar always reserves the gutter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 8ae011808369
The favorites and recent views had local #id { overflow-y: auto }
which has higher CSS specificity than the global .app-scrollbar class.
On macOS, overflow-y: auto means overlay scrollbars that auto-fade,
so the gutter and themed thumb were never reserved.
Bake overflow-y: scroll into the utility itself (its whole purpose) and
drop the now-redundant overflow-y: auto from the two local rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: b2d2124cd992
Round out the channel-list-container views so the favorites and recent
panels get the same persistent scrollbar gutter and themed thumb as the
all-channels and groups views.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 2301533420c9
Move the always-visible themed scrollbar from epg-list into a global
.app-scrollbar utility class in styles.scss. Apply it to the All
channels and Groups view virtual-scroll viewports so the channel list
gets the same persistent gutter and themed thumb as the EPG list.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c470abf78379
Switch to overflow-y: scroll with scrollbar-gutter: stable so the gutter
is reserved (no layout shift between scrollable and non-scrollable
days). Locally style the thumb with --app-muted-color so it's clearly
visible across themes and platforms, overriding the global 6px
near-transparent style which was being hidden by the macOS overlay
auto-fade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 918ee7ade2f3
Separation: replace 4px gap with hairline dividers via ::before, hidden
around the active and current-program rows so they read as a continuous
emphasized block. Title line-height 1.4 → 1.3 + letter-spacing -0.01em
to match channel-name. Time uses en-dash, 0.78rem (parity with summary
bar), token-driven --app-muted-color. Description clamped to one line —
full text remains one click away via the info icon.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: b0389ce441a1
Drop the gradient + bevel + drop-shadow chrome from EPG rows so the panel
speaks the same flat-by-default visual language as the channel list. Add
--app-live-color token, tokenise the LIVE badge, tighten typography
(weight 600, text-wrap balance/pretty), and bump the info icon to 32px
for Fitts-compliant hit area.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 4cc5371f549e
Render live TV channel logos with object-fit: contain on a soft blurred
wash sampled from the logo itself, instead of stretching small (100-256px)
logos to fill 2:3 portrait cards with object-fit: cover. Mirrors the
treatment used by Apple Podcasts and Plex Live TV. VOD/series posters
still get the full-bleed cover treatment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 6bb6f5796090
- Implemented a TypeScript script that generates original fictional demo artwork for IPTVnator release screenshots.
- The script supports multiple artwork kinds (poster and backdrop) and qualities (low, medium, high, auto).
- It includes functionality for dry runs, generating artwork, manifest creation, and validation of generated assets.
- Integrated with OpenAI's image generation API to create artwork based on provided prompts and fixture data.
- Added a manifest file to track generated assets and their metadata.
- Ensured output directories are created as needed and included error handling for API requests.
Entire-Checkpoint: c6e522b4276c